Generate type-safe enums, DTOs, and Mailables from your Lettr templates
Generate type-safe PHP code from your Lettr templates. Catch errors at compile time instead of runtime.The SDK provides three Artisan commands that connect to the Lettr API, read your templates and their merge tags, and generate PHP classes you can use in your application. This means your IDE can autocomplete template slugs, enforce required merge tags, and flag typos before you deploy.
Using string literals for template slugs and merge tags is error-prone:
// ❌ Typos cause runtime errorsMail::lettr()->sendTemplate('welcom-email', 'Welcome!', ['nme' => 'John']);
With generated code, your IDE catches mistakes immediately:
// ✅ Type-safe, autocomplete-friendlyMail::lettr()->sendTemplate( LettrTemplate::WelcomeEmail->value, 'Welcome!', new WelcomeEmailData(name: 'John'));
The first example silently sends an email with the wrong template slug and missing merge tags. The second example fails at compile time with clear error messages — LettrTemplate::WelcomEmail doesn’t exist, and WelcomeEmailData requires name, not nme.
Generate an enum containing all your template slugs:
php artisan lettr:generate-enum
This creates app/Enums/LettrTemplate.php:
namespace App\Enums;enum LettrTemplate: string{ case WelcomeEmail = 'welcome-email'; case OrderConfirmation = 'order-confirmation'; case PasswordReset = 'password-reset'; case InvoiceReady = 'invoice-ready'; // ... all your templates}
The command fetches all templates from your Lettr account (using the API key in your .env) and generates a backed enum case for each one. The case name is a PascalCase version of the template slug.
use App\Enums\LettrTemplate;// IDE autocomplete shows all available templatesMail::lettr() ->to($user->email) ->sendTemplate(LettrTemplate::WelcomeEmail->value, 'Welcome!', $data);
Generate data transfer objects for each template’s merge tags:
php artisan lettr:generate-dtos
This creates a DTO for each template in app/Dto/Lettr/:
namespace App\Dto\Lettr;class WelcomeEmailData{ public function __construct( public string $first_name, public string $activation_url, public ?string $company_name = null, ) {} public function toArray(): array { return array_filter([ 'first_name' => $this->first_name, 'activation_url' => $this->activation_url, 'company_name' => $this->company_name, ], fn($v) => $v !== null); }}
The command reads each template’s merge tags from the Lettr API and generates a DTO class with typed constructor parameters. Required merge tags become required parameters; optional ones get default null values. The toArray() method strips null values so only provided merge tags are sent.
namespace App\Mail\Lettr;use Lettr\Laravel\Mail\LettrMailable;use Illuminate\Mail\Mailables\Envelope;class WelcomeEmail extends LettrMailable{ public function __construct( private string $firstName, private string $activationUrl, ) {} public function envelope(): Envelope { return new Envelope( subject: 'Welcome to Our App', ); } public function build(): static { return $this ->template('welcome-email') ->substitutionData([ 'first_name' => $this->firstName, 'activation_url' => $this->activationUrl, ]); }}
Generated Mailables extend LettrMailable and follow standard Laravel conventions. The constructor takes typed parameters for each merge tag, and the build() method maps them to the template’s substitution data.
When you add or modify templates in Lettr, regenerate your code:
# Regenerate enum with new templatesphp artisan lettr:generate-enum# Regenerate DTOs with updated merge tagsphp artisan lettr:generate-dtos
Generated files are safe to commit to version control. They act as a snapshot of your templates at generation time. If a template is renamed or a merge tag is added in Lettr, the generated code won’t update automatically — run the commands again to pick up changes.
Add these commands to your CI/CD pipeline to ensure code stays in sync with your templates. If the generated output differs from what’s committed, you know someone updated a template without regenerating.