Skip to content
Docs

Tutorial

Send Laravel mail through Emailit with the emailit/emailit-laravel mail transport and facade, or plain SMTP, and verify Emailit webhooks.

Updated Oct 1, 2026

This guide shows two ways to send Laravel mail through Emailit: the emailit/emailit-laravel package, which adds an emailit mail transport and an Emailit facade, and plain SMTP with Laravel’s built-in mailer. It ends with a webhook route that verifies signatures.

Prerequisites

  • PHP 8.1 or later and Laravel 10, 11 or 12.
  • A verified sending domain, for example acme.com.
  • An API key. A Sending Only key restricted to your domain is enough.
  • Until your workspace has production access, you can only send to the account emails of workspace members.

Option 1: the Emailit Laravel package

The package sends your existing Mailables, Markdown mailables, notifications and queued mail through the Emailit API, with no changes to your mail code.

Install the package

Terminal
composer require emailit/emailit-laravel

The service provider is auto-discovered.

Configure the transport

Add your key and make Emailit the default mailer in .env:

.env
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
MAIL_MAILER=emailit
MAIL_FROM_ADDRESS=hello@acme.com
MAIL_FROM_NAME="Acme"

Register the mailer in config/mail.php:

config/mail.php
'mailers' => [
    // ...

    'emailit' => [
        'transport' => 'emailit',
    ],
],

MAIL_FROM_ADDRESS must be on a verified sending domain. To change the API base URL, publish the config file with php artisan vendor:publish --tag=emailit-config; you don’t need to for normal use.

Send a Mailable

Create a Mailable as usual:

Terminal
php artisan make:mail WelcomeEmail
app/Mail/WelcomeEmail.php
namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class WelcomeEmail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public User $user) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: 'Welcome to Acme');
    }

    public function content(): Content
    {
        return new Content(view: 'emails.welcome');
    }
}

Send it, or queue it so the request doesn’t wait for the API:

PHP
use App\Mail\WelcomeEmail;
use Illuminate\Support\Facades\Mail;

Mail::to($user)->send(new WelcomeEmail($user));

Mail::to($user)->queue(new WelcomeEmail($user));

Use the facade for API features

The Emailit facade exposes the full PHP SDK, for features Laravel’s mailer doesn’t model, such as stored templates and scheduled sends:

PHP
use Emailit\Laravel\Facades\Emailit;

$email = Emailit::emails()->send([
    'from'         => 'Acme <hello@acme.com>',
    'to'           => $user->email,
    'template'     => 'welcome',
    'variables'    => ['first_name' => $user->first_name],
    'scheduled_at' => 'tomorrow at 9am',
]);

$email->id; // em_…

The facade also covers domains, contacts, audiences, suppressions, webhooks and the other resources. If you prefer dependency injection, type-hint Emailit\EmailitClient in a controller or job and Laravel resolves it with your configured key.

Catch typed exceptions to handle failures:

PHP
use Emailit\Exceptions\ApiErrorException;
use Emailit\Exceptions\RateLimitException;

try {
    Emailit::emails()->send($payload);
} catch (RateLimitException $e) {
    // 429: release the job back to the queue and try again later
} catch (ApiErrorException $e) {
    report($e); // $e->getHttpStatus() has the status code
}

Option 2: plain SMTP

If you’d rather not add a package, point Laravel’s SMTP mailer at the Emailit relay:

.env
MAIL_MAILER=smtp
MAIL_HOST=smtp.emailit.com
MAIL_PORT=587
MAIL_USERNAME=emailit
MAIL_PASSWORD=secret_••••••••••••••••••••••••••••••••
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=hello@acme.com
MAIL_FROM_NAME="Acme"

On port 587 Laravel’s mailer upgrades the connection with STARTTLS. Older config/mail.php files read MAIL_ENCRYPTION and newer ones ignore it, so it’s safe to keep. For implicit TLS, use port 465; if your host blocks 587, use 2525 or 2587. Mailables, notifications and queues work exactly as with the package. See SMTP settings.

Over SMTP you can’t use stored templates or scheduled_at; use the facade or the API for those.

Receive webhooks

Create a webhook that points to https://your-app.com/webhooks/emailit, then store its signing secret:

.env
EMAILIT_WEBHOOK_SECRET=whsec_••••••••
config/services.php
'emailit' => [
    'webhook_secret' => env('EMAILIT_WEBHOOK_SECRET'),
],

Add a controller that checks the signature against the raw body:

app/Http/Controllers/EmailitWebhookController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class EmailitWebhookController extends Controller
{
    public function __invoke(Request $request)
    {
        $payload = $request->getContent();
        $signature = (string) $request->header('X-Emailit-Signature');
        $timestamp = (string) $request->header('X-Emailit-Timestamp');

        $expected = hash_hmac('sha256', $timestamp.'.'.$payload, config('services.emailit.webhook_secret'));

        if (abs(time() - (int) $timestamp) > 300 || ! hash_equals($expected, $signature)) {
            abort(401, 'Invalid signature');
        }

        // The body is a JSON array of up to 100 events.
        foreach (json_decode($payload, true) as $event) {
            match ($event['type']) {
                'email.bounced', 'email.complained' => $this->stopEmailing($event['data']['object']['to']),
                default => null,
            };
        }

        return response()->noContent();
    }

    private function stopEmailing(string $address): void
    {
        // Mark the address as undeliverable in your database.
    }
}

Register the route and exclude it from CSRF protection, because Emailit can’t send a CSRF token:

routes/web.php
use App\Http\Controllers\EmailitWebhookController;

Route::post('/webhooks/emailit', EmailitWebhookController::class);
bootstrap/app.php (Laravel 11 and 12)
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: ['webhooks/emailit']);
})

On Laravel 10, add 'webhooks/emailit' to the $except array in app/Http/Middleware/VerifyCsrfToken.php instead. Return a 2xx within 30 seconds, and push slow work onto a queue. See Request signature.

Production tips

  • Queue your mail. Use queue() or ShouldQueue so web requests don’t wait on email, and throttle bulk jobs (for example with Redis::throttle) to stay under your sending limits. New workspaces can send 2 emails per second by default.
  • Cache config safely. After php artisan config:cache, env() only works inside config files. Read the key through config, as the package does.
  • Use a dedicated key. Give each app and environment its own Sending Only key so you can rotate one without touching the others. See API keys.
  • Deduplicate webhooks. Store each event_id you process and skip repeats, because failed deliveries are retried.

Next steps

The client behind the facade.
Design emails in Emailit and send them by alias.
Every event and its payload.
Fix authentication and connection errors.

Was this page helpful?

Thanks for the feedback.

Thanks, we read every message.