Looking to hire Laravel developers? Try LaraJobs

lettr-laravel maintained by lettr

Description
Lettr for Laravel - Official Laravel integration for Lettr email API
Last update
2026/08/14 13:07 (dev-main)
License
Downloads
12 794
Tags

Comments
comments powered by Disqus

Lettr for Laravel

CI Latest Version on Packagist Total Downloads PHP Version License

Official Laravel integration for the Lettr email API.

Requirements

  • PHP 8.4+
  • Laravel 10.x, 11.x, 12.x, or 13.x

Installation

composer require lettr/lettr-laravel

Publish the configuration file:

php artisan vendor:publish --tag=lettr-config

Getting Started

The easiest way to set up Lettr in your Laravel application is using the interactive init command:

php artisan lettr:init

This command will guide you through:

  • API Key Configuration - Automatically adds your Lettr API key to .env
  • Mailer Setup - Configures the Lettr mailer in config/mail.php
  • Template Download - Optionally pulls your email templates as Blade files
  • Code Generation - Generates type-safe DTOs, Mailables, and template enums
  • Domain Verification - Checks your sending domain is properly configured

Tip: If you already have a verified sending domain in your Lettr account, the init command will automatically configure your MAIL_FROM_ADDRESS to match it.

After running lettr:init, you're ready to send emails:

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

// Using a generated Mailable
Mail::to('user@example.com')->send(new WelcomeEmail($data));

// Or send templates inline
Mail::lettr()->to('user@example.com')->sendTemplate('welcome-email', substitutionData: $data);

Manual Setup

If you prefer to configure manually, add your Lettr API key to your .env file:

LETTR_API_KEY=your-api-key

Sending Domain

To send emails through Lettr, you must have a verified sending domain in your Lettr account. Your MAIL_FROM_ADDRESS (or any "from" address you use) must match a verified domain.

For example, if you've verified example.com in Lettr:

MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="My App"

Emails sent from addresses on unverified domains will be rejected.

Quick Start

Using Laravel Mail (Recommended)

Add the Lettr mailer to your config/mail.php:

'mailers' => [
    // ... other mailers

    'lettr' => [
        'transport' => 'lettr',
    ],
],

Set as default in .env:

MAIL_MAILER=lettr

Send emails using Laravel's Mail facade:

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

Mail::to('recipient@example.com')->send(new WelcomeEmail());

Using the Lettr Facade Directly

use Lettr\Laravel\Facades\Lettr;

$response = Lettr::emails()->send(
    Lettr::emails()->create()
        ->from('sender@example.com', 'Sender Name')
        ->to(['recipient@example.com'])
        ->subject('Hello from Lettr')
        ->html('<h1>Hello!</h1><p>This is a test email.</p>')
);

echo $response->requestId; // Request ID for tracking
echo $response->accepted;  // Number of accepted recipients

Laravel Mail Integration

With Mailable Classes

use Illuminate\Support\Facades\Mail;
use App\Mail\OrderConfirmation;

// Send using Mailable
Mail::to('customer@example.com')
    ->cc('sales@example.com')
    ->bcc('records@example.com')
    ->send(new OrderConfirmation($order));

With Raw Content

Mail::raw('Plain text email content', function ($message) {
    $message->to('recipient@example.com')
            ->subject('Quick Update');
});

With Views

Mail::send('emails.welcome', ['user' => $user], function ($message) {
    $message->to('recipient@example.com')
            ->subject('Welcome!');
});

Multiple Mail Drivers

Use Lettr for specific emails while keeping another default:

// Use Lettr for this specific email
Mail::mailer('lettr')
    ->to('recipient@example.com')
    ->send(new TransactionalEmail());

// Uses default mailer
Mail::to('other@example.com')
    ->send(new MarketingEmail());

Using Lettr Templates with Mailables

Instead of using Blade views, you can send emails using Lettr templates directly. Extend the LettrMailable class:

<?php

namespace App\Mail;

use Lettr\Laravel\Mail\LettrMailable;
use Illuminate\Mail\Mailables\Envelope;

class WelcomeEmail extends LettrMailable
{
    public function __construct(
        public string $userName,
        public string $activationUrl,
    ) {}

    public function build(): static
    {
        return $this
            ->template('welcome-email', version: 2)
            ->substitutionData([
                'user_name' => $this->userName,
                'activation_url' => $this->activationUrl,
            ]);
    }
}

