Skip to main content
When you need to send the same email to many recipients — newsletters, announcements, reports, or notifications — batch sending lets you do it efficiently. Each API call accepts up to 50 recipients, and you can personalize content per recipient using merge tag variables. For lists larger than 50, you split recipients into batches and send them sequentially or in parallel.

Multiple Recipients Per Request

Pass an array of up to 50 email addresses in the to field. Lettr delivers a separate copy of the email to each recipient:
await lettr.emails.send({
  from: 'you@example.com',
  to: [
    'alice@example.com',
    'bob@example.com',
    'carol@example.com'
    // ... up to 50 recipients
  ],
  subject: 'Team Announcement',
  html: '<p>Important update for the team!</p>'
});

Personalization with Substitution Data

Use substitution_data to provide merge tag values that apply to all recipients in a batch. This is useful for shared variables like campaign names or dates:
await lettr.emails.send({
  from: 'you@example.com',
  to: ['john@example.com', 'jane@example.com', 'bob@example.com'],
  subject: 'Your monthly report',
  html: '<p>Here is the report for {{month}}.</p>',
  substitution_data: {
    month: 'January'
  }
});

Batch Limits

LimitValue
Max recipients per request50 (combined to, cc, bcc)
Max requests per minuteSee Rate Limits

Sending to Large Lists

For recipient lists larger than 50, chunk the list into batches and send each batch with a separate API call. Adding a short delay between batches helps you stay within rate limits:
const recipients = [...]; // Array of 500 recipients
const batchSize = 50;

for (let i = 0; i < recipients.length; i += batchSize) {
  const batch = recipients.slice(i, i + batchSize);

  await lettr.emails.send({
    from: 'you@example.com',
    to: batch,
    subject: 'Newsletter',
    html: '<p>Newsletter content...</p>'
  });

  // Optional: Add delay between batches to respect rate limits
  await sleep(100);
}

Parallel Batch Sending

For faster throughput, send multiple batches concurrently using Promise.all. This reduces total send time proportionally to the number of parallel batches, but be mindful of your rate limits:
const recipients = [...]; // Large recipient list
const batchSize = 50;
const parallelBatches = 5;

const batches = chunkArray(recipients, batchSize);

// Process batches in parallel groups
for (let i = 0; i < batches.length; i += parallelBatches) {
  const parallelGroup = batches.slice(i, i + parallelBatches);

  await Promise.all(
    parallelGroup.map(batch =>
      lettr.emails.send({
        from: 'you@example.com',
        to: batch,
        subject: 'Newsletter',
        html: '<p>Content...</p>'
      })
    )
  );
}

Response for Batch Sends

The response indicates how many recipients were accepted or rejected:
{
  "message": "Email queued for delivery.",
  "data": {
    "request_id": "7582751837467401763",
    "accepted": 48,
    "rejected": 2
  }
}
Rejected recipients may be on your suppression list or have invalid email addresses. Check your suppression list and validate addresses before sending.

Best Practices

Run email addresses through basic format validation and check them against your local suppression list before including them in a batch. Each rejected address in a batch send still counts against your rate limit, and high rejection rates can signal list quality issues to Lettr.
Personalized emails (using the recipient’s name, relevant data, or tailored content) achieve significantly higher open and click rates than generic messages. Use substitution_data to customize merge tags without making separate API calls.
Add a delay between batches proportional to your API rate limit. If your limit is 300 requests per 5 minutes, spacing batches 1 second apart lets you send to 15,000 recipients per 5-minute window (300 batches × 50 recipients) without hitting limits.
A batch send can partially succeed — some recipients are accepted while others are rejected (due to suppression or validation). Always check the accepted and rejected counts in the response and log rejected addresses for investigation rather than silently dropping them.
With batch sends, polling the API for each email’s delivery status is impractical. Set up webhooks to receive message.delivery, message.bounce, and engagament.open events, which let you track outcomes for every recipient asynchronously.

Tracking Batch Emails

Attach metadata to batch sends so you can query all emails from a specific batch later. Include a unique batch_id along with any campaign or timing information relevant to your use case:
const batchId = `batch_${Date.now()}`;

await lettr.emails.send({
  from: 'you@example.com',
  to: batchRecipients,
  subject: 'Newsletter',
  html: '<p>Content...</p>',
  metadata: {
    batch_id: batchId,
    campaign: 'january-newsletter',
    send_date: new Date().toISOString()
  }
});
You can then use the request_id returned from each batch send to look up emails via the Email History API. Metadata is also included in webhook payloads, allowing you to correlate delivery events with your batch data.