API reference
List emails
Page through every email you've sent, newest first, optionally filtered by status.
Why use this
For example
Every morning you pull yesterday's bounced emails and flag those users' addresses in your own database, so your support team knows who never got their receipt.
Example
import { YourMail } from "yourmail";
const yourmail = new YourMail(process.env.YOURMAIL_API_KEY!);
const page = await yourmail.list({ limit: 50 });
for (const email of page.data) {
console.log(email.id, email.status, email.subject);
}const res = await fetch(
"https://api.yourmail.dev/v1/emails?limit=50&status=bounced",
{
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
}
);
const { data, hasMore, nextCursor } = await res.json();curl "https://api.yourmail.dev/v1/emails?limit=50&status=bounced" \
-H "Authorization: Bearer YOUR_API_KEY"Query parameters
All parameters are optional.
limitnumber- Page size, 1–100. Defaults to 50. Values outside the range are clamped rather than rejected.
cursorstring- Opaque cursor taken from a previous response's nextCursor. Omit it to start from the newest email.
statusstring- Optional filter. One of scheduled, queued, sent, delivered, bounced, complained, failed, canceled. Any other value is a 400. Note that queued also covers a message that is mid-dispatch.
searchstring- Optional full-text term, matched against recipients, sender, subject and tag values — the same index the dashboard searches. Combines with status, with one caveat: a search is served by the search index, where status is matched exactly, so search plus status=queued does not also return mid-dispatch messages the way status=queued alone does. Results come back in relevance order rather than newest-first for the same reason; paging works the same way.
Response
// 200 OK
{
"data": [
{
"id": "jn7abc123def456",
"to": ["alice@example.com"],
"from": "hello@mail.acme.com",
"subject": "Welcome to Acme",
"status": "delivered",
"tags": [{ "name": "campaign", "value": "welcome" }],
"createdAt": 1750000000000,
"sentAt": 1750000001234,
"openedAt": 1750000090000,
"clickedAt": null
}
],
"hasMore": true,
"nextCursor": "eyJpZCI6..."
}dataobject[]- The page of emails, newest first.
data[].idstring- Unique email identifier — pass it to the retrieve endpoint for the full record.
data[].tostring[]- Recipient addresses.
data[].fromstring- Sender address as supplied in the send request.
data[].subjectstring- Email subject line.
data[].statusstring- Current delivery status.
data[].tags{name,value}[]- Tags attached at send time.
data[].createdAtnumber- Unix millisecond timestamp when the email was created.
data[].sentAtnumber | null- Unix ms when the message was accepted for delivery. Null until the status reaches sent.
data[].scheduledAtnumber | null- Unix ms a scheduled send fires at. Null for a message sent immediately — so status=scheduled answers when without a request per row.
data[].openedAtnumber | null- Unix ms of the first recorded open. Null if never opened.
data[].clickedAtnumber | null- Unix ms of the first recorded click. Null if no link was clicked.
hasMoreboolean- True when another page is available. A page can legitimately come back EMPTY with hasMore true — a status filter that skips a long run of rows returns what it found in a bounded window rather than reading indefinitely. Iterate until hasMore is false; don't stop on an empty page.
nextCursorstring | null- Pass as cursor to fetch the next page. Null on the last page.
Pagination
Results are cursor-paginated. Read nextCursor from a response and pass it back as cursor to get the following page; when it comes back null, you've reached the end. Cursors are opaque — don't construct or modify them.
// Walk every page. `nextCursor` is null on the last one.
let cursor: string | undefined;
do {
const page = await yourmail.list({ limit: 100, cursor });
for (const email of page.data) {
// ...
}
cursor = page.nextCursor ?? undefined;
} while (cursor);Errors
validation_errorThe status filter isn't a recognised status, the cursor isn't a valid pagination cursor, or search is over 512 characters or 16 terms
authentication_errorAPI key missing, malformed, or not recognised
authentication_errorThe key is send-only; listing requires a full-access key
internal_errorThis page couldn't be assembled completely — rare, and specific to very large messages. A smaller limit does not help; retry, or narrow with status or search