We decided to use the AWS SES platform to send emails to our users in bulk because it provides statistics on bounce rates, complaints, abuse reports, and a trusted stable bulk mail platform. Some of our members have been gone for 3 or 4 years and we've found some of them simply file a complaint about the email even though they signed up for us and we include the unsubscribe link in the message. As we've received each complaint we simply go through and unsubscribe them from future communication from us, no harm no foul.
I downloaded the AWS SDK for PHP and looked at the implementation. It includes classes for many of the AWS services but I'm only interested in the Simple Email Service, so I went and found the ses.php class and chose to use just that.
The ses class provides many functions and digging through the class helped unravel what all could be done with it, this is a simplified version of the function I wrote to deliver email.
I made one modification to the ses class, if there were an error I wanted it to be returned to me so I could handle it. On line 335 of ses.php I changed the line from "return false" to "return $rest->error"
Our true code takes a CSV of emailaddress,fullname and will send the email to every user in the CSV. This is a stripped down example of that so you can use it however you please, but that's why we do the %%USERNAME%% replace and handle errors the way we do.
CODE:
require_once("ses.php");
$ses = new SimpleEmailService("ACCESSKEY","SECRETKEY");
$m = new SimpleEmailServiceMessage();
$m->addTo($to);
$m->setFrom($fromaddress);
$m->setSubject($subject);
$m->setReturnPath('bounces@domain.com'); //the return path defines where any complaints or abuse messages will go -- this is important to have configured so you can clean out your mailing list of members that do not want to receive messages
$usermsg = str_replace("%%USERNAME%%",$username,$msg); //we use a token to replace with the users name to make the message more personalized
/*********************/
//setMessageFromString takes the first parameter for a TEXT message, and an
//optional second for an HTML message. If you would like to provide both
// formats at the same time that is possible, however it isn't something we have
// been doing so I wrote this little code to handle our use case.
/*********************/
if($_REQUEST['content'] == "HTML")
$m->setMessageFromString(null,$usermsg);
else
$m->setMessageFromString($usermsg);
$result = $ses->sendEmail($m);
/*********************/
//check and see if there is an error, print it to the console. In most cases
// you probably want to reattempt delivery to any address that errors out,
//but the throttling error is the only one i've encountered so far.
/*********************/
if(isset($result['Error']['Code']))
{
print("Message not sent to {$name[0]}, {$result['Error']['Code']} - {$result['Error']['Message']}<br />\r\n");
if($result['Error']['Code'] == "Throttling")
$failedemails .= "$to,$username<br>\r\n";
}
else print("Email sent to {$name[0]}<br>\r\n");
print $failedemails; //i do all of this in a for loop, and spit out a list of emails,names that need to be re-sent due to quota limitations
What I've Learned:
After five days of sending 1,000 emails a day our Max24HourSend quota is now 1360, and our MaxSendRate is no 5. The documentation suggests that it should take 3 days to get to 10,000 but our rate may be increasing more slowly due to higher bounce rates (10 to 17%). Last night I sent out a large batch of emails to members that haven't been around from 2006-2008 so our bounce rates are pretty high, as are the complaint rates. I expect that to tail off as we start emailing members that are more current.
Here's the example output of the ses-get-stats.pl command:
Timestamp DeliveryAttempts Rejects Bounces Complaints
2011-09-30T05:33:00Z 414 0 97 2
2011-09-30T06:33:00Z 67 0 8 0
2011-09-30T06:48:00Z 631 0 110 3
2011-09-30T07:18:00Z 93 0 14 0
Going Forward
I would like to build a daemon to monitor the Amazon SES send quota and then as quota becomes available automatically deliver the appropriate number of emails. This could help efficiently distribute the email load and make sure that we keep running up the Amazon SES quota. In order to effectively implement this I want to create an email table that stores all of the outbound emails that are queued up and then let the daemon work through the list automatically, simplifying delivery of email newsletters.
The documentation I've found has said that it takes several days of maxing out your 1,000 message per day quota before your quota will be raised. We've been hammering ours for 2 days and haven't seen an increase, even though we've been lightly using our account for the last several months (under 50 per day).
Complaints and Abuse Messages
We have received a few complaints and a few abuse messages for which we have removed those users from our email campaigns. Even though there is an unsubscribe link in the email people don't use it, probably because they've been trained to never use an unsubscribe link in an email you think is spam. My guess is they forgot they registered for us and are now treating email from us as unsolicited spam. It is important to set the returnpath in the email to an account that can receive the complaints and abuse messages, although the email address used must by verified by Amazon SES before it can be used. Using a tool like saasbouncer to filter your bounces and spam complaints can definitely simplify your life if you are sending out thousands of emails or more.