Skip to content

Instantly share code, notes, and snippets.

@ragusa87
Last active January 24, 2022 13:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ragusa87/0e3ac87950071279f2f223294f88ad0a to your computer and use it in GitHub Desktop.
Save ragusa87/0e3ac87950071279f2f223294f88ad0a to your computer and use it in GitHub Desktop.
Laravel Custom email transport for api.swisscom.com

In your config/mail.php add this:

return [ // ..
      'swisscom_mail_api' => [
            'transport' => 'swisscom_mail_api',
            'api_key_map' => [
                config("emails.from_one") => env('MAIL_MESSAGING_API_KEY_FROM_ONE'),
                config("emails.from_two") => env('MAIL_MESSAGING_API_KEY_FROM_TWO'),
                ],
            "http_client" => [],
        ],
];

In you config/app.php add the provider:

App\Providers\SwisscomApiEmailMessagingProvider::class

Setup the env values:

  • MAIL_MESSAGING_API_KEY_FROM_ONE
  • MAIL_MESSAGING_API_KEY_FROM_TWO

Set your mailer env as below:

MAIL_MAILER=swisscom_mail_api

Clear the config cache.

Documentation for the API is only internal https://digital.swisscom.com/products/email-messaging/documentation/free?v=2.

API Limitation allows only one sender (From) per API Key.

