Skip to main content

Request parameters

from
string
required
RFC 5322 address. Either email@verified-domain.com or "Name" <email@verified-domain.com>. The domain must be verified in your project.
to
string[]
required
Up to 50 recipients per message.
cc
string[]
Optional CC recipients.
bcc
string[]
Optional BCC recipients.
reply_to
string[]
Optional Reply-To override. Default: the from address.
subject
string
required
Subject line. Max 998 chars per RFC 5322.
html
string
HTML body. Either html or text (or both) is required.
text
string
Plain-text body. Strongly recommended for deliverability — many mail clients score text+html better than html-only.
headers
object
Custom RFC 5322 headers. Prefix with X- for safety.
tags
string[]
Free-form labels for dashboard filtering and analytics grouping. e.g. ["campaign:launch-q2", "category:transactional"].
track_opens
boolean
default:"false"
Inject a 1×1 tracking pixel. Fires email.opened webhooks.
track_clicks
boolean
default:"false"
Rewrite links through our click-tracking proxy. Fires email.clicked webhooks.
send_at
string (ISO 8601)
Schedule for a future time. Max lead: 30 days.

Response

{
  "id": "01H8XKQJ3Z...",
  "status": "queued"
}
The status transitions through queued → sent → delivered (or bounced / complained / failed), with webhooks at every transition.

With tracking

await pm.emails.send({
  from: 'team@mail.shuttlers.ng',
  to: ['user@example.com'],
  subject: 'New update on your booking',
  html: '<p>See details <a href="https://shuttlers.ng/bookings/42">here</a>.</p>',
  text: 'See details: https://shuttlers.ng/bookings/42',
  track_opens: true,
  track_clicks: true,
  tags: ['category:transactional'],
});

Batch send

POST /v1/emails/batch with up to 100 emails per call:
await pm.emails.batch([
  { from, to: ['a@example.com'], subject: 'A', html: '...' },
  { from, to: ['b@example.com'], subject: 'B', html: '...' },
]);
Batch returns { data: [{id, status}, ...] } — one row per input, in the same order.

Scheduled send & cancel

const email = await pm.emails.send({
  from, to, subject, html,
  send_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),  // in 1h
});

// Before send_at fires:
await pm.emails.cancel(email.id);
After send_at elapses, cancel fails with conflict.

Error cases

ErrorHTTPCause
domain_unverified400Sending domain has no verified DKIM/SPF
address_suppressed400A recipient is on your suppression list
validation_error400Malformed address, missing required field
quota_exceeded402Monthly quota + wallet empty
rate_limit_exceeded429Slow down