Skip to content
This repository has been archived by the owner on Jan 7, 2021. It is now read-only.

Commit

Permalink
Merge pull request #9 from drixs6o9/feature/code-improvements
Browse files Browse the repository at this point in the history
* feat(transport): added headers support
* feat(transport): added test for api transport
* feat(transport): added documentation
  • Loading branch information
drixs6o9 committed Jun 18, 2020
2 parents 18d6ae4 + 4f2f0f6 commit c3ed898
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 41 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ CHANGELOG
-----

* Added SendinblueApiTransport (Thanks to [@ptondereau](https://github.com/ptondereau))

1.2.0
-----

* Added advanced options support for SendinblueApiTransport :
* Headers
* Tags
* TemplateId
* Params
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Finally, add your Sendinblue credentials into your `.env.local` file of your pro
Your MAILER_DSN can be configured as SMTP with **sendinblue** or **sendinblue+smtp** key, or be configured as STMPS with **sendinblue+smtps** key.

Exemple:

```env
###> drixs6o9/sendinblue-mailer ###
SENDINBLUE_USERNAME=username
Expand All @@ -55,6 +56,36 @@ MAILER_DSN=sendinblue+api://$SENDINBLUE_API_KEY@default
###< drixs6o9/sendinblue-mailer ###
```

With HTTP API, you can use custom headers.

```php
$params = ['param1' => 'foo', 'param2' => 'bar'];
$json = json_encode(['"custom_header_1' => 'custom_value_1']);

$email = new Email();
$email
->getHeaders()
->add(new MetadataHeader('custom', $json))
->add(new TagHeader('TagInHeaders1'))
->add(new TagHeader('TagInHeaders2'))
->addTextHeader('sender.ip', '1.2.3.4')
->addTextHeader('templateId', 1)
->addParameterizedHeader('params', 'params', $params)
->addTextHeader('foo', 'bar')
;
```

This example allow you to set :

* templateId
* params
* tags
* headers
* sender.ip
* X-Mailin-Custom

For more informations, you can refer to [Sendinblue API documentation](https://developers.sendinblue.com/reference#sendtransacemail).

Resources
---------

Expand Down
142 changes: 142 additions & 0 deletions Tests/Transport/SendinblueApiTransportTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace Drixs6o9\SendinblueMailerBundle\Tests\Transport;

use Drixs6o9\SendinblueMailerBundle\Transport\SendinblueApiTransport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\Header\TagHeader;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Contracts\HttpClient\ResponseInterface;

class SendinblueApiTransportTest extends TestCase
{
/**
* @dataProvider getTransportData
*/
public function testToString(SendinblueApiTransport $transport, string $expected)
{
$this->assertSame($expected, (string) $transport);
}

public function getTransportData()
{
yield [
new SendinblueApiTransport('ACCESS_KEY'),
'sendinblue+api://api.sendinblue.com',
];

yield [
(new SendinblueApiTransport('ACCESS_KEY'))->setHost('example.com'),
'sendinblue+api://example.com',
];

yield [
(new SendinblueApiTransport('ACCESS_KEY'))->setHost('example.com')->setPort(99),
'sendinblue+api://example.com:99',
];
}

public function testCustomHeader()
{
$params = ['param1' => 'foo', 'param2' => 'bar'];
$json = json_encode(['"custom_header_1' => 'custom_value_1']);

$email = new Email();
$email->getHeaders()
->add(new MetadataHeader('custom', $json))
->add(new TagHeader('TagInHeaders'))
->addTextHeader('templateId', 1)
->addParameterizedHeader('params', 'params', $params)
->addTextHeader('foo', 'bar')
;
$envelope = new Envelope(new Address('alice@system.com', 'Alice'), [new Address('bob@system.com', 'Bob')]);

$transport = new SendinblueApiTransport('ACCESS_KEY');
$method = new \ReflectionMethod(SendinblueApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);

$this->assertArrayHasKey('X-Mailin-Custom', $payload['headers']);
$this->assertEquals($json, $payload['headers']['X-Mailin-Custom']);

$this->assertArrayHasKey('tags', $payload);
$this->assertEquals('TagInHeaders', current($payload['tags']));
$this->assertArrayHasKey('templateId', $payload);
$this->assertEquals(1, $payload['templateId']);
$this->assertArrayHasKey('params', $payload);
$this->assertEquals('foo', $payload['params']['param1']);
$this->assertEquals('bar', $payload['params']['param2']);
$this->assertArrayHasKey('foo', $payload['headers']);
$this->assertEquals('bar', $payload['headers']['foo']);
}

public function testSendThrowsForErrorResponse()
{
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
$this->assertSame('POST', $method);
$this->assertSame('https://api.sendinblue.com:8984/v3/smtp/email', $url);
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);

return new MockResponse(json_encode(['message' => 'i\'m a teapot']), [
'http_code' => 418,
'response_headers' => [
'content-type' => 'application/json',
],
]);
});

$transport = new SendinblueApiTransport('ACCESS_KEY', $client);
$transport->setPort(8984);

$mail = new Email();
$mail->subject('Hello!')
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
->from(new Address('fabpot@symfony.com', 'Fabien'))
->text('Hello There!')
;

$this->expectException(HttpTransportException::class);
$this->expectExceptionMessage('Unable to send an email: i\'m a teapot (code 418).');
$transport->send($mail);
}

public function testSend()
{
$client = new MockHttpClient(function (string $method, string $url, array $options): ResponseInterface {
$this->assertSame('POST', $method);
$this->assertSame('https://api.sendinblue.com:8984/v3/smtp/email', $url);
$this->assertStringContainsString('Accept: */*', $options['headers'][2] ?? $options['request_headers'][1]);

return new MockResponse(json_encode(['messageId' => 'foobar']), [
'http_code' => 201,
]);
});

$transport = new SendinblueApiTransport('ACCESS_KEY', $client);
$transport->setPort(8984);

$dataPart = new DataPart('body');
$mail = new Email();
$mail->subject('Hello!')
->to(new Address('saif.gmati@symfony.com', 'Saif Eddin'))
->from(new Address('fabpot@symfony.com', 'Fabien'))
->text('Hello here!')
->html('Hello there!')
->addCc('foo@bar.fr')
->addBcc('foo@bar.fr')
->addReplyTo('foo@bar.fr')
->attachPart($dataPart)
;

$message = $transport->send($mail);

$this->assertSame('foobar', $message->getMessageId());
}
}
110 changes: 74 additions & 36 deletions Transport/SendinblueApiTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\HttpTransportException;
use Symfony\Component\Mailer\Header\MetadataHeader;
use Symfony\Component\Mailer\Header\TagHeader;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
Expand Down Expand Up @@ -50,60 +53,50 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e
return $response;
}

