---
title: "Email Campaigns"
description: "Send bulk campaign emails with mail merge, scheduling, follow-ups, and analytics."
source: "https://www.mailmark.dev/docs/email-campaigns"
site: "Mailmark"
---

# Email Campaigns

Campaigns send individual, personalised emails to a list of recipients, all tracked under a shared batch ID.

## Creating a campaign

A campaign send (`type: "campaign"`) sends one individual email per recipient rather than a single email to all recipients together. Each send is tracked with a shared `batchId`.

```javascript
import { Mailmark } from 'mailmark-sdk';

const client = new Mailmark('dm_live_your_api_key');

const result = await client.send({
  from: 'newsletter@acme.com',
  to: [
    'alice@example.com',
    'bob@example.com',
    'carol@example.com',
  ],
  subject: 'Our monthly update',
  html: '<h1>Hello!</h1><p>Here is what happened this month...</p>',
  type: 'campaign',   // key difference from transactional
});

console.log(result.batchId);    // shared batch ID
console.log(result.messageIds); // one messageId per recipient
```

### Transactional vs. campaign

| | Transactional | Campaign |
| - | ------------- | -------- |
| Emails sent | 1 (all recipients in To) | 1 per recipient |
| Recipients see each other | Yes (in To header) | No |
| batchId returned | No | Yes |
| messageIds returned | No (single messageId) | Yes (one per recipient) |
| Best for | Notifications, receipts | Newsletters, outreach |

## Mail merge & personalization

Because each campaign email is rendered and sent individually, you can personalise the HTML body per recipient before calling `client.send()`.

Mailmark does not yet have a built-in template engine. Personalisation happens in your application code before you call the API.

```javascript
import { Mailmark } from 'mailmark-sdk';

const client = new Mailmark('dm_live_your_api_key');

const recipients = [
  { email: 'alice@example.com', name: 'Alice' },
  { email: 'bob@example.com',   name: 'Bob' },
];

for (const { email, name } of recipients) {
  await client.send({
    from: 'hello@acme.com',
    to: email,
    subject: `Hey ${name}, check this out`,
    html: `<p>Hi ${name},</p><p>We have something just for you!</p>`,
    type: 'campaign',
  });
}
```

For large lists, batch the sends and add a small delay between requests to avoid hitting rate limits.

## Scheduling & auto follow-ups

Use the `scheduledAt` field to schedule a campaign for a future time. Pass a Unix millisecond timestamp; the value must be in the future.

```javascript
const ONE_HOUR = 60 * 60 * 1000;

await client.send({
  from: 'newsletter@acme.com',
  to: ['alice@example.com', 'bob@example.com'],
  subject: 'Scheduled newsletter',
  html: '<p>This was scheduled!</p>',
  type: 'campaign',
  scheduledAt: Date.now() + ONE_HOUR, // send in 1 hour
});
// status will be "scheduled" instead of "queued"
```

Automatic follow-ups can be implemented by scheduling subsequent sends with increasing `scheduledAt` values, or with [Sequences](/docs/sequences), which stop automatically when a contact replies.

## Campaign analytics

Each campaign send returns a `batchId` and an array of `messageIds` (one per recipient). Navigate to Dashboard -> Campaigns and enter your `batchId` to see:

- Total recipients
- Delivery status per message (queued, sent, bounced)
- Open and click tracking (on supported plans)
- Bounce and complaint rates

Open and click tracking requires a tracking domain to be configured. This is available on the Pro plan and above.

Programmatic access to the same numbers: `GET https://api.mailmark.dev/v1/campaign-stats`.

## Next steps

- [API: Send Email](/docs/api#send-email) - full send endpoint reference with all options.
- [Mailboxes](/docs/mailboxes) - create the mailboxes you send campaigns from.

---

Mailmark - email hosting and campaigns for your own domain. Canonical page: https://www.mailmark.dev/docs/email-campaigns | [llms.txt](https://www.mailmark.dev/llms.txt) | [OpenAPI](https://www.mailmark.dev/openapi.json) | [sitemap](https://www.mailmark.dev/sitemap.xml)
