Emails
Send, batch, schedule, look up and cancel emails.
All requests go to https://api.padimail.app with Authorization: Bearer <your key>. Try these in the interactive reference with a pm_test_ key.
Send an email
POST/v1/emails
Body
| Field | Type | Required | Notes |
|---|---|---|---|
from | string | Yes | A verified domain address, one of your sender handles, or onboarding@sandbox.padimail.app. A display name is allowed: Acme Store <hello@acme.ng>. |
to | string (email) | string (email)[] | Yes | One address or an array. At most 50 across to, cc and bcc. |
cc | string (email) | string (email)[] | No | |
bcc | string (email) | string (email)[] | No | |
reply_to | string (email) | string (email)[] | No | Where replies go. |
subject | string | No | Required unless the template has one. |
html | string | No | Give html and/or text, or template. With only html, we generate text. |
text | string | No | The plain-text body. |
template | { id, variables } | No | A template ID or alias, and its variables. Uses the published version. |
stream | "transactional" | "marketing" | No | transactional for receipts, codes and alerts; marketing for newsletters. Defaults by sender. |
headers | object | No | Extra headers. Some can't be overridden on shared domains. |
attachments | object[] | No | { filename, content (base64) or path (HTTPS URL), content_type }. 40 MB in total. Not on shared domains. |
tags | object[] | No | { name, value } pairs of letters, digits, _ and -, for filtering and webhooks. At most 10. |
scheduled_at | string (date-time) | No | Send later: 1 minute to 30 days ahead, ISO 8601. |
quota_behavior | "queue" | "reject" | No | Over a limit: queue waits (the default), reject fails with 429 or 402. Use reject for OTPs. |
fallback | boolean | No | Free plan only: false keeps this email off your connected Gmail. |
Send an Idempotency-Key header to make retries safe. The Node SDK adds one for you.
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.send({
from: "Acme Store <hello@acme.ng>",
to: ["ada@example.com"],
subject: "Your receipt #1042",
html: "<p>Thanks for your order.</p>",
tags: [{ name: "category", value: "receipt" }],
});Response 200
{ "id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S" }List emails
GET/v1/emails
Query parameters
| Field | Type | Required | Notes |
|---|---|---|---|
limit | integer | No | Page size, 1 to 100. Default 50. |
after | string | No | Return items after this ID (the next value of the previous page). |
before | string | No | Return items before this ID. |
created_after | string (date-time) | No | ISO 8601. |
created_before | string (date-time) | No | ISO 8601. |
status | "queued" | "scheduled" | "deferred" | "scanning" | "held" | "sending" | "sent" | "delivered" | "delivery_delayed" | "bounced" | "complained" | "suppressed" | "rejected" | "failed" | "canceled" | No | |
to | string (email) | No | An exact recipient address. |
from | string | No | |
domain_id | string | No | |
tag | string | No | name:value. |
stream | "transactional" | "marketing" | No | |
source | "api" | "smtp" | "dashboard" | "paste_send" | "broadcast" | "automation" | No | Where the email came from: api, smtp, dashboard and so on. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.list({ status: "delivered", limit: 20 });Response 200
{
"data": [
{
"id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"object": "email",
"created_at": "2026-10-27T13:05:09Z",
"mode": "live",
"from": "string",
"to": ["string"],
"subject": "string",
"stream": "transactional",
"source": "api",
"sender_kind": "custom_domain",
"sent_via": "padimail",
"status": "queued",
"scheduled_at": "2026-10-27T13:05:09Z",
"deferred_reason": "daily_limit",
"deferred_until": "2026-10-27T13:05:09Z",
"tags": [{ "name": "string", "value": "string" }],
"recipient_count": 0
}
],
"has_more": false,
"next": "string"
}Send up to 100 emails
POST/v1/emails/batch
Query parameters
| Field | Type | Required | Notes |
|---|---|---|---|
validation | "strict" | "permissive" | No | strict (default) fails the batch on one invalid email; permissive sends the valid ones and lists the rest in errors. |
Body: an array of 1 to 100 objects with these fields
| Field | Type | Required | Notes |
|---|---|---|---|
from | string | Yes | A verified domain address, one of your sender handles, or onboarding@sandbox.padimail.app. A display name is allowed: Acme Store <hello@acme.ng>. |
to | string (email) | string (email)[] | Yes | One address or an array. At most 50 across to, cc and bcc. |
cc | string (email) | string (email)[] | No | |
bcc | string (email) | string (email)[] | No | |
reply_to | string (email) | string (email)[] | No | Where replies go. |
subject | string | No | Required unless the template has one. |
html | string | No | Give html and/or text, or template. With only html, we generate text. |
text | string | No | The plain-text body. |
template | { id, variables } | No | A template ID or alias, and its variables. Uses the published version. |
stream | "transactional" | "marketing" | No | transactional for receipts, codes and alerts; marketing for newsletters. Defaults by sender. |
headers | object | No | Extra headers. Some can't be overridden on shared domains. |
tags | object[] | No | { name, value } pairs of letters, digits, _ and -, for filtering and webhooks. At most 10. |
quota_behavior | "queue" | "reject" | No | Over a limit: queue waits (the default), reject fails with 429 or 402. Use reject for OTPs. |
fallback | boolean | No | Free plan only: false keeps this email off your connected Gmail. |
Send an Idempotency-Key header to make retries safe. The Node SDK adds one for you.
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.batch([
{
from: "Acme Store <hello@acme.ng>",
to: ["ada@example.com"],
subject: "Your receipt #1042",
html: "<p>Thanks, Ada.</p>",
},
{
from: "Acme Store <hello@acme.ng>",
to: ["chidi@example.com"],
subject: "Your receipt #1043",
html: "<p>Thanks, Chidi.</p>",
},
]);Response 200
{
"data": [
{ "id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S" },
{ "id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5T" }
]
}Get an email
GET/v1/emails/{id}
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | The email's ID, em_…. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.get("em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S");Response 200
{
"id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"object": "email",
"created_at": "2026-10-27T13:05:09Z",
"mode": "live",
"from": "string",
"to": ["string"],
"subject": "string",
"stream": "transactional",
"source": "api",
"sender_kind": "custom_domain",
"sent_via": "padimail",
"status": "queued",
"scheduled_at": "2026-10-27T13:05:09Z",
"deferred_reason": "daily_limit",
"deferred_until": "2026-10-27T13:05:09Z",
"tags": [{ "name": "string", "value": "string" }],
"recipient_count": 0,
"reply_to": ["string"],
"domain_id": "dom_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"api_key_id": "string",
"template_id": "string",
"template_version": 0,
"has_attachments": false,
"size_bytes": 0,
"content_expires_at": "2026-10-27T13:05:09Z",
"content_available": false,
"canceled_at": "2026-10-27T13:05:09Z",
"recipients": [
{
"id": "rcp_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"address": "ada@example.com",
"kind": "to",
"status": "queued",
"sent_at": "2026-10-27T13:05:09Z",
"delivered_at": "2026-10-27T13:05:09Z",
"opened_at": "2026-10-27T13:05:09Z",
"clicked_at": "2026-10-27T13:05:09Z",
"bounced_at": "2026-10-27T13:05:09Z",
"complained_at": "2026-10-27T13:05:09Z",
"bounce_type": "hard",
"status_detail": "string",
"open_count": 0,
"click_count": 0
}
]
}Reschedule a scheduled email
PATCH/v1/emails/{id}
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | The email's ID, em_…. |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
scheduled_at | string (date-time) | Yes | The new send time. Only while the email is scheduled. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.update("em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S", { scheduled_at: "2026-10-28T09:00:00+01:00" });Response 200
{
"id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"object": "email",
"created_at": "2026-10-27T13:05:09Z",
"mode": "live",
"from": "string",
"to": ["string"],
"subject": "string",
"stream": "transactional",
"source": "api",
"sender_kind": "custom_domain",
"sent_via": "padimail",
"status": "queued",
"scheduled_at": "2026-10-27T13:05:09Z",
"deferred_reason": "daily_limit",
"deferred_until": "2026-10-27T13:05:09Z",
"tags": [{ "name": "string", "value": "string" }],
"recipient_count": 0,
"reply_to": ["string"],
"domain_id": "dom_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"api_key_id": "string",
"template_id": "string",
"template_version": 0,
"has_attachments": false,
"size_bytes": 0,
"content_expires_at": "2026-10-27T13:05:09Z",
"content_available": false,
"canceled_at": "2026-10-27T13:05:09Z",
"recipients": [
{
"id": "rcp_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"address": "ada@example.com",
"kind": "to",
"status": "queued",
"sent_at": "2026-10-27T13:05:09Z",
"delivered_at": "2026-10-27T13:05:09Z",
"opened_at": "2026-10-27T13:05:09Z",
"clicked_at": "2026-10-27T13:05:09Z",
"bounced_at": "2026-10-27T13:05:09Z",
"complained_at": "2026-10-27T13:05:09Z",
"bounce_type": "hard",
"status_detail": "string",
"open_count": 0,
"click_count": 0
}
]
}An email's events, oldest first
GET/v1/emails/{id}/events
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | The email's ID, em_…. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.events("em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S");Response 200
{
"data": [
{
"id": "evt_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"type": "queued",
"occurred_at": "2026-10-27T13:05:09Z",
"email_id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"recipient_id": "rcp_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"to": "string",
"data": {}
}
]
}An email's content while it is retained
GET/v1/emails/{id}/content
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | The email's ID, em_…. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.content("em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S");Response 200
{ "html": "string", "text": "string", "headers": {} }Cancel a scheduled, waiting or held email
POST/v1/emails/{id}/cancel
Path parameters
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | The email's ID, em_…. |
Example
import { PadiMail } from "@padimail/node";
const pm = new PadiMail(process.env.PADIMAIL_API_KEY);
const result = await pm.emails.cancel("em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S");Response 200
{
"id": "em_01JBX7Q3F6ZK0Q9W1Y2R3T4V5S",
"object": "email",
"status": "canceled"
}