Then send it like any other Mailable:

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

Mail::to('user@example.com')
    ->send(new WelcomeEmail(
        userName: 'John',
        activationUrl: 'https://example.com/activate/abc123'
    ));

LettrMailable Methods

Method Description
template($slug, $version) Set template slug with optional version
templateVersion($version) Set template version separately
substitutionData($data) Set substitution variables for the template
customHeaders($headers) Set custom email headers
scheduledAt($when) Schedule delivery for a future DateTimeInterface (or ISO-8601 string)

Example: Order Confirmation

class OrderConfirmation extends LettrMailable
{
    public function __construct(
        public Order $order,
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: "Order #{$this->order->id} Confirmed",
        );
    }

    public function build(): static
    {
        return $this
            ->template('order-confirmation')
            ->substitutionData([
                'order_id' => $this->order->id,
                'customer_name' => $this->order->customer->name,
                'items' => $this->order->items->map(fn ($item) => [
                    'name' => $item->name,
                    'quantity' => $item->quantity,
                    'price' => $item->formatted_price,
                ])->toArray(),
                'total' => $this->order->formatted_total,
                'shipping_address' => $this->order->shipping_address,
            ]);
    }
}

Inline Template Sending

For quick template sending without creating a Mailable class, use the Mail::lettr() method:

Note: When no subject is provided, the template's own subject is used. Pass a subject only if you want to override it.

use Illuminate\Support\Facades\Mail;

// Simple usage — subject comes from the template
Mail::lettr()
    ->to('user@example.com')
    ->sendTemplate('welcome-email', substitutionData: ['name' => 'John']);

// Override the template's subject
Mail::lettr()
    ->to('user@example.com')
    ->sendTemplate('welcome-email', subject: 'Hey John!', substitutionData: ['name' => 'John']);

// With specific template version
Mail::lettr()
    ->to('user@example.com')
    ->sendTemplate('order-confirmation', substitutionData: [
        'order_id' => 123,
        'items' => $items,
    ], version: 2);

// With a custom from address
Mail::lettr()
    ->from('hello@marketing.example.com', 'Marketing Team')
    ->to('user@example.com')
    ->sendTemplate('promo-campaign', substitutionData: $promoData);

// With CC and BCC
Mail::lettr()
    ->to('user@example.com')
    ->cc('manager@example.com')
    ->bcc('records@example.com')
    ->sendTemplate('invoice', substitutionData: $invoiceData);

// With a generated DTO (implements Arrayable)
Mail::lettr()
    ->to('user@example.com')
    ->sendTemplate('welcome-email', substitutionData: new WelcomeEmailData(
        userName: 'John',
        activationUrl: 'https://example.com/activate/abc123',
    ));

Custom From Address

By default, emails are sent from the address configured in MAIL_FROM_ADDRESS. To send from a different address (e.g. a marketing domain), use from():

// Inline template sending
Mail::lettr()
    ->from('hello@marketing.example.com', 'Marketing Team')
    ->to('user@example.com')
    ->sendTemplate('promo-campaign', substitutionData: $promoData);

// Regular Mailable sending
Mail::lettr()
    ->from('noreply@transactional.example.com')
    ->to('user@example.com')
    ->send(new OrderConfirmation($order));

For Mailable classes, you can also set the from address in the envelope() method:

class MarketingEmail extends LettrMailable
{
    public function envelope(): Envelope
    {
        return new Envelope(
            from: new Address('hello@marketing.example.com', 'Marketing Team'),
            subject: 'Special Offer',
        );
    }
}

Note: The from address must belong to a verified sending domain in your Lettr account.

Custom Headers

You can pass custom headers with your emails. These are forwarded directly to the Lettr API.

// Inline template sending
Mail::lettr()
    ->to('user@example.com')
    ->sendTemplate('welcome-email', substitutionData: ['name' => 'John'], customHeaders: [
        'X-Campaign-Id' => 'welcome-2024',
        'X-Entity-Ref' => 'order-123',
    ]);

For Mailable classes, use the customHeaders() method:

class WelcomeEmail extends LettrMailable
{
    public function build(): static
    {
        return $this
            ->template('welcome-email')
            ->customHeaders([
                'X-Campaign-Id' => 'welcome-2024',
                'X-Entity-Ref' => 'order-123',
            ]);
    }
}

Error Handling

