ちょっと話題の記事

How to send Email from PHP application via Amazon SES

2013.10.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Amazon SES provides bulk mail sending service. This makes it easy for developers to send lots of e-mail and operation engineers will be relieved from the maintenance load. Whether or not placed in on-premises or cloud, I think MTA server on EC2 is difficult to maintain because we have to fight against e-mail black list. And We have to send a request to AWS for registering PTR record for the MTA server. SES eases this burden. Amazon SES fits to the systems like below:

  • A small-scaled system which does not have operation engineers
  • Mail magazine, notification mails regards the service ...

SES provides two protocols for sending email; SMTP and HTTP. For example, when you send a email from Java application, JavaMail, the most popular library for sending email, will be the solution. But what would be the best solution for PHP? I'll introduce 3 ways and compare them.

ses_php_image

1. via standard mail() function

PHP has a standard function mail(), which simply pass the To address, subject, and mail body to the local Sendmail/Postfix command. This means that we need to configure our local SMTP server for relaying the received mail toward Amazon SES. I used Postfix instead of Sendmail this time (because I could not understand complicated Sendmail configuration...)

Firstly, you need to add some lines to /etc/postfix/main.cf:

relayhost = email-smtp.us-east-1.amazonaws.com:25
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/relay_password
smtp_sasl_security_options = noanonymous
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt

You need to create a IAM user which can use SES. The access key and secret key for this user is used for SMTP-Auth for SES. For this step, see the official documentation.
Then you have to make database hash file for Postfix by following steps:

# 'xxx...' indicates IAM access key created in the above step, and 'yyy...' does IAM secret key.
$ echo 'email-smtp.us-east-1.amazonaws.com:25 xxxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyy' | sudo tee /etc/postfix/relay_password
$ sudo postmap /etc/postfix/relay_password
# reload the configuration
$ sudo service postfix reload

The php script is like below:

<?php

$to        = '<youraddress>@<yourdomain>';
$subject   = 'sample email part1';
$message   = 'Sent via mail() function.';
$header    = 'From: <youraddress>@<yourdomain>' . "\r\n";
$param     = '-f<youraddress>@<yourdomain>';

mail($to, $subject, $message, $header, $param);

The point is the fourth argument $param. It passes command line argument to the Sendmail binary. -f option is the 'envelope from'. Without this option, Amazon SES rejects the message and following messages can be found on /var/log/maillog:

Oct 25 09:57:07 ip-172-31-7-40 postfix/smtp[14072]: D6CA323AD1: to=<ec2-user@ip-172-31-7-40.ap-northeast-1.compute.internal>, relay=email-smtp.us-east-1.amazonaws.com[54.243.73.188]:587, delay=2, delays=0.01/0/1.8/0.21, dsn=5.0.0, status=bounced (host email-smtp.us-east-1.amazonaws.com[54.243.73.188] said: 501 Invalid MAIL FROM address provided (in reply to MAIL FROM command))

2. via other libraries

PHP has several libraries to send e-mail via SMTP, such as PEAR::Mail. In often case, you have to configure SMTP server, username, password and so on for the library. As an example, Today I use CakeEmail, which is the default SMTP library for CakePHP.

At first, you need to set configuration to Config/email.php file like below:

class EmailConfig {

	public $ses = array(
		'transport' => 'Smtp',
		'tls' => true,
		'from' => array('<fromaddress>@<fromdomain>' => 'Display Name'),
		'host' => 'email-smtp.us-east-1.amazonaws.com',
		'port' => 587,
		'username' => 'xxxxxxxxx',
		'password' => 'yyyyyyyyy',
		'timeout' => 30,
		'client' => null,
		'log' => true,
		'charset' => 'utf-8',
		'headerCharset' => 'utf-8',
	);
}

You need to set 'tls' to true because SES only accepts tls connection. And username and password are the IAM access key and secret key.
The simplest Controller will be like below:

<?php

App::uses('AppController', 'Controller');
App::uses('CakeEmail','Network/Email');

class MailController extends AppController {

    public function index() {

        $email = new CakeEmail('ses');
        $email->to('<youraddress>@<yourdomain>')
            ->subject('Sample email subject send by CakeEmail')
            ->send('This mail is sent by CakeEmail');
    }
}

Then you will receive a mail from CakeEmail.

3. via SES REST API

AWS SDK for PHP2 enables you to send email more conveniently. When you use SDK, you will not be affected by the configuration of postfix. The sample codes are below : (as a prerequisite, you need to install AWS SDK for PHP2 via composer. see there

<?php

require '/path/to/vendor/autoload.php'; // include autoloader of composer.

use Aws\Ses\SesClient;
$client = SesClient::factory(
    array(
        'region' => 'us-east-1' // SES has only us-east-1 endpoint, but can be used from global
    )
);

// email settings
$options = array(
    'Source'      => '<fromaddress>@<fromdomain>',
    'Destination' => array(
        'ToAddresses' => array('<youraddress>@<yourdomain>'),
    ),
    'Message' => array(
        'Subject' => array(
            'Data' => 'Sample email using SDK',
            'Charset' => 'utf8'
        ),
        'Body' => array(
            'Text' => array(
                'Data' => 'This email is sent by AWS SDK',
                'Charset' => 'utf8'
            )
        )
    )
);

try {
    $client->sendEmail($options);
} catch(SesException $e) {
    echo $e->getMessage();
    exit(1);
}

This case does not require any changes to postfix, or any other middleware. Note that you have to provide access key and secret key in SesClient::factory() when you run this script except for the EC2 instances associated with IAM Role.

Summary

I introduced 3 ways to use SES from PHP. If you develop a system from scratch, I recommend 3rd pattern, which use SDK. Standard mail() requires the configuration of local SMTP daemon, I feel it inconvenient. Additionally, this pattern behaves differently between Linux and Windows.

The 2nd pattern, which uses general libraries, is considarable, but the question is that what is the most useful mail library? I think PEAR::Mail is most popular, but it is almost obsolete. Like CakeEmail, if you have some libraries included in the Framework, you can use them and almost all SMTP library will work.

The 3rd pattern requires SDK installation, but that's it. If you run your application on Amazon EC2, attaching IAM role to your instance will make it easy to run the application because access key and secret key are retrieved from instance meta-data automatically. However, if you plan to migrate the existing application and SMTP Server to SES, it might be best solution to keep using SMTP library and change only SMTP settings.

References