Created
September 30, 2016 09:10
-
-
Save wittwerch/8d8c1da7ad78ed4710bc9d56f40b9b72 to your computer and use it in GitHub Desktop.
Example transport class to send through Amazon API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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