lettr / lettr-laravel
Lettr for Laravel - Official Laravel integration for Lettr email API
Requires
- php: ^8.4
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- lettr/lettr-php: ^2.4.0
- symfony/mailer: ^6.2|^7.0|^8.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.18
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.17|^9.0|^10.8|^11.0
- pestphp/pest: ^2.0|^3.7
This package is auto-updated.
Last update: 2026-06-02 13:55:35 UTC
README
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_ADDRESSto 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
subjectonly 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.