-
-
Save bitbay/0e713dc9225283fb9807290c8bddb83e to your computer and use it in GitHub Desktop.
Laravel - Postal mail transport
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
MAIL_MAILER=postal | |
... | |
# URL of postal service, without routes ('/api/...') | |
POSTAL_MAIL_API_URL= | |
# CLIENT API KEY configured in postal | |
POSTAL_MAIL_API_KEY= | |
... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* app/Providers/AppServiceProvider.php | |
*/ | |
namespace App\Providers; | |
use Illuminate\Support\ServiceProvider; | |
use Illuminate\Support\Facades\Mail; | |
use App\Mail\Transport\PostalTransport; | |
class AppServiceProvider extends ServiceProvider | |
{ | |
/** | |
* Register any application services. | |
*/ | |
public function register(): void | |
{ | |
// | |
} | |
/** | |
* Bootstrap any application services. | |
*/ | |
public function boot(): void | |
{ | |
Mail::extend('postal', function (array $config = []) { | |
return new PostalTransport($config); | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use App\Mail\WelcomeEmail; | |
use Illuminate\Support\Facades\Mail; | |
$user = ['name' => 'John Doe', 'email' => '[email protected]']; | |
// WelcomeEmail is created with `php artisan make:mail WelcomeEmail` | |
// and a corresponding blade template in resources/views/emails/welcome.blade.php | |
Mail::to($user['email'])->send(new WelcomeEmail()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* config/mail.php | |
*/ | |
return [ | |
/* | |
|-------------------------------------------------------------------------- | |
| Default Mailer | |
|-------------------------------------------------------------------------- | |
| | |
| This option controls the default mailer that is used to send all email | |
| messages unless another mailer is explicitly specified when sending | |
| the message. All additional mailers can be configured within the | |
| "mailers" array. Examples of each type of mailer are provided. | |
| | |
*/ | |
'default' => env('MAIL_MAILER', 'postal'), | |
/* | |
|-------------------------------------------------------------------------- | |
| Mailer Configurations | |
|-------------------------------------------------------------------------- | |
| | |
| Here you may configure all of the mailers used by your application plus | |
| their respective settings. Several examples have been configured for | |
| you and you are free to add your own as your application requires. | |
| | |
| Laravel supports a variety of mail "transport" drivers that can be used | |
| when delivering an email. You may specify which one you're using for | |
| your mailers below. You may also add additional mailers if needed. | |
| | |
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", | |
| "postmark", "resend", "log", "array", | |
| "failover", "roundrobin" | |
| | |
*/ | |
'mailers' => [ | |
'postal' => [ | |
'transport' => 'postal', | |
'api_url' => env('POSTAL_MAIL_API_URL'), | |
'api_key' => env('POSTAL_MAIL_API_KEY') | |
], | |
// ... | |
] | |
// ... | |
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* app/Mail/Transport/PostalTransport.php | |
*/ | |
namespace App\Mail\Transport; | |
use Illuminate\Support\Facades\Http; | |
use Illuminate\Http\Client\Response; | |
use Illuminate\Support\Facades\Log; | |
use Exception; | |
use Symfony\Component\Mailer\Envelope; | |
use Symfony\Component\Mailer\Exception\TransportException; | |
use Symfony\Component\Mailer\SentMessage; | |
use Symfony\Component\Mailer\Transport\AbstractTransport; | |
use Symfony\Component\Mime\Address; | |
use Symfony\Component\Mime\Email; | |
use Symfony\Component\Mime\MessageConverter; | |
class PostalTransport extends AbstractTransport | |
{ | |
/** | |
* Create a new Postal transport instance. | |
*/ | |
public function __construct(protected $config) | |
{ | |
parent::__construct(); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
protected function doSend(SentMessage $message): void | |
{ | |
$email = MessageConverter::toEmail($message->getOriginalMessage()); | |
$envelope = $message->getEnvelope(); | |
try { | |
$response = Http::withHeaders([ | |
'Accept' => 'application/json', | |
'Content-Type' => 'application/json', | |
'X-Server-API-Key' => $this->config['api_key'] | |
])->post($this->config['api_url'] . '/api/v1/send/message', $this->getPayload($email, $envelope)); | |
$error = $this->getError($response); | |
if (!is_null($error)) { | |
Log::error('throwing error: ' . $error); | |
throw new Exception($error); | |
} | |
// Log::debug($response->json('status')); | |
// Log::debug($response->json('data')['message_id']); | |
} catch (Exception $exception) { | |
throw new TransportException( | |
sprintf('Request to the API failed. Reason: %s', $exception->getMessage()), | |
is_int($exception->getCode()) ? $exception->getCode() : 0, | |
$exception | |
); | |
} | |
$messageId = $response->json('data')['message_id']; | |
$email->getHeaders()->addHeader('X-Message-ID', $messageId); | |
} | |
private function getPayload(Email $email, Envelope $envelope): array | |
{ | |
$payload = [ | |
'from' => $envelope->getSender()->getAddress(), | |
'to' => array_map(fn(Address $address) => $address->getAddress(), $this->getRecipients($email, $envelope)), | |
'subject' => $email->getSubject(), | |
]; | |
if ($emails = $email->getCc()) { | |
$payload['cc'] = array_map(fn(Address $address) => $address->getAddress(), $emails); | |
} | |
if ($emails = $email->getBcc()) { | |
$payload['bcc'] = array_map(fn(Address $address) => $address->getAddress(), $emails); | |
} | |
if ($email->getTextBody()) { | |
$payload['plain_body'] = $email->getTextBody(); | |
} | |
if ($email->getHtmlBody()) { | |
$payload['html_body'] = $email->getHtmlBody(); | |
} | |
if ($attachments = $this->prepareAttachments($email)) { | |
$payload['attachments'] = $attachments; | |
} | |
if ($headers = $this->getCustomHeaders($email)) { | |
$payload['headers'] = $headers; | |
} | |
if ($emails = $email->getReplyTo()) { | |
$payload['reply_to'] = $emails[0]->getAddress(); | |
} | |
return $payload; | |
} | |
private function prepareAttachments(Email $email): array | |
{ | |
$attachments = []; | |
foreach ($email->getAttachments() as $attachment) { | |
$attachments[] = [ | |
'name' => $attachment->getFilename(), | |
'content_type' => $attachment->getContentType(), | |
'data' => base64_encode($attachment->getBody()), | |
]; | |
} | |
return $attachments; | |
} | |
private function getCustomHeaders(Email $email): array | |
{ | |
$headers = []; | |
$headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type', 'sender', 'reply-to']; | |
foreach ($email->getHeaders()->all() as $name => $header) { | |
if (in_array($name, $headersToBypass, true)) { | |
continue; | |
} | |
$headers[] = [ | |
'key' => $header->getName(), | |
'value' => $header->getBodyAsString(), | |
]; | |
} | |
return $headers; | |
} | |
/** | |
* @return Address[] | |
*/ | |
private function getRecipients(Email $email, Envelope $envelope): array | |
{ | |
return array_filter($envelope->getRecipients(), fn(Address $address) => false === in_array($address, array_merge($email->getCc(), $email->getBcc()), true)); | |
} | |
private function getError(Response $response): ?string | |
{ | |
if (!$response->successful()) { | |
return 'HTTP Request not successful'; | |
} | |
if (200 !== $response->status()) { | |
return sprintf('HTTP STATUS: %d', $response->status()); | |
} | |
if ('success' !== $response->json('status')) { | |
if (is_null($response->json('status'))) { | |
return 'Postal empty response'; | |
} else { | |
return sprintf('Postal status: %s', $response->json('status')); | |
} | |
} else if (is_null($response->json('data'))) { | |
if (is_null($response->json('message'))) { | |
return 'Postal empty data'; | |
} else { | |
return sprintf('Postal message: %s', $response->json('message')); | |
} | |
} | |
return null; | |
} | |
/** | |
* Get the string representation of the transport. | |
*/ | |
public function __toString(): string | |
{ | |
return 'postal'; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment