# 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:
```bash
# .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
```

config/services.php:
```php
<?php
// config/services.php
return [
    // Leave the services already in this file where they are, and add:
    'emailssh' => [
        'key' => env('EMAILSSH_API_KEY'),
    ],
];
```

config/mail.php:
```php
<?php
// config/mail.php, in the 'mailers' array
'emailssh' => [
    'transport' => 'emailssh',
],
```

### The transport

app/Mail/EmailsshTransport.php:
```php
<?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';
    }
}
```

app/Providers/AppServiceProvider.php:
```php
<?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

Anywhere in the application:
```php
<?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));
```

php artisan config:clear after editing config/mail.php, otherwise a cached config keeps the old mailer and the change looks like it did nothing.

---

Base URL: https://emails.sh/v1. Auth: `Authorization: Bearer esh_...`.
Whole API in one file: https://emails.sh/llms.txt. All documentation: https://emails.sh/docs.md.
