Laravel
A custom mail transport, so Mail::to() and every queued Mailable go through emails.sh.
You could call the API from a service class, but Laravel already has a mail layer with queues, Mailables, Blade templates, and Mail::fake() in tests. The right integration is a transport, and then nothing else in the application changes.
The key
# .env, which Laravel gitignores. The key comes from
# https://emails.sh/dashboard/api-keys.
EMAILSSH_API_KEY=esh_your_key_here
MAIL_MAILER=emailssh
MAIL_FROM_ADDRESS=hello@acme.com
MAIL_FROM_NAME=Acme<?php
// config/services.php
return [
// Leave the services already in this file where they are, and add:
'emailssh' => [
'key' => env('EMAILSSH_API_KEY'),
],
];<?php
// config/mail.php, in the 'mailers' array
'emailssh' => [
'transport' => 'emailssh',
],The transport
<?php
// app/Mail/EmailsshTransport.php
namespace App\Mail;
use Illuminate\Support\Facades\Http;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\MessageConverter;
class EmailsshTransport extends AbstractTransport
{
public function __construct(private string $key)
{
parent::__construct();
}
protected function doSend(SentMessage $message): void
{
$email = MessageConverter::toEmail($message->getOriginalMessage());
$payload = [
'from' => $this->address($email->getFrom()[0]),
'to' => array_map(fn ($a) => $a->getAddress(), $email->getTo()),
'subject' => $email->getSubject(),
'html' => $email->getHtmlBody(),
'text' => $email->getTextBody(),
];
if ($cc = $email->getCc()) {
$payload['cc'] = array_map(fn ($a) => $a->getAddress(), $cc);
}
if ($replyTo = $email->getReplyTo()) {
$payload['reply_to'] = $replyTo[0]->getAddress();
}
foreach ($email->getAttachments() as $attachment) {
$payload['attachments'][] = [
'filename' => $attachment->getFilename(),
'content_type' => $attachment->getContentType(),
'content_base64' => base64_encode($attachment->getBody()),
];
}
$response = Http::withToken($this->key)
->timeout(10)
->post('https://emails.sh/v1/emails', $payload);
if ($response->failed()) {
// The message says what to do about it, so keep it in the exception.
throw new \RuntimeException(
'emails.sh refused the send: ' . $response->json('error') . ': ' . $response->json('message')
);
}
}
private function address(\Symfony\Component\Mime\Address $address): string
{
return $address->getName()
? sprintf('%s <%s>', $address->getName(), $address->getAddress())
: $address->getAddress();
}
public function __toString(): string
{
return 'emailssh';
}
}<?php
// app/Providers/AppServiceProvider.php, in boot()
use App\Mail\EmailsshTransport;
use Illuminate\Support\Facades\Mail;
public function boot(): void
{
Mail::extend('emailssh', function (array $config) {
return new EmailsshTransport(config('services.emailssh.key'));
});
}Use it
<?php
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
// Everything Laravel already knows how to do now goes through emails.sh.
Mail::to($order->customer_email)->send(new OrderShipped($order));
// Or queue it, which is what you want on a web request.
Mail::to($order->customer_email)->queue(new OrderShipped($order));