Send, batch, schedule, reschedule, cancel and look up transactional email through the Reggio Digital API, with attachments and test addresses.
Every call on this page needs a key with the email:send ability, on an account with a transactional email plan and at least one verified sending domain. The dashboard's Send API page shows the same examples with the account's own key and sender filled in.
Store the id. It is how you look the message up later and how our webhooks refer to it. Treat it as an opaque string: a message sent now and a message held for later have ids of different shapes.
to is the recipients we are sending to. suppressed is any recipient we skipped because we have stopped sending to that address after a hard bounce, a spam complaint or an unsubscribe. A skipped address is not an error, so check the list rather than treating the send as failed. If every recipient is suppressed, the send is refused with a 422.
An address on one of the account's verified sending domains, on its own. A name in front of it, as in Acme <hello@yourdomain.com>, is refused with a 422.
to
Yes
A list of up to 50 addresses. Always a list, even for one recipient.
subject
Without a template
The subject line, up to 998 characters.
html
Without a template, html or text
The formatted body.
text
Without a template, html or text
The plain body. Send both when you can.
template_id
Instead of a body
A published template. Leave out subject, html and text.
variables
With a template
The values the template fills in, as name and value pairs.
reply_to
No
Where replies go, if not the from address.
cc, bcc
No
Lists of up to 50 addresses each.
tags
No
Up to 20 name and value pairs for your own use, such as {"kind": "receipt", "order": "1042"}. Names use letters, numbers, dashes, underscores, colons and dots; values are strings up to 128 characters. They come back when you look a message up and in webhooks.
headers
No
Up to 10 extra email headers. Any name starting with X-, plus List-Unsubscribe and List-Unsubscribe-Post. Values cannot contain line breaks.
Give each attachment a filename and either content, the file as base64, or path, an https link we download it from. A link saves your server holding a large file in memory.
A path must be https and reachable from the public internet. Links into private networks are refused.
Add a content_id and the file becomes an inline image. Point at it from your HTML with <img src="cid:logo">.
Set content_type to the file's type, such as application/pdf. Without it the file is sent as application/octet-stream, which some mail apps will not preview.
Keep the whole message under 28MB. Anything bigger is refused with a 413; send a link to the file instead.
const response = await fetch("https://reggiodigital.com/api/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REGGIO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"from": "hello@yourdomain.com",
"to": [
"success@simulator.amazonses.com"
],
"subject": "Your receipt",
"html": "<p>Thanks for your order.</p>",
"text": "Thanks for your order.",
"tags": {
"kind": "receipt",
"order": "1042"
},
"scheduled_at": "2026-09-21T09:00:00-04:00"
}),
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error.message);
}
We check everything about the message straight away, so a problem comes back on this request rather than at send time. The response has an id starting with sched_ and the scheduled_at we recorded, in UTC. That id keeps working after the message has gone. A scheduled message does not count against the monthly allowance until it is sent.
const response = await fetch("https://reggiodigital.com/api/v1/emails/sched_123", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.REGGIO_API_KEY}`,
},
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error.message);
}
A message that has already gone out, or that we have started sending, can be neither moved nor cancelled. Moving it answers 409 with not_reschedulable, and cancelling it answers 409 with not_cancellable.
const response = await fetch("https://reggiodigital.com/api/v1/emails/batch", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REGGIO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"emails": [
{
"from": "hello@yourdomain.com",
"to": [
"success@simulator.amazonses.com"
],
"subject": "Your table is ready",
"text": "See you soon."
},
{
"from": "hello@yourdomain.com",
"to": [
"bounce@simulator.amazonses.com"
],
"subject": "Your table is ready",
"text": "See you soon."
}
]
}),
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error.message);
}
Every message is checked before any is accepted, so the whole batch is taken or none of it is. A problem names the message by position, as in emails.1.to. The response lists one result per message, in the order you sent them:
const response = await fetch("https://reggiodigital.com/api/v1/emails/MESSAGE_ID", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.REGGIO_API_KEY}`,
},
});
const body = await response.json();
if (!response.ok) {
throw new Error(body.error.message);
}
{
"id": "0100019a1b2c3d4e-5f6a7b8c-1234-5678-9abc-def012345678-000000",
"from": "hello@yourdomain.com",
"to": ["customer@example.com"],
"subject": "Your receipt",
"tags": { "kind": "receipt", "order": "1042" },
"status": "bounced",
"status_description": "The receiving server refused this message and sent it back.",
"scheduled_at": null,
"sent_at": "2026-09-14T15:04:05+00:00",
"delivered_at": null,
"bounced_at": "2026-09-14T15:04:07+00:00",
"complained_at": null,
"bounce": {
"type": "Transient",
"subtype": "MailboxFull",
"permanent": false,
"summary": "Their mailbox is full.",
"what_to_do": "Nothing is wrong with the address. It may work again once they clear space, so it is worth trying later.",
"diagnostic": "552 5.2.2 Mailbox full"
},
"events": [
{ "type": "sent", "at": "2026-09-14T15:04:05+00:00", "detail": null },
{ "type": "bounced", "at": "2026-09-14T15:04:07+00:00", "detail": "552 5.2.2 Mailbox full" }
]
}
status is one of scheduled, queued, sent, delivered, delayed, bounced, complained, rejected, failed or canceled. Delivery is not instant: a message read back straight away usually says sent, and delivered follows within a minute or two. bounce is null unless the message bounced.
Rather than polling, most integrations use webhooks.
These addresses behave in a fixed way and nothing sent to them leaves the mail network, so you can build bounce and complaint handling before a real customer is involved. They are metered and logged like any other send, and show up on the account's Sent page.