amazon_ses-2.0.x-dev/src/AmazonSesHandler.php
src/AmazonSesHandler.php
<?php
namespace Drupal\amazon_ses;
use Aws\Exception\CredentialsException;
use Aws\Result;
use Aws\SesV2\Exception\SesV2Exception;
use Drupal\amazon_ses\Event\MailSentEvent;
use Drupal\aws\AwsClientFactoryInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Mime\Email;
/**
* Amazon SES service.
*/
class AmazonSesHandler implements AmazonSesHandlerInterface {
use StringTranslationTrait;
/**
* The AWS SesClient.
*
* @var \Aws\SesV2\SesV2Client
*/
protected $client;
/**
* The account quota.
*
* @var array
*/
protected $quota = [];
/**
* The number of attempts to refresh expired credentials.
*
* @var int
*/
protected $retry = 2;
public function __construct(
protected AwsClientFactoryInterface $awsClientFactory,
protected LoggerChannelInterface $logger,
protected MessengerInterface $messenger,
protected EventDispatcherInterface $eventDispatcher,
protected ConfigFactoryInterface $configFactory,
) {
$this->client = $awsClientFactory->getClient('sesv2');
}
/**
* {@inheritdoc}
*/
public function getClient() {
return $this->client;
}
/**
* {@inheritdoc}
*/
public function verifyClient() {
return (bool) $this->client;
}
/**
* {@inheritdoc}
*/
public function send(Email $email) {
// Avoid an infinite loop on expired (temporary) credentials.
if ($this->retry <= 0) {
return FALSE;
}
try {
$result = $this->client->sendEmail([
'Content' => [
'Raw' => [
'Data' => $email->toString(),
],
],
]);
$event = new MailSentEvent($result['MessageId'], $email);
$this->eventDispatcher->dispatch($event, MailSentEvent::SENT);
$throttle = $this->configFactory
->get('amazon_ses.settings')
->get('throttle');
if ($throttle) {
$sleep_time = $this->getSleepTime();
usleep($sleep_time);
}
return $result['MessageId'];
}
catch (CredentialsException $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
catch (SesV2Exception $e) {
// If the credential has expired, request a new one and try again.
if ($e->getAwsErrorCode() == 'ExpiredTokenException') {
$this->retry--;
$this->refreshClient();
return $this->send($email);
}
$this->logger->error($e->getAwsErrorMessage());
return FALSE;
}
}
/**
* Get the number of microseconds to pause for throttling.
*
* @return int
* The time to sleep in microseconds.
*/
protected function getSleepTime() {
$results = $this->getSendQuota();
// Avoid a division by zero if the quota call failed.
if (empty($results)) {
return 0;
}
$multiplier = $this->configFactory
->get('amazon_ses.settings')
->get('multiplier');
$per_second = ceil((1000000 * $multiplier) / $results['MaxSendRate']);
return intval($per_second);
}
/**
* {@inheritdoc}
*/
public function getIdentities() {
$identities = [];
try {
$results = $this->client->listEmailIdentities();
foreach ($results['EmailIdentities'] as $emailIdentity) {
$identity = $emailIdentity['IdentityName'];
$result = $this->client->getEmailIdentity([
'EmailIdentity' => $identity,
]);
$domain = $result['IdentityType'] == 'DOMAIN';
$item = [
'identity' => $identity,
'status' => $result['VerifiedForSendingStatus'] ? 'Success' : 'Not verified',
'type' => $domain ? 'Domain' : 'Email Address',
];
if ($domain) {
$item['dkim'] = $result['DkimAttributes']['SigningEnabled'] ? 'Enabled' : 'Disabled';
}
else {
$item['dkim'] = '';
}
$identities[] = $item;
}
}
catch (SesV2Exception $e) {
$this->logger->error($e->getMessage());
$this->messenger->addError($this->t('Unable to list identities.'));
}
return $identities;
}
/**
* {@inheritdoc}
*/
public function verifyIdentity($identity) {
$this->client->createEmailIdentity([
'EmailIdentity' => $identity,
]);
}
/**
* {@inheritdoc}
*/
public function deleteIdentity($identity) {
$this->client->deleteEmailIdentity([
'EmailIdentity' => $identity,
]);
}
/**
* {@inheritdoc}
*/
public function getSendQuota() {
if (empty($this->quota)) {
try {
$result = $this->client->getAccount();
$this->quota = array_map('number_format', $result['SendQuota']);
}
catch (SesV2Exception $e) {
$this->logger->error($e->getMessage());
$this->messenger->addError($this->t('Unable to retrieve quota.'));
}
}
return $this->quota;
}
/**
* Return the result data as an array.
*
* @param \Aws\Result $result
* The result from the API call.
*
* @return array
* The result data.
*/
protected function resultToArray(Result $result) {
$array = $result->toArray();
unset($array['@metadata']);
return $array;
}
/**
* Get a new SesClient.
*
* This helps us if a mail run takes over an hour, in which case the
* authentication token expires. This should generate us a new one
* so we can continue.
*/
protected function refreshClient() {
$this->client = $this->awsClientFactory->getClient('sesv2');
$this->logger->info('Refreshing the SesV2 client after the session token expired');
}
}
