Mailchimp Transactional
Transports

Mailchimp Transactional

Send emails via the Mailchimp Transactional (Mandrill) API

The Mailchimp Transactional transport sends email through the Mandrill HTTP API (now part of Mailchimp). It uses plain fetch() with zero external dependencies, so it works in Node.js, Bun, Cloudflare Workers, and any runtime with a global fetch.

Install

npm install @betternotify/mailchimp @betternotify/core @betternotify/email

Getting your API key

  1. Log in to your Mailchimp account.
  2. Navigate to Transactional (or go directly to mandrillapp.com).
  3. Go to Settings > SMTP & API Info.
  4. Click + New API Key and copy the key.

Store it as an environment variable:

MANDRILL_API_KEY=your-api-key-here

You must verify a sending domain in your Mandrill account before sending email. Go to Settings > Domains to add and verify your domain.

Usage

import { createNotify, createClient } from '@betternotify/core';
import { emailChannel } from '@betternotify/email';
import { mailchimpTransport } from '@betternotify/mailchimp';

const email = emailChannel({
  defaults: { from: { name: 'My App', email: 'noreply@example.com' } },
});

const rpc = createNotify({ channels: { email } });
const catalog = rpc.catalog({
  /* routes */
});

const mail = createClient({
  catalog,
  transportsByChannel: {
    email: mailchimpTransport({
      apiKey: process.env.MANDRILL_API_KEY!,
    }),
  },
});

Standalone usage

You can use the transport directly without the full Better-Notify pipeline:

import { mailchimpTransport } from '@betternotify/mailchimp';

const transport = mailchimpTransport({
  apiKey: process.env.MANDRILL_API_KEY!,
});

const result = await transport.send(
  {
    from: { name: 'My App', email: 'noreply@example.com' },
    to: [{ email: 'user@example.com' }],
    subject: 'Welcome!',
    html: '<h1>Hello</h1>',
  },
  { route: 'welcome', messageId: 'msg-1', attempt: 1 },
);

Options

Prop

Type

Error handling

The transport maps Mandrill errors and HTTP status codes to Better-Notify error codes:

ConditionMandrill errorBetter-Notify code
Invalid API keyInvalid_KeyCONFIG
Payment requiredPaymentRequiredCONFIG
Unknown subaccountUnknown_SubaccountCONFIG
Validation failureValidationErrorVALIDATION
HTTP 429Rate limitedRATE_LIMITED
HTTP 5xxServer errorPROVIDER
Network failure—PROVIDER
Timeout—TIMEOUT

Per-recipient rejections (rejected, invalid, bounced) are not transport-level errors — the send succeeds with ok: true, and rejected addresses appear in result.data.rejected.

Tags

Mandrill supports up to 100 tags per message. Better-Notify's tags field (Record<string, string | number | boolean>) maps to Mandrill's tag array using the keys only (values are discarded — Mandrill tags are labels, not key-value pairs):

await mail.welcome.send({
  to: 'user@example.com',
  input: { name: 'Alice' },
  tags: { onboarding: true, version: 2 },
});
// Sent as: ["onboarding", "version"]

Attachments

The transport base64-encodes attachment content before sending. Mandrill distinguishes between regular attachments and inline images:

  • Attachments without a cid go to Mandrill's attachments array.
  • Attachments with a cid go to Mandrill's images array (for inline images in HTML).
await mail.invoice.send({
  to: 'user@example.com',
  input: { orderId: '12345' },
  attachments: [
    { filename: 'invoice.pdf', content: pdfBuffer, contentType: 'application/pdf' },
    { filename: 'logo.png', content: logoBuffer, contentType: 'image/png', cid: 'logo' },
  ],
});

Limits

These limits are enforced by Mandrill, not by the transport:

  • 1,000 recipients per /messages/send call (to + cc + bcc combined)
  • 25 MB total message size (including base64-encoded attachments)
  • 100 tags per message
  • Sending limits depend on your Mailchimp Transactional plan

If you need throttling or provider fallback, add withRateLimit middleware or wrap the transport in multiTransport({ strategy: 'failover' }).

On this page