Skip to main content
The Lettr PHP SDK offers multiple ways to send emails, from quick one-liners to the full-featured email builder. This page covers every approach so you can choose the one that fits your use case.
ApproachBest For
Quick Send MethodsSimple emails with minimal configuration
Email BuilderComplex emails with tracking, metadata, and multiple recipients
TemplatesLettr-managed templates with dynamic data
AttachmentsSending files alongside email content

Quick Send Methods

For simple emails, use the shorthand methods. These are convenience wrappers around the email builder that handle the most common use cases with minimal code.

HTML Email

$response = $lettr->emails()->sendHtml(
    from: 'sender@yourdomain.com',
    to: 'recipient@example.com',
    subject: 'Hello',
    html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>',
);
The sendHtml() method is perfect for simple HTML emails where you don’t need advanced features like tracking configuration or metadata.
When providing HTML content, the SDK automatically enables CSS inlining and template substitutions by default. You can disable these behaviors using the email builder for more control.

Plain Text Email

use Lettr\ValueObjects\EmailAddress;

$response = $lettr->emails()->sendText(
    from: new EmailAddress('sender@yourdomain.com', 'Your App'),
    to: ['user1@example.com', 'user2@example.com'],
    subject: 'Plain text update',
    text: 'This is a plain text email.',
);
The from parameter accepts either a string email address or an EmailAddress value object to set a display name. The to parameter accepts a single email string or an array of recipients (maximum 50).
You can send both HTML and plain text in the same email by using the email builder with both ->html() and ->text() methods. Mail clients that don’t support HTML will display the plain text version.

Template Email

$response = $lettr->emails()->sendTemplate(
    from: 'sender@yourdomain.com',
    to: 'customer@example.com',
    subject: 'Welcome!',
    templateSlug: 'welcome-email',
    substitutionData: ['name' => 'John'],
);
The substitutionData array keys correspond to merge tags in your Lettr template (e.g., {{name}}). See Merge Tags & Template Language for the full syntax including conditionals and loops. For more advanced template usage including versioning, see the Templates page.

Email Builder

For complex emails that require multiple features, use the fluent email builder. This gives you access to every option the Lettr API supports in a chainable interface:
$response = $lettr->emails()->send(
    $lettr->emails()->create()
        ->from('sender@yourdomain.com', 'Your Company')
        ->to(['recipient@example.com'])
        ->cc(['cc@example.com'])
        ->bcc(['bcc@example.com'])
        ->replyTo('reply@yourdomain.com')
        ->subject('Monthly Newsletter')
        ->html('<h1>Newsletter</h1><p>{{content}}</p>')
        ->text('Newsletter: {{content}}')
        ->substitutionData(['content' => $newsletterContent])
        ->withOpenTracking(true)
        ->withClickTracking(true)
        ->tag('welcome')
        ->metadata(['source' => 'website'])
);

// Access the response
echo $response->requestId;
echo $response->accepted;
The builder is useful when you need to combine multiple features in a single email — for example, HTML content with a plain text fallback, tracking configuration, metadata for analytics, and attachments.
All builder methods return the builder instance, so you can chain as many method calls as needed. The order of method calls doesn’t matter — the builder collects all the data and sends it to the API when you call send().

Templates with Substitution Data

Send using a Lettr template with dynamic data:
$response = $lettr->emails()->send(
    $lettr->emails()->create()
        ->from('sender@yourdomain.com')
        ->to(['recipient@example.com'])
        ->subject('Your Order #{{order_id}}')
        ->useTemplate('order-confirmation', version: 1, projectId: 123)
        ->substitutionData([
            'order_id' => '12345',
            'customer_name' => 'John Doe',
            'items' => [
                ['name' => 'Product A', 'price' => 29.99],
                ['name' => 'Product B', 'price' => 49.99],
            ],
            'total' => 79.98,
        ])
);
The useTemplate() method accepts an optional version parameter to pin a specific template version. See Templates for details. When to use versioning:
  • Production safety — Pin to a known-good version so publishing a new draft doesn’t affect live emails
  • A/B testing — Send different versions to different cohorts and compare metrics
  • Gradual rollout — Test a new version with a subset of users before publishing

Attachments

