A rock-solid setup for sending SMTP mail from your EC2 web server
(None of this is EC2-centric, but it’s particularly needed on EC2.)
A frequent topic of discussion on the EC2 forums is how to send email reliably, efficiently, and especially without it being marked as spam. I found that even with a valid SPF record most mail sent from an EC2 instance was marked as spam or silently discarded.
This is probably partly because of the lack of matching reverse DNS records. But spam filters can be a bit arbitrary and the easiest way is to relay outgoing mail through a good smtp provider. (I don’t recommend relaying outbound mail through Google Apps, they supposedly have a 500 messages/day limit according to many people on their forums, although I couldn’t find that published anywhere. UPDATE: The info is here, thanks John Ward.)
I have tried a couple of SMTP providers, and I recommend AuthSMTP. They are reliable, have good service, and our mail that’s delivered through them almost never gets marked as spam. Also, they have monthly quotas rather than daily, so you have a chance to increase it before you hit the limit.
Rather than deliver directly to the AuthSMTP mail server from your web app it’s a good idea to deliver to a local queueing mail server, which will forward via the AuthSMTP gateway. Your web app will deliver mail to localhost (or perhaps a dedicated instance if you prefer), port 25.
This has several advantages:
- Your web server can finish the request more quickly.
- There’s less chance that the mail server will be unavailable. At least the mail will be queued locally until the remote server becomes available again. AuthSMTP has proven to be quite reliable, but it has been unavailable on a couple of occasions.
- AuthSMTP limits the number of concurrent connections that you can make. You can easily configure your local mail server to limit the number of outgoing connections to the gateway.
Configuration
I recommend using Postfix, it’s fast, reliable and most importantly, easy to configure. Your Linux distribution will definitely have a Postfix package available (it comes pre-installed on EC2 on Rails). On Debian or Ubuntu install with:
sudo aptitude install postfix
Here’s the config file, /etc/postfix/main.cf:
myhostname = www.YOURDOMAIN.com mydomain = YOURDOMAIN.com myorigin = $mydomain smtpd_banner = $myhostname ESMTP $mail_name biff = no append_dot_mydomain = no alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases mydestination = localdomain, localhost, localhost.localdomain, localhost mynetworks = 127.0.0.0/8 mailbox_size_limit = 0 recipient_delimiter = + # SECURITY NOTE: Listening on all interfaces. Make sure your firewall is # configured correctly inet_interfaces = all relayhost = [mail.authsmtp.com] smtp_connection_cache_destinations = mail.authsmtp.com smtp_sasl_auth_enable = yes smtp_sasl_password_maps = static:YOUR_AUTHSMPT_USER_ID:YOUR_AUTHSMTP_PW smtp_sasl_security_options = noanonymous default_destination_concurrency_limit = 4 soft_bounce = yes
How simple is that?! Have you ever seen a sendmail config file?
soft_bounce is important because it means that postfix will queue the messages if they’re bounced by the remote gateway for any reason (this is only if it’s bounced by the gateway, not if it’s bounced by the destination server). This would usually be caused by some configuration problem like an authentication failure. If the message is bounced by the eventual destination server (e.g. the mailbox doesn’t exist or is full), or if the destination server can’t be contacted, your local server won’t know about it because the message has already been accepted by the gateway. (It’s probably a good idea to keep track of bounced messages returned by the eventual destination server, see “Don’t spoof the From field” below.)
default_destination_concurrency_limit is so you stay within AuthSMTP’s concurrent connection limit. If you have Postfix running on multiple instances you’ll need to adjust this accordingly.
To see mail that’s stuck in the queue:
mailq
Postfix will automatically try to resend it, but you can force it to be sent immediately using:
sudo postqueue -f
Monitoring
Of course you need to know if anything goes wrong with the mail delivery and it won’t be in your web app’s log. I use scripts in /etc/cron.hourly to check logs and mail me the output if there are errors. But when it comes you mail delivery failure you might have a bit of a chicken-and-egg problem: you can’t use postfix to send the mail if postfix is having problems. Here’s a simple ruby script to send emergency mail via a different mail server. It’s configured to use Google Apps (you’ll need to create a new account to send the mail from), if you don’t use Google Apps you can easily change this to use a different mail server.
Save this as /usr/local/bin/emergency_mail_sender:
#!/usr/bin/env ruby
# This is a simple script to send mail via an alternate server when there are
# errors with the normal queueing mail sender
# The subject is the first command-line arg and the body is received on stdin
#################################
from_address = "admin_mail_sender@YOURDOMAIN.com"
to_address = "admin@YOURDOMAIN.com"
smtp_server = "smtp.gmail.com"
smtp_port = 587
smtp_mail_from_domain = "YOURDOMAIN.com"
smtp_account_name = "admin_mail_sender@YOURDOMAIN.com"
smtp_password = "YOUR_PASSWORD"
smtp_authentication_type = :plain
debug = false
#################################
subject = ARGV[0]
body = $stdin.read
require 'rubygems'
require 'net/smtp'
require 'tlsmail'
exit if body.nil? || body == ""
msgstr = <<END_OF_MESSAGE
Subject: #{subject}
#{body}
END_OF_MESSAGE
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
smtp = Net::SMTP.new(smtp_server, smtp_port)
smtp.set_debug_output $stderr if debug
smtp.start(smtp_mail_from_domain, smtp_account_name, smtp_password, smtp_authentication_type) do |s|
s.send_message msgstr, from_address, to_address
end
Here’s a script that can be run by cron every hour to check for mail delivery problems, it uses the emergency_mail_sender script to notify you of the problem. It works on Ubuntu (but it needs the logtail package installed), it might not work on other systems. Save this as /etc/cron.hourly/check_mail_logs
#!/bin/sh hostname=`hostname -s` mailer = /usr/local/bin/emergency_mail_sender /usr/sbin/logtail -f/var/log/mail.warn | $mailer "$hostname: mail warnings" /usr/sbin/logtail -f/var/log/mail.err | $mailer "$hostname: mail errors" /usr/sbin/logtail -f/var/log/syslog | grep 'status=' | egrep -v 'status=sent' | $mailer "$hostname: undelivered mail"
SPF
Here’s your SPF record:
v=spf1 include:authsmtp.com include:aspmx.googlemail.com ~all
If you’re not using Google Apps to send mail for your domain remove include:aspmx.googlemail.com. If you want to create your own SPF record there’s a good SPF record generator at spfwizard.com.
Don’t spoof the From field
You should only send mail from somebody@yourdomain.com. If you try to send mail from somebody@pauldowman.com, for example, the receiver will see that pauldowman.com has an SPF record, and that it doesn’t authorize your mail server. Then into the spam folder you go.
To get around this you can send from something like noreply@yourdomain.com, and set the Reply-To header to somebody@pauldowman.com. You can even set the name in the from field, for example: “Paul Dowman via yoursite” <noreply@pauldowman.com>. The Reply-To header will make sure that most people’s replies go to the correct address, but a few will inevitably end up at noreply@yourdomain.com so it’s probably a good idea to set up an autoresponder at that address, or at least make sure the message bounces so the user eventually realizes the mistake.

