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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy