Sending and batch
POST /v1/emails sends one email, and a batch sends up to 100 at once. Field names match Resend’s, so most existing requests work unchanged.
Sending email
POST /v1/emails sends one email. Field names match Resend's, so most existing requests work unchanged.
| Field | Meaning |
|---|---|
| from | Sender, optionally with a name. Its domain must be a verified sending domain on your account. |
| to, cc, bcc | A string or an array; at most 50 recipients across to, cc and bcc. |
| reply_to | A string or an array. |
| subject, html, text | Subject up to 998 characters, and at least one of html or text. Omit all three when you send a template. |
| variables | Values to substitute into placeholders in subject, html and text. Ignored when template is set; a template's own variables go in template.variables instead. |
| template | Send a stored template by id or alias, with variables. |
| attachments | filename plus one of content (base64), content_base64 or path (an HTTPS URL). content_id embeds the file inline. Up to 10 files and 25 MB in total. The first 2 MB per recipient are included; each started MB above that costs $0.002 per recipient (email_attachment_price_millicents_per_mb on GET /v1/pricing). Executable and script types (.exe, .js, .bat, .jar and the like) are refused. |
| tags | Up to 10 name and value pairs, each part 1 to 256 letters, digits, underscores or hyphens. Names starting with honkio_ are reserved. Filter with GET /v1/emails?tag=name:value. Tags are kept for as long as the email record, after the subject and body are purged, so keep personal information out of them. A fetched email's body_purged_at is null until that happens. |
| headers | Extra email headers, sent as given. Headers HonkIO sets itself (To, From, Subject, Content-Type, List-Unsubscribe and the like) and any name starting with ARC- or X-SES- are refused with EMAIL_HEADER_INVALID. |
| scheduled_at | An ISO 8601 date-time, 1 minute to 30 days ahead. |
| is_commercial | False by default. True marks a commercial message under CASL; see below. |
| tracking | Per email opens and clicks, overriding the domain's setting. |
const { data, error } = await honkio.emails.send({
from: 'Acme Receipts <receipts@mail.acme.ca>',
to: ['ada@example.com'],
replyTo: 'help@acme.ca',
subject: 'Your receipt for order 1042',
html: '<img src="cid:logo"><p>Thanks for your order.</p>',
text: 'Thanks for your order.',
tags: [{ name: 'kind', value: 'receipt' }],
attachments: [
{ filename: 'receipt.pdf', path: 'https://files.acme.ca/r/1042.pdf' },
{ filename: 'logo.png', content: 'iVBORw0KGgo...', contentId: 'logo' },
],
scheduledAt: '2026-10-01T13:00:00-04:00',
}, { idempotencyKey: 'order-1042-receipt' })
if (error) throw new Error(error.message)
console.log(data.id, data.status, data.scheduled_at)curl -X POST https://api.honkio.ca/v1/emails \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Idempotency-Key: order-1042-receipt" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme Receipts <receipts@mail.acme.ca>",
"to": ["ada@example.com"],
"reply_to": "help@acme.ca",
"subject": "Your receipt for order 1042",
"html": "<img src=\"cid:logo\"><p>Thanks for your order.</p>",
"text": "Thanks for your order.",
"tags": [{ "name": "kind", "value": "receipt" }],
"attachments": [
{ "filename": "receipt.pdf", "path": "https://files.acme.ca/r/1042.pdf" },
{ "filename": "logo.png", "content": "iVBORw0KGgo...", "content_id": "logo" }
],
"scheduled_at": "2026-10-01T13:00:00-04:00"
}'// Move it, or cancel it (refunded), while it is still scheduled:
const moved = await honkio.emails.update('EMAIL_ID', { scheduledAt: '2026-10-02T09:00:00-04:00' })
if (moved.error) throw new Error(moved.error.message)
const cancelled = await honkio.emails.cancel('EMAIL_ID')
if (cancelled.error) throw new Error(cancelled.error.message)# Move it, or cancel it (refunded), while it is still scheduled:
curl -X PATCH https://api.honkio.ca/v1/emails/EMAIL_ID -H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" -d '{ "scheduled_at": "2026-10-02T09:00:00-04:00" }'
curl -X DELETE https://api.honkio.ca/v1/emails/EMAIL_ID -H "Authorization: Bearer mk_live_YOUR_KEY"A path attachment is fetched once when you send, over HTTPS only, from a public address, within 10 seconds. If it can't be fetched, the send is refused with EMAIL_ATTACHMENT_FETCH_FAILED and nothing is charged. Attachment bytes are stored only until the email is sent.
Scheduled emails can be moved with PATCH or cancelled with DELETE while they are still scheduled; cancelling refunds the charge. Attachments work on scheduled sends too.
Send an Idempotency-Key header on anything that may retry. The same key returns the original response instead of sending, and charging, twice.
A replay that arrives while the first request with that key is still being charged gets 409 EMAIL_IDEMPOTENCY_IN_PROGRESS. Retry the same key shortly rather than switching to a new one.
Read what you sent with GET /v1/emails, newest first, and GET /v1/emails/:id for one email with its events. Filter the list by to (any address in the to list; cc and bcc are not searched), from, since and until (ISO 8601 with an offset), domain_id, status and tag, and page with limit and cursor. A key only reads emails of its own mode: a live key lists live sends, a test key test sends.
const { data, error } = await honkio.emails.list({ to: 'sam@example.com', since: '2026-09-01T00:00:00Z', limit: 20 })
if (error) throw new Error(error.message)
for (const email of data.data) console.log(email.id, email.to, email.status, email.livemode)
console.log(data.has_more, data.next_cursor)curl "https://api.honkio.ca/v1/emails?to=sam@example.com&since=2026-09-01T00:00:00Z&limit=20" \
-H "Authorization: Bearer mk_live_YOUR_KEY"
# → { "data": [{ "id": "...", "from": "...", "to": ["sam@example.com"], "status": "delivered", "attachments_count": 0, "livemode": true, ... }], "has_more": false, "next_cursor": null }const { data: email, error } = await honkio.emails.get('EMAIL_ID')
if (error) throw new Error(error.message)
console.log(email.status, email.events)curl https://api.honkio.ca/v1/emails/EMAIL_ID -H "Authorization: Bearer mk_live_YOUR_KEY"Batch sending
Send a JSON array of up to 100 independent emails. Every item is checked before any is sent; an invalid item fails the whole call, while a compliance refusal such as a suppressed address rejects only that item and is listed with its index.
const { data, error } = await honkio.batch.send([
{ from: 'noreply@mail.acme.ca', to: 'ada@example.com', subject: 'Your code', text: '123456' },
{ from: 'noreply@mail.acme.ca', to: 'lin@example.com', template: { id: 'welcome', variables: { first_name: 'Lin' } } },
])
if (error) throw new Error(error.message)
console.log(data.data, data.batch_id, data.rejected) // [{ id }, { id }], '...', []curl -X POST https://api.honkio.ca/v1/emails/batch \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '[
{ "from": "noreply@mail.acme.ca", "to": "ada@example.com", "subject": "Your code", "text": "123456" },
{ "from": "noreply@mail.acme.ca", "to": "lin@example.com", "template": { "id": "welcome", "variables": { "first_name": "Lin" } } }
]'
# → 200 { "data": [{ "id": "cm..." }, { "id": "cm..." }], "batch_id": "...", "rejected": [] }data lists only the emails that were actually sent, so it is not aligned with your request by position. Match a rejection back to its input item with rejected's index field.
Or send one message to up to 500 recipients as an object with a recipients list, each recipient with its own variables. Attachments and per item scheduling are not available in batches.
An email's html and text may be up to 2 MB each, also once variables are substituted: a larger body is refused with 422 VALIDATION_ERROR, or with EMAIL_RENDER_FAILED when its variables make it too large. A batch request may be up to 10 MB in all; send a larger email on its own.
const { data, error } = await honkio.batch.send({
from: 'noreply@mail.acme.ca',
template: { id: 'shipping-update' },
recipients: [
{ to: 'ada@example.com', variables: { first_name: 'Ada', tracking: '1Z999' } },
{ to: 'lin@example.com', variables: { first_name: 'Lin', tracking: '1Z998' } },
],
})
if (error) throw new Error(error.message)
console.log(data.batch_id, data.accepted_count, data.rejected)curl -X POST https://api.honkio.ca/v1/emails/batch \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "noreply@mail.acme.ca",
"template": { "id": "shipping-update" },
"recipients": [
{ "to": "ada@example.com", "variables": { "first_name": "Ada", "tracking": "1Z999" } },
{ "to": "lin@example.com", "variables": { "first_name": "Lin", "tracking": "1Z998" } }
]
}'
HonkIO