<?php
namespace App\Providers;
use App\Mail\Transport\SwisscomApiEmailMessagingTransport;
use GuzzleHttp\Client;
use Illuminate\Contracts\Container\Container as ContainerContract;
use Illuminate\Mail\MailManager;
use Illuminate\Support\ServiceProvider;
use Psr\Log\LoggerInterface;
class SwisscomApiEmailMessagingProvider extends ServiceProvider
{
/**
* Add the new "swisscom_mail_api" transport mailer.
* Config:
* - http_client: for Guzzle Options
* - api_key_map for: the Client_id used as API Key indexed by email
* - endpoint: to override the default endpoint.
*/
public function register()
{
$this->booting(function () {
$this->app->extend('mail.manager', function (MailManager $mailManager, ContainerContract $container) {
return $mailManager->extend('swisscom_mail_api', function ($config) use ($container) {
// Don't we have a better way to get the client from the container ?
$client = new Client($config["http_client"] ?? []);
$logger = $container->get(LoggerInterface::class);
return new SwisscomApiEmailMessagingTransport($client, $config["api_key_map"], $logger, $config["endpoint"] ?? null);
});
});
});
}
}
<?php
namespace App\Mail\Transport;
use GuzzleHttp\ClientInterface;
use Illuminate\Mail\Transport\Transport;
use Illuminate\Support\Str;
use JetBrains\PhpStorm\ArrayShape;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Log\LoggerInterface;
use Swift_Mime_SimpleMessage;
use Swift_Mime_SimpleMimeEntity;
use Swift_TransportException;
class SwisscomApiEmailMessagingTransport extends Transport
{
/**
* Guzzle client instance.
*/
protected ClientInterface $client;
/**
* @var string Client ID for API Key
*/
private string $clientId;
private string $endpoint;
private LoggerInterface $logger;
private ?array $forceFrom = null;
private array $clientIdMapByEmail;
/**
* Create a new SwisscomApiEmailMessagingTransport transport instance.
*/
public function __construct(ClientInterface $client, array $clientIdMapByEmail, LoggerInterface $logger, string $endpoint = null)
{
$this->client = $client;
$this->endpoint = $endpoint === null ? 'https://api.swisscom.com' : $endpoint;
$this->logger = $logger;
$this->clientIdMapByEmail = $clientIdMapByEmail;
}
/**
* Send the email
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$data = [
"headers" => $this->getHeaders($message),
"json" => $this->payload($message),
'http_errors' => false,
];
if ($this->forceFrom !== null) {
$message->setFrom($this->forceFrom);
}
$this->beforeSendPerformed($message);
try {
$this->logger->debug("Send email:" . json_encode($data));
// https://digital.swisscom.com/products/email-messaging/documentation/free?v=2
$response = $this->client->request("POST", $this->endpoint . "/messaging/email", $data);
} catch (ClientExceptionInterface $e) {
$this->logger->warning("Sending email failure", ["exception" => $e]);
throw new Swift_TransportException(sprintf('Sending mail via Swisscom-API failed. Code %s', $e->getCode()), 500, $e);
}
if ($response->getStatusCode() !== 200) {
$content = $response->getBody()->getContents();
$this->logger->debug(sprintf("Unable to send email. Code %s", $response->getStatusCode()),
[
"body" => $content,
]
);
throw new Swift_TransportException(sprintf('Sending mail via Swisscom-API failed. Code %s', $response->getStatusCode()));
}
return $this->getRecipientCount($message);
}
/**
* Get the number of recipients for a message
*/
protected function getRecipientCount(Swift_Mime_SimpleMessage $message): int
{
return count(array_merge(
(array)$message->getTo(),
(array)$message->getCc(),
(array)$message->getBcc()
));
}
/**
* Format email to "name <email>;name2 <email2>;email3"
*/
protected function getEmailsString(array $contacts = null): ?string
{
if ($contacts === null) {
return null;
}
return collect($contacts)->map(function ($display, $address) {
return $display && !preg_match("/^[0-9]+$/", $display) ? $display . " <$address>" : $address;
})->values()->implode(';');
}
/**
* Gets MIME parts that match the message type.
* Exclude parts of type \Swift_Mime_Attachment as those are handled later.
* Inspired by https://github.com/wildbit/swiftmailer-postmark/blob/master/src/Postmark/Transport.php
*/
protected function getMIMEPart(Swift_Mime_SimpleMessage $message, $mimeType): ?Swift_Mime_SimpleMimeEntity
{
foreach ($message->getChildren() as $part) {
if (str_starts_with($part->getContentType(), $mimeType) && !($part instanceof \Swift_Mime_Attachment)) {
return $part;
}
}
return null;
}
/**
* Applies the message parts and attachments
* into the API Payload.
* Inspired by https://github.com/wildbit/swiftmailer-postmark/blob/master/src/Postmark/Transport.php
*/
protected function processMessageParts(array &$payload, Swift_Mime_SimpleMessage $message): void
{
//Get the primary message.
switch ($message->getContentType()) {
case 'text/html':
case 'multipart/alternative':
case 'multipart/mixed':
$payload['html'] = $message->getBody();
break;
default:
$payload['text'] = $message->getBody();
break;
}
// Provide an alternate view from the secondary parts.
if ($plain = $this->getMIMEPart($message, 'text/plain')) {
$payload['text'] = $plain->getBody();
}
if (!array_key_exists('html', $payload) && $html = $this->getMIMEPart($message, 'text/html')) {
$payload['html'] = $html->getBody();
}
// Process attachments
if ($message->getChildren()) {
$payload['attachments'] = array();
foreach ($message->getChildren() as $attachment) {
if (is_object($attachment) and $attachment instanceof \Swift_Mime_Attachment) {
$a = array(
'fileName' => $attachment->getFilename(),
'base64FileContent' => base64_encode($attachment->getBody()),
// $attachment->getContentType() not supported by API.
);
$payload['attachments'][] = $a;
}
}
}
}
private function payload(Swift_Mime_SimpleMessage $message): array
{
$requiredPayload = [
"to" => $this->getEmailsString($message->getTo()),
"subject" => $message->getSubject(),
"text" => "", // Text is mandatory, but may be overridden by "processMessageParts"
];
$optionalPayload = [
"replyTo" => $message->getReplyTo(),
"cc" => $this->getEmailsString($message->getCc()),
"bcc" => $this->getEmailsString($message->getBcc()),
];
// Fill the keys: text, html, attachments
$this->processMessageParts($optionalPayload, $message);
$payload = array_filter($optionalPayload) + $requiredPayload;
if ($payload["html"] ?? null) {
$payload["html"] = base64_encode($payload["html"]);
}
return $payload;
}
#[ArrayShape(["client_id" => "string", "SCS-Request-ID" => "string", "SCS-Version" => "int", "Content-Type" => "string"])]
private function getHeaders(Swift_Mime_SimpleMessage $message): array
{
return [
"client_id" => $this->getClientId($message),
"SCS-Request-ID" => Str::random(),
"SCS-Version" => 2,
"Content-Type" => 'application/json; charset=utf-8',
];
}
/**
* @param array|null $forceFrom
*/
public function setForceFrom(?array $forceFrom): void
{
$this->forceFrom = $forceFrom;
}
private function getClientId(Swift_Mime_SimpleMessage $message): string
{
$from = (string)array_key_first((array)$message->getFrom());
if (!in_array($from, array_keys($this->clientIdMapByEmail))) {
throw new \RuntimeException("\"api_key_map\" is not configured for email " . $from);
}
return $this->clientIdMapByEmail[$from];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment