Skip to main content
Lettr provides a powerful and flexible API for sending transactional and marketing emails. Whether you’re sending password resets, order confirmations, or newsletters, the sending API handles delivery, tracking, and analytics so you can focus on building your application.

Prerequisites

Before sending your first email, make sure you have:
  1. A verified sending domain with DKIM and SPF records configured
  2. An API key with sending permissions
  3. At least one of html or text content prepared for your email
New to Lettr? Start with a quickstart guide to get set up in minutes.

Basic Email

Send a simple email with minimal configuration:
curl -X POST https://app.lettr.com/api/emails \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": ["recipient@example.com"],
    "subject": "Hello World",
    "html": "<p>This is my first email!</p>"
  }'

Email Options

ParameterTypeRequiredDescription
fromstringYesSender email address (must be from verified domain)
from_namestringNoFriendly sender name
tostring[]YesRecipient address(es), max 50
subjectstringConditionalEmail subject line (max 998 chars). Required unless template_slug is provided — when omitted with a template, the template’s stored subject is used (or the template name if no subject is set).
htmlstringNo*HTML content
textstringNo*Plain text content
amp_htmlstringNoAMP HTML content
ccstring[]NoCC recipients
bccstring[]NoBCC recipients
reply_tostringNoReply-to address
reply_to_namestringNoReply-to display name
attachmentsarrayNoFile attachments
optionsobjectNoTracking and sending options
tagstringNoTag for analytics grouping (max 64 chars)
metadataobjectNoCustom metadata (available in webhooks)
headersobjectNoCustom email headers (max 10, max 998 chars per value). See Custom Headers.
substitution_dataobjectNoVariables for template substitution
template_slugstringNoTemplate slug to use for email content
template_versionintegerNoSpecific template version to use
project_idintegerNoProject to associate with this email
*At least one of html, text, or template_slug is required.

Response

{
  "message": "Email queued for delivery.",
  "data": {
    "request_id": "7582751837467401763",
    "accepted": 1,
    "rejected": 0
  }
}
The request_id uniquely identifies this send request. Store it to track delivery status, correlate with webhook events, and debug issues. See Idempotency for patterns to prevent duplicate sends.

Transactional vs. Marketing Emails

Lettr handles both transactional and marketing emails through the same API, but the two have different characteristics and best practices. Transactional emails are triggered by a user action — password resets, order confirmations, shipping notifications, account alerts. They are expected by the recipient and typically have high open rates. Marketing emails are sent proactively — newsletters, promotions, product announcements. They require clear unsubscribe mechanisms and careful list management. Use the transactional option to signal the email type:
// Transactional: password reset (transactional is true by default)
await lettr.emails.send({
  from: 'security@example.com',
  to: ['user@example.com'],
  subject: 'Reset your password',
  html: '<p>Click the link below to reset your password.</p>',
  options: {
    transactional: true
  }
});

// Marketing: newsletter
await lettr.emails.send({
  from: 'updates@example.com',
  from_name: 'Acme Newsletter',
  to: ['subscriber@example.com'],
  subject: 'This month at Acme',
  html: newsletterHtml,
  options: {
    transactional: false,
    open_tracking: true,
    click_tracking: true
  }
});
Transactional emails typically have higher deliverability. Separating transactional and marketing email on different sending domains or subdomains is a common practice to protect your transactional sender reputation. See Best Practices for domain warm-up and reputation guidance.

Unsubscribe Handling

When you send a marketing email (transactional: false), Lettr automatically adds the List-Unsubscribe and List-Unsubscribe-Post headers. This enables one-click unsubscribe in email clients that support it (Gmail, Apple Mail, Outlook, etc.), which is required by major mailbox providers. For an HTML unsubscribe link in the email body, add the data-msys-unsubscribe="1" attribute to your link. The URL should point to a simple “Sorry to see you go” page — Lettr handles the unsubscribe automatically:
<a href="https://example.com/unsubscribe" data-msys-unsubscribe="1">
  Unsubscribe from these emails
