Skip to content

Commit 87cd915

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

File tree

5 files changed

+168
-11
lines changed

5 files changed

+168
-11
lines changed
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
CHANGELOG
22
=========
33

4-
5.2
4+
5.2.0
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
Mailjet Bridge
2-
===============
2+
==============
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+
618
Resources
719
---------
820

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

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
1616
use Symfony\Component\Mailer\Transport\Dsn;
1717
use Symfony\Component\Mailer\Transport\TransportInterface;
18+
use function in_array;
1819

1920
class MailjetTransportFactory extends AbstractTransportFactory
2021
{
@@ -23,8 +24,13 @@ public function create(Dsn $dsn): TransportInterface
2324
$scheme = $dsn->getScheme();
2425
$user = $this->getUser($dsn);
2526
$password = $this->getPassword($dsn);
27+
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
2628

27-
if ('mailjet+smtp' === $scheme || 'mailjet+smtps' === $scheme || 'mailjet' === $scheme) {
29+
if ('maijlet+api' === $scheme) {
30+
return (new MailjetApiTransport($user, $password, $this->client, $this->dispatcher, $this->logger))->setHost($host);
31+
}
32+
33+
if (in_array($scheme, ['mailjet+smtp', 'mailjet+smtps', 'mailjet'])) {
2834
return new MailjetSmtpTransport($user, $password, $this->dispatcher, $this->logger);
2935
}
3036

@@ -33,6 +39,6 @@ public function create(Dsn $dsn): TransportInterface
3339

3440
protected function getSupportedSchemes(): array
3541
{
36-
return ['mailjet', 'mailjet+smtp', 'mailjet+smtps'];
42+
return ['mailjet', 'mailjet+api', 'mailjet+smtp', 'mailjet+smtps'];
3743
}
3844
}

src/Symfony/Component/Mailer/Bridge/Mailjet/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"minimum-stability": "dev",
3434
"extra": {
3535
"branch-alias": {
36-
"dev-master": "5.1-dev"
36+
"dev-master": "5.2-dev"
3737
}
3838
}
3939
}

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