private function getPayload(Email $email, Envelope $envelope): array
protected function stringifyAddresses(array $addresses): array
{
$addressStringifier = function (Address $address) {
$stringified = ['email' => $address->getAddress()];

if ($address->getName()) {
$stringified['name'] = $address->getName();
}
$stringifiedAddresses = [];
foreach ($addresses as $address) {
$stringifiedAddresses[] = $this->stringifyAddress($address);
}

return $stringified;
};
return $stringifiedAddresses;
}

private function getPayload(Email $email, Envelope $envelope): array
{
$payload = [
'sender' => $addressStringifier($envelope->getSender()),
'subject' => $email->getSubject()
'sender' => $this->stringifyAddress($envelope->getSender()),
'to' => $this->stringifyAddresses($this->getRecipients($email, $envelope)),
'subject' => $email->getSubject(),
];

// To
if ($emails = array_map($addressStringifier, $email->getTo())) {
$payload['to'] = $emails;
if ($attachements = $this->prepareAttachments($email)) {
$payload['attachment'] = $attachements;
}

// CC
if ($emails = array_map($addressStringifier, $email->getCc())) {
$payload['cc'] = $emails;
if ($emails = $email->getReplyTo()) {
$payload['replyTo'] = current($this->stringifyAddresses($emails));
}

// BCC
if ($emails = array_map($addressStringifier, $email->getBcc())) {
$payload['bcc'] = $emails;
if ($emails = $email->getCc()) {
$payload['cc'] = $this->stringifyAddresses($emails);
}

// ReplyTo
if ($emails = array_map($addressStringifier, $email->getReplyTo())) {
$payload['replyTo'] = $emails;
if ($emails = $email->getBcc()) {
$payload['bcc'] = $this->stringifyAddresses($emails);
}

// Body
if (null !== $email->getTextBody()) {
if ($email->getTextBody()) {
$payload['textContent'] = $email->getTextBody();
}
if (null !== $email->getHtmlBody()) {
if ($email->getHtmlBody()) {
$payload['htmlContent'] = $email->getHtmlBody();
}

// Attachments
if ($email->getAttachments()) {
$payload['attachment'] = $this->getAttachments($email);
if ($headersAndTags = $this->prepareHeadersAndTags($email->getHeaders()))
{
$payload = array_merge($payload, $headersAndTags);
}

return $payload;
}

private function getAttachments(Email $email): array
private function prepareAttachments(Email $email): array
{
$attachments = [];
foreach ($email->getAttachments() as $attachment) {
Expand All @@ -121,6 +114,51 @@ private function getAttachments(Email $email): array
return $attachments;
}

private function prepareHeadersAndTags(Headers $headers): array
{
$headersAndTags = [];
$headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'reply-to', 'content-type', 'accept', 'api-key'];
foreach ($headers->all() as $name => $header) {
if (\in_array($name, $headersToBypass, true)) {
continue;
}
if ($header instanceof TagHeader) {
$headersAndTags['tags'][] = $header->getValue();

continue;
}
if ($header instanceof MetadataHeader) {
$headersAndTags['headers']['X-Mailin-'.ucfirst(strtolower($header->getKey()))] = $header->getValue();

continue;
}
if ('templateid' === $name) {
$headersAndTags[$header->getName()] = (int) $header->getValue();

continue;
}
if ('params' === $name) {
$headersAndTags[$header->getName()] = $header->getParameters();

continue;
}
$headersAndTags['headers'][$name] = $header->getBodyAsString();
}

return $headersAndTags;
}

private function stringifyAddress(Address $address): array
{
$stringifiedAddress = ['email' => $address->getAddress()];

if ($address->getName()) {
$stringifiedAddress['name'] = $address->getName();
}

return $stringifiedAddress;
}

private function getEndpoint(): ?string
{
return ($this->host ?: self::SENDINBLUE_API_HOST).($this->port ? ':'.$this->port : '');
Expand Down
9 changes: 4 additions & 5 deletions Transport/SendinblueTransportFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ public function create(Dsn $dsn): TransportInterface
$transport = sprintf(self::NAMESPACE, 'SendinblueSmtpsTransport');
break;
case 'sendinblue+api':
$key = $this->getUser($dsn);
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
$port = $dsn->getPort();
return (new SendinblueApiTransport($key, $this->client, $this->dispatcher, $this->logger))
->setHost($host)->setPort($port);
return (new SendinblueApiTransport($this->getUser($dsn), $this->client, $this->dispatcher, $this->logger))
->setHost('default' === $dsn->getHost() ? null : $dsn->getHost())
->setPort($dsn->getPort())
;
}

return new $transport(
Expand Down

0 comments on commit c3ed898

Please sign in to comment.