Skip to content

Instantly share code, notes, and snippets.

@wittwerch
Created September 30, 2016 09:10
Show Gist options
  • Select an option

  • Save wittwerch/8d8c1da7ad78ed4710bc9d56f40b9b72 to your computer and use it in GitHub Desktop.

Select an option

Save wittwerch/8d8c1da7ad78ed4710bc9d56f40b9b72 to your computer and use it in GitHub Desktop.
Example transport class to send through Amazon API
<?php
/**
* @package Mautic
* @copyright 2014 Mautic Contributors. All rights reserved.
* @author Mautic
* @link http://mautic.org
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
*/
namespace Mautic\EmailBundle\Swiftmailer\Transport;
use Aws\Ses\Exception\SesException;
use Aws\Ses\SesClient;
use Mautic\EmailBundle\Helper\MailHelper;
use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\BlockingConsumer;
use bandwidthThrottle\tokenBucket\storage\FileStorage;
use Symfony\Component\Security\Acl\Exception\Exception;
/**
* Class AmazonTransport
*/
class AmazonTransport extends AbstractTokenHttpTransport
{
/* @var $_client SesClient */
private $_client;
private $_region;
private $_access_key_id;
private $_secret_access_key;
/* Email quota of remaining messages that can be sent */
private $_emailQuoteRemaining = 0;
/* Max emails per second according to your account quota */
private $_maxSendRate = 0;
/* @var $_consumer BlockingConsumer */
private $_consumer;
/**
* @return mixed
*/
public function getRegion()
{
return $this->_region;
}
/**
* @param mixed $region
*/
public function setRegion($region)
{
$this->_region = $region;
}
/**
* @return mixed
*/
public function getAccessKeyId()
{
return $this->_access_key_id;
}
/**
* @param mixed $access_key_id
*/
public function setAccessKeyId($access_key_id)
{
$this->_access_key_id = $access_key_id;
}
/**
* @return mixed
*/
public function getSecretAccessKey()
{
return $this->_secret_access_key;
}
/**
* @param mixed $secret_access_key
*/
public function setSecretAccessKey($secret_access_key)
{
$this->_secret_access_key = $secret_access_key;
}
/**
* Start this Transport mechanism.
*/
public function start()
{
$logger = $this->factory->getLogger();
$this->_client = SesClient::factory([
'version' => 'latest',
'region' => $this->getRegion(),
'credentials' => [
'key' => $this->getAccessKeyId(),
'secret' => $this->getSecretAccessKey(),
]
]);
/**
* AWS SES has a limit of how many messages can be sent in a 24h time slot. The remaining messages are calculated
* from the api. The transport will fail when the quota is exceeded
*/
$quota = $this->_client->getSendQuota();
$this->_emailQuoteRemaining = $quota->get('Max24HourSend') - $quota->get('SentLast24Hours');
if ($this->_emailQuoteRemaining > 0) {
$this->started = true;
$logger->debug("AWS SES Quota remaining: ".$this->_emailQuoteRemaining." messages");
}
else {
throw new \Exception("Your AWS SES quota is exceeded");
}
/**
* AWS SES limits the amount of messages that can be sent in parallel. This limit is bound to an account and can vary.
* The AmazonTransport reads that value from the API and uses a TokenBucket to limit the requests to that rate
*/
$this->_maxSendRate = floor($quota->get('MaxSendRate'));
// Initialize a token bucket to track the sending limit
$storage = new FileStorage(tempnam(sys_get_temp_dir(), 'MauticBucket'));
$rate = new Rate($this->_maxSendRate, Rate::SECOND);
$bucket = new TokenBucket($this->_maxSendRate, $rate, $storage);
$this->_consumer = new BlockingConsumer($bucket);
$bucket->bootstrap($this->_maxSendRate);
$logger->debug("AWS SES throughput limited to ".$this->_maxSendRate."messages/s based on your account limitation");
}
/**
* Return an array of headers for the POST
*
* @return array
*/
protected function getHeaders()
{
// TODO: Implement getHeaders() method.
}
/**
* Return the payload for the POST
*
* @return mixed
*/
protected function getPayload()
{
$from = $this->message->getFrom();
$fromEmail = current(array_keys($from));
$charset = 'utf-8';
// @TODO
//'ReplyToAddresses' => array('string', ... ),
// 'ReturnPath' => 'string',
$payload = array(
'Source' => $fromEmail,
'Destination' => array(),
'Message' => array(
'Subject' => array(
'Data' => 'string',
'Charset' => 'string',
),
'Body' => array(
'Text' => array(
'Data' => 'string',
'Charset' => 'string',
),
'Html' => array(
'Data' => 'string',
'Charset' => 'string',
),
),
),
);
$to = $this->message->getTo();
foreach ($to as $email => $name) {
$payload['Destination']['ToAddresses'][] = $email;
}
$cc = $this->message->getCc();
if (!empty($cc)) {
foreach ($cc as $email => $name) {
$payload['Destination']['CcAddresses'][] = $email;
}
}
$bcc = $this->message->getBcc();
if (!empty($bcc)) {
foreach ($bcc as $email => $name) {
$payload['Destination']['BccAddresses'][] = $email;
}
}
$payload['Message']['Subject']['Data'] = $this->message->getSubject();
$payload['Message']['Subject']['Charset'] = $charset;
$payload['Message']['Body']['Text']['Data'] = MailHelper::getPlainTextFromMessage($this->message);
$payload['Message']['Body']['Text']['Charset'] = $charset;
$payload['Message']['Body']['Html']['Data'] = $this->message->getBody();
$payload['Message']['Body']['Html']['Charset'] = $charset;
return $payload;
}
/**
* Return the URL for the API endpoint
*
* @return string
*/
protected function getApiEndpoint()
{
// not used, see start()
}
/**
* POST payload to API endpoint
*
* @param array $settings
*
* @return array of failed addresses
* @throws \Swift_TransportException
*/
protected function post($settings = array())
{
$logger = $this->factory->getLogger();
$payload = empty($settings['payload']) ? $this->getPayload() : $settings['payload'];
//$headers = empty($settings['headers']) ? $this->getHeaders() : $settings['headers'];
//$endpoint = empty($settings['url']) ? $this->getApiEndpoint() : $settings['url'];
try {
/**
* Blocking consumer on the bucket. The process only continues if there is a token available
* This allows us to respect the sending limit from Amazon SES
*/
$this->_consumer->consume(1);
$start = microtime(true);
$result = $this->_client->sendEmail($payload);
$messageId = $result->get('MessageId');
$this->_emailQuoteRemaining--;
$time_elapsed_secs = microtime(true) - $start;
$logger->debug("Email sent! Message ID: $messageId"." Duration: $time_elapsed_secs\n");
return array();
} catch (SesException $e) {
$logger->error("The email was not sent. Error message: ".$e->getMessage());
// AbstractTokenHttpTransport:send expects an array with all failed addresses as the return value
return array_merge(
$payload['Destination']['ToAddresses'],
$payload['Destination']['CcAddresses'],
$payload['Destination']['BccAddresses']
);
}
}
/**
* Analyze the output of the API response and return any addresses that FAILED to send
*
* @param $response
* @param $curlInfo
*
* @throws \Swift_TransportException
* @return array
*/
protected function handlePostResponse($response, $curlInfo)
{
// not used and handled in post()
}
/**
* Return the max number of to addresses allowed per batch. If there is no limit, return 0
*
* @return int
*/
function getMaxBatchLimit()
{
return 50;
}
/**
* Get the count for the max number of recipients per batch
*
* @param \Swift_Message $message
* @param int $toBeAdded Number of emails about to be added
* @param string $type Type of emails being added (to, cc, bcc)
*
* @return mixed
*/
function getBatchRecipientCount(\Swift_Message $message, $toBeAdded = 1, $type = 'to')
{
// TODO: Implement getBatchRecipientCount() method.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment