Skip to content

Commit e4f54e3

Browse files
committed
[Mailer] Add api transport for Mailjet
1 parent 51b3f67 commit e4f54e3

File tree

4 files changed

+167
-7
lines changed

4 files changed

+167
-7
lines changed

src/Symfony/Component/Mailer/Bridge/Mailjet/CHANGELOG.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,3 @@ CHANGELOG
55
-----
66

77
* Added the bridge
8-
9-
* Configuration example :
10-
11-
```dotenv
12-
MAILER_DSN=mailjet+api://$MJ_API_PUBLIC_KEY:$MJ_API_PRIVATE_KEY@api.mailjet.com
13-
```

src/Symfony/Component/Mailer/Bridge/Mailjet/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ Mailjet Bridge
33

44
Provides Mailjet integration for Symfony Mailer.
55

6+
7+
8+
Configuration examples :
9+
---------
10+
11+
```dotenv
12+
# Api usage
13+
MAILER_DSN=mailjet+api://$PUBLIC_KEY:$PRIVATE_KEY@default
14+
# Smtp usage
15+
MAILER_DSN=mailjet+smtp://$PUBLIC_KEY:$PRIVATE_KEY@default
16+
```
17+
18+
619
Resources
720
---------
821

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
4+
namespace Symfony\Component\Mailer\Bridge\Mailjet\Transport;
5+
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\Mailer\Envelope;
9+
use Symfony\Component\Mailer\Exception\HttpTransportException;
10+
use Symfony\Component\Mailer\SentMessage;
11+
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
12+
use Symfony\Component\Mime\Address;
13+
use Symfony\Component\Mime\Email;
14+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
use function array_key_exists;
18+
use function array_map;
19+
use function count;
20+
use function implode;
21+
use function is_array;
22+
use function rewind;
23+
use function sprintf;
24+
use function stream_get_contents;
25+
use function stream_get_meta_data;
26+
27+
class MailjetApiTransport extends AbstractApiTransport
28+
{
29+
private const HOST = 'api.mailjet.com';
30+
private const API_VERSION = '3.1';
31+
private $privateKey;
32+
private $publicKey;
33+
34+
public function __construct(string $publicKey, string $privateKey, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
35+
{
36+
$this->publicKey = $publicKey;
37+
$this->privateKey = $privateKey;
38+
39+
parent::__construct($client, $dispatcher, $logger);
40+
}
41+
42+
protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
43+
{
44+
$response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/v'.self::API_VERSION.'/send', [
45+
'headers' => [
46+
'Accept' => 'application/json',
47+
],
48+
'auth_basic' => $this->publicKey.':'.$this->privateKey,
49+
'json' => $this->getPayload($email, $envelope),
50+
]);
51+
52+
$result = $response->toArray(false);
53+
54+
if (200 !== $response->getStatusCode()) {
55+
if ('application/json' === $response->getHeaders(false)['content-type'][0]) {
56+
throw new HttpTransportException('Unable to send an email: '.$result['message'].sprintf(' (code %d).', $response->getStatusCode()), $response);
57+
}
58+
59+
throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).sprintf(' (code %d).', $response->getStatusCode()), $response);
60+
}
61+
62+
// The response needs to contains a 'Messages' key that is an array
63+
if (!array_key_exists('Messages', $result) || !is_array($result['Messages']) || 0 === count($result['Messages'])) {
64+
throw new HttpTransportException('Unable to send an email: '.$response->getContent(false).' malformed api response', $response);
65+
}
66+
67+
$sentMessage->setMessageId($response->getHeaders(false)['x-mj-request-guid'][0]);
68+
69+
return $response;
70+
}
71+
72+
private function getPayload(Email $email, Envelope $envelope): array
73+
{
74+
$html = $email->getHtmlBody();
75+
if (null !== $html && \is_resource($html)) {
76+
if (stream_get_meta_data($html)['seekable'] ?? false) {
77+
rewind($html);
78+
}
79+
$html = stream_get_contents($html);
80+
}
81+
[$attachments, $inlines, $html] = $this->prepareAttachments($email, $html);
82+
83+
$message = [
84+
'From' => [
85+
'Email' => $envelope->getSender()->getAddress(),
86+
'Name' => $envelope->getSender()->getName(),
87+
],
88+
'To' => array_map(function (Address $recipient) {
89+
return [
90+
'Email' => $recipient->getAddress(),
91+
'Name' => $recipient->getName(),
92+
];
93+
}, $this->getRecipients($email, $envelope)),
94+
'Subject' => $email->getSubject(),
95+
'Attachments' => $attachments,
96+
'InlinedAttachments' => $inlines,
97+
];
98+
if ($emails = $email->getCc()) {
99+
$message['Cc'] = implode(',', $this->stringifyAddresses($emails));
100+
}
101+
if ($emails = $email->getBcc()) {
102+
$message['Bcc'] = implode(',', $this->stringifyAddresses($emails));
103+
}
104+
if ($email->getTextBody()) {
105+
$message['TextPart'] = $email->getTextBody();
106+
}
107+
if ($html) {
108+
$message['HTMLPart'] = $html;
109+
}
110+
111+
$payload = [
112+
'Messages' => [$message],
113+
];
114+
115+
return $payload;
116+
}
117+
118+
private function prepareAttachments(Email $email, ?string $html): array
119+
{
120+
$attachments = $inlines = [];
121+
foreach ($email->getAttachments() as $attachment) {
122+
$headers = $attachment->getPreparedHeaders();
123+
$filename = $headers->getHeaderParameter('Content-Disposition', 'filename');
124+
$formattedAttachment = [
125+
'ContentType' => $attachment->getMediaType().'/'.$attachment->getMediaSubtype(),
126+
'Filename' => $filename,
127+
'Base64Content' => $attachment->bodyToString(),
128+
];
129+
if ('inline' === $headers->getHeaderBody('Content-Disposition')) {
130+
$inlines[] = $formattedAttachment;
131+
} else {
132+
$attachments[] = $formattedAttachment;
133+
}
134+
}
135+
136+
return [$attachments, $inlines, $html];
137+
}
138+
139+
public function __toString(): string
140+
{
141+
return sprintf('mailjet+api://%s', $this->getEndpoint());
142+
}
143+
144+
private function getEndpoint(): ?string
145+
{
146+
return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : '');
147+
}
148+
}

src/Symfony/Component/Mailer/Bridge/Mailjet/Transport/MailjetTransportFactory.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public function create(Dsn $dsn): TransportInterface
2323
$scheme = $dsn->getScheme();
2424
$user = $this->getUser($dsn);
2525
$password = $this->getPassword($dsn);
26+
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
27+
28+
if ('maijlet+api' === $scheme) {
29+
return (new MailjetApiTransport($user, $password, $this->client, $this->dispatcher, $this->logger))->setHost($host);
30+
}
2631

2732
if ('mailjet+smtp' === $scheme || 'mailjet+smtps' === $scheme || 'mailjet' === $scheme) {
2833
return new MailjetSmtpTransport($user, $password, $this->dispatcher, $this->logger);
@@ -33,6 +38,6 @@ public function create(Dsn $dsn): TransportInterface
3338

3439
protected function getSupportedSchemes(): array
3540
{
36-
return ['mailjet', 'mailjet+smtp', 'mailjet+smtps'];
41+
return ['mailjet', 'mailjet+api', 'mailjet+smtp', 'mailjet+smtps'];
3742
}
3843
}

0 commit comments

Comments
 (0)