Ilya Grigorik:
Awesome writeup Paul, much appreciated! This will save us at least a couple of days of debugging.
18 February 2008, 10:30 amRyan Duffield:
Very timely! I was just wondering what the best approach would be for some of my EC2 apps. :-)
23 February 2008, 10:01 pmgilltots:
The only downside to AuthSMTP is the cost. for example, to send 15,000 emails a month costs $248/year, which comes out to .14 cents per email. compare that with 1 google apps account which allows 500 emails/day (15,000/month) for $50/year, or .028 cents per email. that means authsmtp is 5 times as expensive! and don’t forget to factor in the outgoing bandwidth costs that you’ll be charged for sending data from EC2. maybe amazon is working on Simple SMTP or something, or google will up their daily limit. we can hope!
3 March 2008, 11:00 pmJohn Ward:
The limit for Google Apps is documented here
http://www.google.com/support/a/bin/answer.py?answer=59797
4 March 2008, 4:00 amPaul Dowman:
John: Thanks for the link.
gilltots: A hard daily limit of 500 messages is too low for me. AuthSMTP has monthly limits and they send you a warning when you hit 80% of your limit so you can increase it (they charge a pro-rated amount).
Hey, I don’t mean to shill for any one vendor here, if it makes sense for you to save money and use Google Apps then go for it! I still recommend having a backup provider though, or some other way to receive warnings when there are problems with your normal SMTP server.
4 March 2008, 8:15 amfrederic sidler:
I also found in the amazon forum that fastmail.fm was a good solution and reliable.
21 March 2008, 11:54 amI don’t know yet if we can use it as mail relay, but they seem to be serious and pricing is very competitive
http://fastmail.fm/pages/fastmail/docs/pricingtbl.html
Neil Gunder:
We’ve been using http://triggermail.net which has been great. They helped us with the setup and monitor all our email delivery. Also works great with google analytics. They also offer it free to smaller sites. If you go to them please tell them I sent you.
4 April 2008, 2:47 pmTom:
You. Rock.
5 May 2008, 10:31 amBill:
Terrific post. Just what I was looking for. We recently setup a postfix server on Slicehost, which delivers to Gmail fine, but emails to Yahoo disappear into the ether. I’m not sure what happens with Hotmail since Hotmail won’t let me log in (good grief… how big are they and they still have login bugs?), but I’m going to sign up for AuthSMTP and combined with your config file, I bet that fixes the Yahoo problems.
27 May 2008, 8:45 pmBipinDas:
Paul
30 May 2008, 3:11 amIt was great. This will help a lot.
Alan:
Paul, absolutely brilliant.
This was indeed a problem we faced at Amazon EC2, and after doing a little digging your post shone through like a guiding light in the storm.
Thanks for pulling this together.
Quick Question: How are you authorising Postfix to only accept email coming from your other EC2 instances?
26 June 2008, 5:34 amPaul Dowman:
Alan,
In my case I run postfix on each app server, and the app delivers mail via postfix on localhost because it’s just easier that way (one reason not to might be if you have lots of hosts and you’re exceeding the AuthSMTP’s concurrent connection limit.
But you could use the EC2 firewall rules to allow connections only from your own instances (you should block port 25 to others anyway). You might need to change some settings like mynetworks if you want to relay from other hosts.
26 June 2008, 8:28 amZubair Khan:
Hi Paul,
I created the AuthSMTP account like you mentioned in your writeup and installed postfix using yum/remi on my fedora 4 EC2. But how do I point my php application to use this setup? My application has the option of a. smtp( server/user/password) b. sendmail (path to sendmail /usr/sbin/sendmail) or phpmail.
Also the default /etc/postfix/main.cf file did not have the below user/password variables like you mentioned.
smtp_connection_cache_destinations = mail.authsmtp.com
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:YOUR_AUTHSMPT_USER_ID:YOUR_AUTHSMTP_PW
smtp_sasl_security_options = noanonymous
Any input will be greatly appreciated.
Thanks,
Zubair
4 July 2008, 11:45 pmPaul Dowman:
Zubair, in your PHP code mail configuration you should use smtp with “localhost” as the server, “25″ as the port (it’s the default so you probably don’t need to specify that) and no username and password. And you don’t need to keep the default main.cf file, you can replace it with what I’ve suggested above or edit it as you like.
5 July 2008, 7:28 amZubair Khan:
Thanks Paul for the reply, I did like you said, but it seems my script is still trying to use the local mail server. Do I need to start postfix or something? I don’t get an error but I get mail back saying ..
The original message was received at Sat, 5 Jul 2008 15:01:25 -0400
from localhost.localdomain [127.0.0.1]
—– The following addresses had permanent fatal errors —–
(reason: 553 http://www.spamhaus.org/query/bl?ip=75.101.231.205)
—– Transcript of session follows —–
… while talking to smtp.secureserver.net.:
>>> RCPT To:
<<< 553 http://www.spamhaus.org/query/bl?ip=75.101.231.205
550 5.1.1 … User unknown
Unknown command: “<<<”
& 550: Invalid message number
My apologies for sending you error messages. Just really desperate. The funny thing is, I keep getting these same reply messages even if I use sendmail or smtp.
Thanks again in advance.
Zubair
5 July 2008, 2:12 pmZubair Khan:
Hi again Paul, FYI, I pointed my application directly to authsmtp.com and was able to send the email. Only that it reached the destination marked [SPAM].
Thanks for your help.
Zubair
5 July 2008, 2:48 pm