use Lettr\Exceptions\ApiException;
use Lettr\Exceptions\TransporterException;
use Lettr\Exceptions\ValidationException;
use Lettr\Exceptions\NotFoundException;
use Lettr\Exceptions\UnauthorizedException;
use Lettr\Exceptions\RateLimitException;
use Lettr\Exceptions\QuotaExceededException;

try {
    $response = Lettr::emails()->send($email);
} catch (RateLimitException $e) {
    // Too many requests (429)
    Log::warning("Rate limited, retry after: " . $e->retryAfter . "s");
} catch (QuotaExceededException $e) {
    // Sending quota exceeded
    Log::error("Quota exceeded: " . $e->getMessage());
} catch (ValidationException $e) {
    // Invalid request data (422)
    Log::error("Validation failed: " . $e->getMessage());
} catch (UnauthorizedException $e) {
    // Invalid API key (401)
    Log::error("Authentication failed: " . $e->getMessage());
} catch (NotFoundException $e) {
    // Resource not found (404)
    Log::error("Not found: " . $e->getMessage());
} catch (ApiException $e) {
    // Other API errors
    Log::error("API error ({$e->getCode()}): " . $e->getMessage());
} catch (TransporterException $e) {
    // Network/transport errors
    Log::error("Network error: " . $e->getMessage());
}

Configuration

The published config/lettr.php file contains:

return [
    'api_key' => env('LETTR_API_KEY'),

    'templates' => [
        'html_path' => resource_path('templates/lettr'),
        'blade_path' => resource_path('views/emails/lettr'),
        'mailable_path' => app_path('Mail/Lettr'),
        'mailable_namespace' => 'App\\Mail\\Lettr',
        'dto_path' => app_path('Dto/Lettr'),
        'dto_namespace' => 'App\\Dto\\Lettr',
        'enum_path' => app_path('Enums'),
        'enum_namespace' => 'App\\Enums',
        'enum_class' => 'LettrTemplate',
    ],
];

The templates block configures where lettr:pull, lettr:generate-dtos, and lettr:generate-enum commands save generated files.

The package also supports config('services.lettr.key') as a fallback for the API key.

Documentation

Full guides, every facade method, and complete request/response details live in the docs:

📚 docs.lettr.com/quickstart/laravel

Topic Guide
Install, config, and verify Installation
Mail facade, Lettr facade, Mailables, scheduling, testing Sending Emails
Lettr templates, versioning, pull/push Templates
Generated enums, DTOs, and Mailables Type Safety
Add, verify, and manage sending domains Domains
Webhook endpoints for delivery & engagement events Webhooks
Lists, contacts, topics, properties, segments Audience
List, send, and schedule campaigns Campaigns
Endpoint reference (params & schemas) API Reference

CLI Commands

lettr:check

Verify that your Lettr integration is correctly configured:

php artisan lettr:check

Checks mailer registration, API key validity, and sending domain verification. Returns exit code 0 if all checks pass.

lettr:pull

Download email templates from your Lettr account as Blade files:

php artisan lettr:pull
php artisan lettr:pull --template=welcome-email
php artisan lettr:pull --as-html
php artisan lettr:pull --with-mailables
php artisan lettr:pull --dry-run
Option Description
--template= Pull only a specific template by slug
--as-html Save as raw HTML instead of Blade
--with-mailables Also generate Mailable and DTO classes
--skip-templates Skip downloading templates, only generate DTOs and Mailables
--dry-run Preview what would be downloaded

lettr:generate-enum

Generate a PHP enum from your Lettr template slugs for type-safe template references:

php artisan lettr:generate-enum
php artisan lettr:generate-enum --dry-run

Generates an enum like:

enum LettrTemplate: string
{
    case WelcomeEmail = 'welcome-email';
    case OrderConfirmation = 'order-confirmation';
}

lettr:generate-dtos

Generate type-safe DTO classes from template merge tags:

php artisan lettr:generate-dtos
php artisan lettr:generate-dtos --template=welcome-email
php artisan lettr:generate-dtos --dry-run

Generated DTOs implement Arrayable and can be passed directly to sendTemplate():

$data = new WelcomeEmailData(userName: 'John', activationUrl: '...');

Mail::lettr()->to('user@example.com')->sendTemplate('welcome-email', substitutionData: $data);

Development

Install Dependencies

composer install

Code Style

composer lint

Static Analysis

composer analyse

Testing

composer test

Contributing

Please see CONTRIBUTING for details.

License

MIT License. See LICENSE for details.