Successfully sending email from a web application is a complex problem. Unfortunately, this complexity is often hidden until you have to spend hours or days trying to figure out why your email isn’t getting to your users.
Sending an email in PHP is seemingly as simple as this:
mail($to, $subject, $body);
Frameworks like Rails and Django have similar methods for sending email.
With the default configuration and the likely case that your web application server has a Mail Transfer Agent (MTA) like Sendmail or Postfix, running on it, PHP will happily execute this call for you. And it’ll return true, suggesting that the email will actually be delivered. Sometimes this will work beautifully. However, unless you’ve done a lot of work upfront to ensure that your web server is allowed to send email on behalf of your domain, you’ll start getting complaints from users that your email is ending up in their spam folder or not arriving at all.
Rather than spending the time focusing on making your web server a valid point of origin for your email, it’s much easier to punt the responsibility to a service that’s already been setup to send email on behalf of your domain, like an existing email provider. Or, better yet, you can use a 3rd party service that specifically focuses on sending large volumes of transactional email from web apps. Some options are Sendgrid, Postmark, or Amazon SES. Just follow the directions provided by any of these services for setting up DNS records to ensure that they are authorized to send email from your domain.
Note to GMail/Google Apps users: It’s not a great idea to have your web app send email using Google’s SMTP servers, as Google limits all accounts to sending a maximum of 500 emails per day. If you start sending significant numbers of messages, you’ll easily max that out. It’s likely that many other mail providers have similar limits, so check with yours before using their SMTP servers to send high volumes of email.
Remote Network Connections
However, sending mail via a remote server introduces a whole new problem: a remote network connection has to made whenever your app sends an email. So every time a user submits a request that triggers an email being sent, a connection has to be made to the remote SMTP server and the message data needs to be passed through it. All of this probably happens while the end-user is waiting for a response. If network latency is high then it can really slow down that response being returned to the user, or if latency is really bad the user’s connection to your site might timeout entirely. Even worse, the remote SMTP server might be unreachable because it’s down or there’s a network partition somewhere along the line. If that happens, depending on how your app is built, it may fail silently or an error will be be returned to the user, in both cases the email is never sent.
To prevent such problems, rather than attempting to connect to your SMTP server while the user is waiting for a response, it’s preferable to queue the message locally, return a response to the user, and then in the background connect to your SMTP server and send the message. If the SMTP server is unreachable, requeue the message and try to send it later.
You could build all that logic into your application. Maybe store messages to be sent in a database table, and have a background job that attempts to send them, but that really seems like a lot of work and overhead for something as simple as sending email. Low and behold, all this queuing and retry logic for sending messages to remote SMTP servers is already built into MTA software like Postfix. As we saw above, you can run Postfix locally on your web server, which virtually eliminates any latency or possibility of a network partition when your application is connecting to send an email.
But you’ve just spent all this money on a Sendgrid account so that your email you wouldn’t end up in a user’s spam box, why would you go back to using a local MTA!? Well it turns out that MTAs can be configured to relay all of the mail they receive to an upstream SMTP server (like Sendgrid, Postmark, or your email provider of choice), that server in turn will go ahead and relay the message to third party recipients.
So rather than connecting to a remote server to send email, your web application can connect to it’s local instance of Postfix. Postfix accepts the message, quickly adds to it to its message queue, and responds with a success code. Your web app can then go ahead return a response to the user, who can go about their business. In the meantime, Postfix will asynchronously attempt to connect to your upstream SMTP server and pass the message along. In the event that it can’t reach the upstream server, it’ll put the message back on the queue and try again later. Once the upstream SMTP server gets the message, it sends it to the recipient’s SMTP server; so to that server it looks like the email is coming from a valid point of origin: your email provider, rather than your web server.
Implementing the Solution
If your application server is running a *NIX system, I highly recommend setting up Postfix as a local MTA to relay messages to an upstream SMTP server. It’s fast and relatively easy to configure. If it’s not already installed, you should be available to install it with your system’s package manager. For example, using Debian or Ubuntu:
If the install process asks you about configuring Postfix, simply select No Configuration, we’ll configure it ourselves in just a minute.
Once it’s installed, test to ensure that postfix is capable in sending mail in it’s default state, by running the following command:
echo "Oh hi there" | sendmail your_email_address@host.com
This will send an email using Postfix to your email address. If you don’t receive it, first check your spam folder, then check the Postfix log, usually stored at /var/log/mail.log, for errors.
The first thing to do whenever you setup an MTA, is to ensure you’re not creating an open relay. Open relays allow anyone on the Internet to connect to your MTA and send mail using it. This is really bad. Spammers will find this and use it to send lots of email through your server. Leading to many terrible things, including having your IP blacklisted as a server that sends spam. By default Postfix accepts all connections on port 25, so ideally you should deny all remote connections to port 25 via your firewall. In addition to that, you should to configure Postfix itself to only accept connections from localhost, by adding this line to the bottom of Postfix’s main.cf file (located at /etc/postfix/main.cf on Debian/Ubuntu):
inet_interfaces = loopback-only
Once you’ve added that, restart the Postfix daemon. On Debian or Ubuntu:
/etc/init.d/postfix restart
With that, only connections coming from the within the server itself will be able to send email through Postfix.
Note to Amazon SES users: While it is possible to relay mail from Postfix to Amazon SES, the configuration is slightly different than what is outlined beyond this point, for more information check out Amazon’s guide.
Your upstream SMTP server probably requires authentication, so you’ll also need to install some SASL libraries that Postfix relies on for authentication. On Dedian/Ubuntu:
apt-get install libsasl2-modules
Or a yum-based system:
yum install cyrus-sasl-plain
Postfix then needs to be configured to relay all the mail it receives to your upstream SMTP server. To do that, simply add the following lines to the bottom of main.cf file:
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:your-SMTP-Username:your-SMTP-Password
smtp_sasl_security_options = noanonymous
header_size_limit = 4096000
relayhost = [your.smtp.host]:25
Obviously replacing the auth credentials and host with those provided by your email provider.
Or, if your upstream SMTP server supports it, enable TLS encryption, so that your messages and authentication credentials aren’t sent in plaintext, use this configuration:
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:your-SMTP-Username:your-SMTP-Password
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = may
header_size_limit = 4096000
relayhost = [your.smtp.host]:587
After updating the configuration file, restart Postfix.
/etc/init.d/postfix restart
And configure your web application to send email through the SMTP server localhost, on port 25 with no authentication. For example in Rails:
ActionMailer::Base.smtp_settings = {
:address => "localhost",
:port => '25',
:domain => "yourdomain.com"
}
Send some test emails from your web app, or from the command line:
echo "Oh hi there" | sendmail your_email_address@host.com
If you look at the headers of these messages, they should indicate that they’re being sent through your upstream SMTP server rather than directly from your web app server.
This gives you the advantages of sending mail via local MTA: minimal network latency, as well as having messages queued and retried when the upstream SMTP server is unavailable, combined with the deliverability advantages of using an SMTP server that’s properly setup to deliver mail on behalf of your domain. To the recipient mail servers, messages will look like it’s coming from your proper SMTP server rather than your web app server.