Using Amazon SES in Python with Postman and Postfix
Details
Submitted By: | Craig@AWS |
AWS Products Used: | Amazon SES |
Language(s): | Python |
Created On: | |
Last Updated: |
By Patrick Altman, AWS Community Developer
A recent addition to the Amazon Web Services (AWS) family is Amazon Simple Email Service (Amazon SES). This article discusses the application programming interface (API) calls to Amazon SES through boto
, a Python library for AWS. It also walks you through a sample command-line tool called postman
, which is designed for use in your Postfix configuration to send mail through Amazon SES.all while remaining transparent to your applications.
In addition, you will discover an alternative to postman
for use with Django.django-ses
.and get the pros and cons of both solutions. Finally, the article looks at the information you can pull from the quota
and stats
APIs as well as how to avoid rejection and Internet service provider (ISP).level spam filtering.
Getting Started
This article assumes that you have already signed up for AWS and added Amazon SES to your account.
Install Postman
Let's start by installing postman
, a command-line client for Amazon SES built on top of boto
, the leading Python library for AWS. The library is designed to be fed raw email messages from Postfix through its send
command, but it also has useful commands for interacting with the service from the command line. For now, install the library and configure your system to work with your AWS account:
$ pip install postman
If you do not have pip
installed, you can install it manually by downloading the tarball from myhttp://pypi.python.org/pypi/postman and running:
$ tar zxf postman-0.5.tar.gz $ cd postman-0.5 $ python setup.py install
Installing postman
also installs boto
, as the library is a dependency. (The next section walks through the variousboto
API calls.) This code is open source and can be found on Github.
Configuration
With the code installed, you must now configure boto to use your account. You do this by editing the /etc/boto.cfg
file with the following content:
[Credentials] aws_access_key_id=<your_key> aws_secret_access_key=<your_secret_key>
You can find these keys under the Access Credentials section. Copy and paste them into this file.
Now you're ready to have some fun!
Postman
I wrote postman
for two reasons. First, I wanted an example for this article.a concrete example rather than abstract ideas that would provide more value to the reader who wants to get something working. Second, it solves a real-world problem for me. I wanted to send mail from a Django-based project I had been hosting on Amazon Elastic Compute Cloud (Amazon EC2) without having to deal with proper email configuration. I wanted to use Postfix so that applications on my server could send email out of the box without special configuration.
I mentioned that you can find the code on Github, but I'm actually going to be reviewing the code found in__main__.py
(see https://github.com/paltman/postman/blob/master/postman/__main__.py).
The first thing you'll notice is that this is just a simple command-line utility that does some simple wrapping of the API that boto
exposes. There's nothing fancy or remarkable about the code, but it serves my dual purposes well.
The send Command
Let's start with the send
command:
def cmd_send(args): ses = boto.connect_ses() out("Sending mail to: %s" % ", ".join(args.destinations), args) msg = sys.stdin.read() r = ses.send_raw_email(args.f, msg, args.destinations) if r.get("SendRawEmailResponse", {}).get("SendRawEmailResult", {}).get("MessageId"): out("OK", args) else: out("ERROR: %s" % r, args)
In standard boto
fashion, you get a connection object for the service. Next, call the send_raw_email
method on the connection object with content from standard input. That's all there is to sending an email message using Python andboto
through Amazon SES. Some notable improvements here would be to catch quota/rate exceptions and try again after a sleep period or.better yet.return the appropriate return code so that Postfix could manage the retry.
The verify Command
Now on to the other commands, starting with verify
:
def cmd_verify(args): ses = boto.connect_ses() for email in args.email: ses.verify_email_address(email) out("Verification for %s sent." % email, args)
Again, this is a simple call to a single boto
connection method, verify_email_address
. You need to call this method for every email address from which you want to send a message. In fact, while in the Sandbox, you will also need to call this method for the email addresses you are going to send mail to. After calling this method, Amazon SES sends an email with a confirmation link. The recipient must click the link before the address is considered verified. Once verified, you can send mail as that address (or to that address during the Sandbox period).
The list_verified Command
To check which email addresses are verified on your account, you can run the list_verified
command like so:
$ postman list_verified
The code for this command is slightly more involved, but it is just cleaning up the return data from the single boto
method to provide cleaner output:
def cmd_list_verified(args): ses = boto.connect_ses() args.verbose = True addresses = ses.list_verified_email_addresses() addresses = addresses["ListVerifiedEmailAddressesResponse"] addresses = addresses["ListVerifiedEmailAddressesResult"] addresses = addresses["VerifiedEmailAddresses"] if not addresses: out("No addresses are verified on this account.", args) return for address in addresses: out(address, args)
This code sends output to standard out as a listing of each email address that has been verified.
The show_quota and show_stats Commands
Next, two commands.show_quota
and show_stats
.query the service for data about current limits as well as information on what you have sent:
def cmd_show_quota(args): ses = boto.connect_ses() args.verbose= True data = ses.get_send_quota()["GetSendQuotaResponse"]["GetSendQuotaResult"] out("Max 24 Hour Send: %s" % data["Max24HourSend"], args) out("Sent Last 24 Hours: %s" % data["SentLast24Hours"], args) out("Max Send Rate: %s" % data["MaxSendRate"], args)
def cmd_show_stats(args): ses = boto.connect_ses() args.verbose = True data = ses.get_send_statistics() data = data["GetSendStatisticsResponse"]["GetSendStatisticsResult"] for datum in data["SendDataPoints"]: out("Complaints: %s" % datum["Complaints"], args) out("Timestamp: %s" % datum["Timestamp"], args) out("DeliveryAttempts: %s" % datum["DeliveryAttempts"], args) out("Bounces: %s" % datum["Bounces"], args) out("Rejects: %s" % datum["Rejects"], args) out("", args)
Again, these are simple wrappers around two boto
methods.get_send_quota
and get_send_statistics
.that provide some parsing out of the data structure that boto
returns to provide cleaner console output.
The delete_verified Command
The last command.delete_verified
.provides a way to remove an email address from the verified emails that are allowed be in the From
header of an email message:
def cmd_delete_verified(args): ses = boto.connect_ses() for email in args.email: ses.delete_verified_email_address(email_address=email) out("Deleted %s" % email, args)
The rest of the __main__.py
module consists of Python code that parses input arguments and calls the right command function. The module is missing one API call that boto
does provide, however: a more structured emailsend
that does not require a raw email message body. Adding this call provides a clean method for sending email messages from the command line without having to structure and PIPE
in content with email headers. I will leave that addition as an exercise for later (or perhaps an ambitious reader).
Postman and Postfix
Now that you have installed postman
and are familiar with what it's doing, you're ready to hook it up as your default transport for Postfix. I am no postfix expert, but after a bit of searching and finding instructions for hooking up a Perl script in Amazon SES as a Postfix transport, I thought I could do the same thing with postman send
:
# /etc/postfix/master.cf postman unix - n n - - pipe flags=R user=ubuntu argv=/usr/local/bin/postman send -f ${sender} ${recipient}
# /etc/postfix/main.cf default_transport = postman
If, like me, you have a Django project being served on this machine and want it to be able to send email through thispostman
transport, you need to update two settings in your project's settings.py
file:
# Django project's settings.py SERVER_EMAIL = "user@gmail.com" DEFAULT_FROM_EMAIL = "user@gmail.com"
After saving this change and bouncing your server to reload the settings.py
changes, you must verify the email addresses you are going to be sending from:
$ postman verify user@gmail.com
Then, reload Postfix to pick up the new changes to main.cf
and master.cf
:
$ sudo /etc/init.d/postfix reload
While you're in Sandbox mode, remember that you must verify any emails you are going to send to, as well, so after doing that, try sending a few test messages from your Django project. You can tail
the Postfix logs to aide in troubleshooting this setup, but you shouldn't have any problems:
$ tail -f /var/log/mail.info
Quotas and Statistics
There are two limits on your Amazon SES account: a daily quota and a send rate. The daily quota is how many emails you are permitted to send within a 24-hour period. The send rate is how many emails your account can send per second. For example, you start out with a daily quota of 1000 and a 1/email/sec rate, so if you had a batch of 1000 emails to send, you would have to throttle the sending to 1 per second, or else you would get an exception. So, it would take approximately 17 minutes to send all 1000 messages, but after doing so, you would need to wait another 23.75 hours until you could send anymore. The postman show_quota
command will assist you in monitoring these limits, which Amazon raises according to your usage over time.
$ postman show_quota Max 24 Hour Send: 1000.0 Sent Last 24 Hours: 9.0 Max Send Rate: 1.0
Amazon provides an API to fetch statistics on emails sent grouped in 15-minute intervals for a rolling previous two-week period. These statistics are useful for assisting in monitoring how your application is using and sending email. They provide counts on delivery attempts, complaints, bounces, and rejects. The postman show_stats
command prints these figures to the console for you:
$ postman show_stats Complaints: 0 Timestamp: 2011-04-10T18:48:00Z DeliveryAttempts: 1 Bounces: 0 Rejects: 0 Complaints: 0 Timestamp: 2011-04-10T19:18:00Z DeliveryAttempts: 1 Bounces: 0 Rejects: 0
You receive Bounce and Complaint notifications via email with the address that either bounced or complained. It is wise to take action and remove the affected email address from your application to avoid future, repeated sends to that address.
Avoiding SPAM Filters
To avoid spam filtering or outright rejections from ISPs, it's a good idea to set Sender Policy Framework (SPF) and Sender ID records. These are Domain Name System (DNS) TXT records that have the following content:
- SPF:
v=spf1 include:amazonses.com ?all
- Sender ID:
spf2.0/pra include:amazonses.com ?all
If you already have either of these records, you MUST add these entries as additions or replace the current entries, as ISPs will query and see a different authorization and reject them, whereas if they are simply missing, it might allow the authorization through and/or mark it as spam. Bottom line: Add the records to the domains from which you are sending email to ensure high-quality delivery of your email.
django-ses
One alternative to the postman
solution presented earlier is a project by boto
core committer, Harry Marr, calleddjango-ses
, which you can find at Github. It is a Django mail back end. Obviously, this tool only works within the confines of a Django-based project, so it's not exactly an apples-to-apples comparison to postman
. However, in the context of a Django project, the tools solve the same problem.
The django-ses
project includes user interface elements for graphing and displaying statistics, which may be more useful than pure console output. In addition, depending on where you were deploying your Django project, you may not have control over your Postfix configuration or have a good email solution in place at your host provider. So using postman
would not be an option, whereas django-ses
is 100 percent Python and is deployed as part of your site.
The downside to django-ses
is that sending mail is a blocking call. Depending on what is triggering the email to send, the message may have to wait on the request to Amazon SES to finish before returning. This delay could become problematic in high-performance scenarios.
Summary
With the aptly titled "Simple Email Service" becoming one of the latest additions to its plethora of cloud services, Amazon continues to impress. Indeed, the service is simple.just as email should be. Using postman
, django-ses
, or even boto
directly, you are well on your way to integrating email services into your application with the backing of an email platform that will scale when you need it to.
Resources
This article highlights a aspects of working with Amazon SES. Here are a few more resources available to help you learn more:
- AWS. Learn more about each Web service in the AWS suite.
- Amazon SES. Learn more about Amazon SES on the AWS Web site.
- Create an AWS Account. Sign on to create an AWS account.
- Developer Connection. The community site for AWS developers includes forums on AWS, a Solutions Catalog for examples of what your peers have built, and more.
- Resource Center. Part of the Developer Connection site, the Resource Center has links to tutorials, code samples, technical documentation, and other resources for building your application on AWS.
'Web Service > AWS' 카테고리의 다른 글
디스크 채우기 테스트(대용량 파일 생성) (0) | 2012.08.15 |
---|---|
[mhddfs]여러 disk 하나로 mount 하기 (0) | 2012.08.14 |
Sending Email from EC2 Instances (0) | 2012.07.27 |
Ubuntu에 postfix 설정하기 (0) | 2012.07.19 |
[Mysql] 외부에서 mysql에 접근할 권한 주는 명령어 (0) | 2012.07.17 |