Skip to main content
Generate type-safe PHP code from your Lettr templates, so mistakes show up in your editor instead of in a sent email. 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. Your IDE can then autocomplete template slugs, enforce required merge tags, and flag typos before you deploy.
This page describes lettr/lettr-laravel 2.7.0 and later. On 2.6.x the generated code looks different: DTO properties lose camelCase, Blade loops use $item->key, Mailables for Lettr templates set a subject, and none of the files carry a generated-file docblock. Upgrade with composer update lettr/lettr-laravel, then re-run the commands.

Why Code Generation?

Using string literals for template slugs and merge tags is error-prone:
With generated code, your IDE catches mistakes immediately:
The first example sends with a slug that doesn’t exist and a merge tag the template never reads. In the second, your IDE and static analysis flag both mistakes: LettrTemplate::WelcomEmail doesn’t exist, and WelcomeEmailData requires firstName and activationUrl.

Generate Everything at Once

The lettr:init command can generate all code for you during setup:
Or generate individually using the commands below.

Template Enum

Generate an enum containing all your template slugs:
This creates app/Enums/LettrTemplate.php:
The command reads every template in your Lettr account (using the API key in your .env) and generates a backed enum case for each one. The case name is the PascalCase version of the template slug, adjusted when PHP wouldn’t accept it:

Usage

Benefits

  • Autocomplete - Your IDE suggests available templates
  • Refactoring - Rename templates safely across your codebase
  • Discovery - See all templates without leaving your editor

Template DTOs

Generate data transfer objects for each template’s merge tags:
This creates a DTO for each template in app/Dto/Lettr/:
The command reads each template’s merge tags from its active version and generates a readonly class with typed constructor parameters:
  • Required merge tags become required parameters and come first; optional ones default to null.
  • Property names are camelCase: first_name and FIRST_NAME become $firstName, and orderId stays $orderId. If two keys would end up with the same name (FIRST_NAME and first_name in one template), both keep their original key as the property name instead.
  • toArray() maps the properties back to the original merge tag keys. Optional values you leave out are sent as null.
  • Class names follow the same rules as enum cases, plus a Data suffix. PHP reserves more words for class names than for enum cases, so a template with the slug new gets NewTemplateData.
Templates without merge tags, or without an active version, are skipped and listed in the command output. --template with a slug that doesn’t exist exits with a failure code.

Loops

A loop block gets its own item DTO, and the parent takes an array of them:
If the loop merge tag is optional, you can leave it out and toArray() sends null for it.

Usage

sendTemplate() accepts the DTO directly, because it implements Arrayable:

Benefits

  • Required fields - Constructor enforces required merge tags
  • Type hints - Know exactly what data each template needs
  • Documentation - DTOs serve as living documentation
Combine DTOs with the template enum for maximum safety:

Mailable Classes

Generate a Mailable class, and a DTO for its merge tags, for each template:
Mailables are created in app/Mail/Lettr/. What they send depends on how you pull:

Lettr template Mailables

With --as-html or --skip-templates, the Mailable points at the template in Lettr. Editing the template in the dashboard changes the email without a deploy:

Blade Mailables

Without those flags, the template is saved as a Blade view and the Mailable renders it locally. LettrMailable passes whatever withMergeTags() returns — here the DTO’s toArray() — to the view. Loop items reach the view as arrays, so the pulled view reads them as $item['name'], and a loop you leave out renders nothing:
The view name comes from blade_path in config/lettr.php, so a custom path still resolves as long as it’s inside one of your view directories.

Usage

Either kind is sent like any other Mailable:

Re-running the Command

Every run rewrites all the files it generates: Blade views, HTML files, DTOs, Mailables and the enum. Files that already existed are marked with ↻ in the output, followed by a warning:
If you’ve edited a generated Mailable (a from address, attachments, a custom subject), commit it first so you can bring the changes back, or run with --dry-run to see which files would be overwritten.

Benefits

  • Standard Laravel - Works with queues, events, and testing
  • Encapsulation - Template logic stays in one place
  • Familiar API - Same patterns you already use

Keeping Code in Sync

When you add or modify templates in Lettr, regenerate your code:
Every generated class has a docblock naming the command that generated it and saying the next run overwrites it. A Mailable for a template without merge tags has no constructor. The files follow Laravel’s code style, so Pint leaves them unchanged. 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. Regenerating replaces the enum and DTOs. A DTO for a template that no longer has merge tags isn’t deleted, so remove it yourself.
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.
  1. Create or update a template in the Lettr dashboard
  2. Run php artisan lettr:generate-enum and php artisan lettr:generate-dtos
  3. Commit the generated files
  4. Use the enum and DTO in your application code

Configuration

Customize paths and names in config/lettr.php:
Paths can be absolute or relative to your project root. Any key you leave out of a published config/lettr.php falls back to its default. Each namespace has to match its path under the autoload.psr-4 section of your composer.json, or the generated classes won’t load. The commands warn when they don’t match:

Artisan Commands Reference

What’s Next

Sending Emails

Put your generated code to use

Templates

Template versioning, projects, and sync commands

API Reference

Complete REST API documentation

Best Practices

Sending best practices and deliverability tips