Add attachments from file paths or binary data:
$response = $lettr->emails()->send(
    $lettr->emails()->create()
        ->from('billing@yourdomain.com')
        ->to(['customer@example.com'])
        ->subject('Your Invoice')
        ->html('<p>Please find your invoice attached.</p>')
        // From file path
        ->attachFile('/path/to/invoice.pdf')
        // With custom name and MIME type
        ->attachFile('/path/to/file', 'custom-name.pdf', 'application/pdf')
        // From binary data
        ->attachData($binaryContent, 'report.csv', 'text/csv')
);
When attaching files, the SDK automatically:
  • Base64-encodes the file content
  • Detects the MIME type if not provided
  • Validates file size and format
Large attachments increase email size and can affect deliverability. Keep attachments under 10 MB total per email. For larger files, consider uploading to cloud storage and including a download link in the email instead.

Email Options

Fine-tune email behavior with tracking and processing options:
$email = $lettr->emails()->create()
    ->from('sender@yourdomain.com')
    ->to(['recipient@example.com'])
    ->subject('Newsletter')
    ->html($htmlContent)
    // Tracking
    ->withClickTracking(true)
    ->withOpenTracking(true)
    // Mark as transactional (bypasses unsubscribe lists)
    ->transactional()
    // CSS inlining
    ->withInlineCss(true)
    // Template variable substitution in HTML
    ->withSubstitutions(true);

$response = $lettr->emails()->send($email);
OptionDefaultDescription
withClickTracking()trueTrack link clicks in the email
withOpenTracking()trueTrack when recipients open the email
transactional()trueMark as transactional (bypasses unsubscribe lists)
withInlineCss()falseAutomatically inline CSS styles for better email client compatibility
withSubstitutions()trueEnable merge tag substitution in HTML/text content
Click and open tracking are enabled by default. For transactional emails like password resets or order confirmations, leave tracking enabled — the data helps you diagnose delivery issues. For privacy-sensitive communications, disable tracking explicitly.

Handling Responses

Every send method returns a SendEmailResponse with details about the request:
$response = $lettr->emails()->send($email);

$response->requestId;      // Request ID for tracking
$response->accepted;       // Number of accepted recipients
$response->rejected;       // Number of rejected recipients
$response->allAccepted();  // true if all recipients accepted
$response->hasRejections(); // true if any were rejected
$response->total();        // Total recipients (accepted + rejected)
The requestId is a unique identifier for this email transmission. Store it in your database to correlate emails with delivery events later. You can use it to query the Lettr API for detailed delivery information including opens, clicks, bounces, and complaints.
Save the requestId in your application’s database alongside the user record or transaction. This lets you look up delivery status, debug issues, and provide customer support without searching through logs.

Error Handling

The SDK throws typed exceptions for different API errors, making it easy to handle specific failure cases:
use Lettr\Exceptions\ValidationException;
use Lettr\Exceptions\UnauthorizedException;
use Lettr\Exceptions\NotFoundException;
use Lettr\Exceptions\ConflictException;
use Lettr\Exceptions\QuotaExceededException;
use Lettr\Exceptions\RateLimitException;
use Lettr\Exceptions\ApiException;
use Lettr\Exceptions\TransporterException;

try {
    $response = $lettr->emails()->send($email);
} catch (ValidationException $e) {
    // Invalid request data (422)
    // e.g., unverified sender domain, invalid email format
} catch (UnauthorizedException $e) {
    // Invalid API key (401)
} catch (NotFoundException $e) {
    // Resource not found (404)
    // e.g., template slug doesn't exist
} catch (ConflictException $e) {
    // Resource conflict (409)
} catch (QuotaExceededException $e) {
    // Sending quota exceeded (429) — monthly or daily limit reached
    // $e->quota contains quota details (free tier only)
} catch (RateLimitException $e) {
    // API rate limit exceeded (429) — too many requests per second
    // $e->rateLimit contains limit/remaining/reset info
    // $e->retryAfter contains seconds to wait
} catch (ApiException $e) {
    // Other API errors (server errors, etc.)
} catch (TransporterException $e) {
    // Network/transport errors (connection failures, timeouts)
}
ExceptionHTTP StatusCommon Causes
ValidationException422Invalid email format, unverified sending domain, missing required fields
UnauthorizedException401Invalid or expired API key
NotFoundException404Template slug doesn’t exist, invalid project ID
ConflictException409Resource already exists or conflicts with current state
QuotaExceededException429Monthly or daily sending limit reached (free tier)
RateLimitException429Too many requests per second (3 req/s per team)
ApiException4xx/5xxServer errors, other API errors
TransporterExceptionN/ANetwork failures, timeouts, DNS errors
Always wrap email sending in a try-catch block in production. Network failures and API errors can happen at any time, and unhandled exceptions will crash your application.

What’s Next