</a>
For plain text emails, use the following format:
http://www.yourdomain.com[[data-msys-unsubscribe="1"]]
The unsubscribe URL should point to a simple “Sorry to see you go” confirmation page — it does not need to handle the actual unsubscription logic. Lettr processes the unsubscribe automatically and fires a list_unsubscribe or link_unsubscribe webhook event, which you should use to update subscription state in your application.
You do not need to handle unsubscribe for transactional emails (transactional: true, the default). Unsubscribe headers and tracking only apply to marketing emails.

Common Sending Patterns

Personalized Emails with Merge Tags

Use substitution_data to personalize content for each recipient. Lettr’s template language supports variables, conditionals, and loops:
await lettr.emails.send({
  from: 'orders@example.com',
  from_name: 'Acme Store',
  to: ['customer@example.com'],
  subject: 'Your order #{{order_id}} has shipped',
  html: `
    <p>Hi {{name}},</p>
    <p>Your order <strong>#{{order_id}}</strong> has shipped via {{carrier}}.</p>
    <p>Track your package: <a href="{{tracking_url}}">{{tracking_number}}</a></p>
  `,
  substitution_data: {
    name: 'Alex',
    order_id: '12345',
    carrier: 'FedEx',
    tracking_number: '9400111899223456789012',
    tracking_url: 'https://example.com/track/9400111899223456789012'
  }
});

Sending with Templates

Instead of inline HTML, reference a template created in the Lettr dashboard or via the API:
await lettr.emails.send({
  from: 'welcome@example.com',
  from_name: 'Acme',
  to: ['newuser@example.com'],
  subject: 'Welcome to Acme, {{first_name}}!',
  template_slug: 'welcome-email',
  substitution_data: {
    first_name: 'Jordan',
    login_url: 'https://app.example.com/login'
  }
});
When using template_slug, the subject field is optional. If you omit it, Lettr uses the subject stored on the template (or the template name if no subject has been set). If you provide a subject, it overrides the template’s subject — useful for A/B testing or dynamic subject lines.
See Templates for creating and managing email templates with the visual editor.

Tracking and Metadata

Attach metadata for analytics and debugging, and control tracking per email:
await lettr.emails.send({
  from: 'notifications@example.com',
  to: ['user@example.com'],
  subject: 'Your invoice is ready',
  html: '<p>Your invoice for January is ready. <a href="https://example.com/invoices/123">View invoice</a></p>',
  metadata: {
    invoice_id: 'inv_123',
    customer_id: 'cust_456',
    source: 'billing-service'
  },
  options: {
    open_tracking: true,
    click_tracking: true
  }
});
Metadata is included in webhook payloads, allowing you to correlate delivery events with your application data.

Sending to Multiple Recipients

Send to up to 50 recipients per API call. Use substitution_data to personalize content with merge tags that apply to all recipients:
await lettr.emails.send({
  from: 'team@example.com',
  to: ['alice@example.com', 'bob@example.com', 'carol@example.com'],
  subject: 'Team Update',
  html: '<p>Here is the latest update for the team.</p>'
});
For larger lists, see Batch Sending for patterns to send to hundreds or thousands of recipients efficiently.

Error Handling

The API returns structured errors to help you diagnose issues:
{
  "error_code": "invalid_domain",
  "message": "The sending domain is not verified."
}
Common errors include unverified domains (invalid_domain), unconfigured domains (unconfigured_domain), validation failures (validation_error), and template errors (template_not_found). Server errors (5xx) are safe to retry with exponential backoff, while client errors (4xx) should not be retried. See Errors & Retries for a complete error code reference and retry patterns.

Next Steps

Once you’re sending emails, there are three systems you should configure to complete your integration:
  • Webhooks — Push real-time delivery, bounce, open, and click notifications to your server so you can react to events as they happen
  • Analytics — Aggregate dashboards for monitoring delivery rates and engagement trends over time
  • Suppressions — Automatically manage bounced and unsubscribed addresses to protect your sender reputation

Learn More

Content Types

HTML, plain text, and AMP email content

Template Language

Personalize your emails with merge tags

Attachments

Add files to your emails

Integrations

Send from Stripe, Supabase, or WordPress