resend/references/sending/email-management.md
Version edbfece3f402.bb1 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
Email Management
Overview
After sending, emails can be retrieved, listed, rescheduled, cancelled, or shared. Updates are limited to scheduled_at only — content cannot be changed after creation.
SDK Methods
Node.js
| Operation | Method | Notes |
|---|---|---|
| Get | resend.emails.get(id) |
Returns full email details and status |
| List | resend.emails.list({ limit, offset }) |
Paginated list of sent emails |
| Update | resend.emails.update({ id, scheduledAt }) |
Reschedule only — no content changes |
| Cancel | resend.emails.cancel(id) |
Cancel a scheduled email before it sends |
| Share | resend.emails.share(id, { expiresIn }) |
Create a public link for a sent or received email; expiresIn defaults to and caps at 48h |
Python
| Operation | Method |
|---|---|
| Get | resend.Emails.get(id) |
| List | resend.Emails.list(params) |
| Update | resend.Emails.update(params) — params: { "id": ..., "scheduled_at": ... } |
| Cancel | resend.Emails.cancel(id) |
| Share | resend.Emails.share(email_id, params) — params: { "expires_in": ... } |
Examples
Get Email
// Node.js — always destructure { data, error }
const { data, error } = await resend.emails.get('email_abc123');
if (error) {
console.error(error);
return;
}
console.log(data.status); // 'delivered', 'bounced', 'scheduled', etc.
# Python — returns data directly
email = resend.Emails.get("email_abc123")
print(email["status"])
Reschedule a Scheduled Email
const { data, error } = await resend.emails.update({
id: 'email_abc123',
scheduledAt: '2026-04-01T09:00:00Z',
});
if (error) console.error(error);
resend.Emails.update({
"id": "email_abc123",
"scheduled_at": "2026-04-01T09:00:00Z",
})
Cancel a Scheduled Email
const { data, error } = await resend.emails.cancel('email_abc123');
if (error) console.error(error);
resend.Emails.cancel("email_abc123")
Share a Sent or Received Email
Creates a public, unauthenticated link — anyone with the URL can view the email. Works for both sent and received emails; the API detects which type the ID belongs to.
const { data, error } = await resend.emails.share('email_abc123', {
expiresIn: '2 hours', // optional — human-readable duration, defaults to and caps at 48h
});
if (error) {
console.error(error);
} else {
console.log(data.url);
}
shared = resend.Emails.share("email_abc123", {"expires_in": "2 hours"})
print(shared["url"])
Retrieving Attachments
List and download attachments for sent emails. Returns metadata and a signed download URL.
SDK Methods
| Operation | Node.js | Python |
|---|---|---|
| List | resend.emails.attachments.list({ emailId }) |
resend.Emails.Attachments.list(email_id) |
| Get | resend.emails.attachments.get({ emailId, id }) |
resend.Emails.Attachments.get(email_id, attachment_id) |
Examples
// List all attachments for a sent email
const { data: attachments, error } = await resend.emails.attachments.list({
emailId: 'email_abc123',
});
if (error) {
console.error(error);
return;
}
for (const att of attachments.data) {
console.log(att.filename); // 'invoice.pdf'
console.log(att.content_type); // 'application/pdf'
console.log(att.size); // bytes
console.log(att.download_url); // signed URL, expires at att.expires_at
}
// Get a single attachment
const { data: attachment, error: getError } = await resend.emails.attachments.get({
emailId: 'email_abc123',
id: 'att_def456',
});
if (getError) {
console.error(getError);
return;
}
// Download the content
const response = await fetch(attachment.download_url);
const buffer = await response.arrayBuffer();
Important: download_url expires (see expires_at field). Call the API again for a fresh URL if needed.
Attachment Response Fields
| Field | Type | Description |
|---|---|---|
id |
string | Attachment ID |
filename |
string | Original filename |
content_type |
string | MIME type |
content_id |
string | Content ID for inline attachments |
content_disposition |
"inline" | "attachment" |
Display mode |
download_url |
string | Signed download URL |
expires_at |
string | When the download URL expires |
size |
number | Size in bytes |
Retrieving Metrics
Account-level email delivery and engagement metrics (sent, delivered, bounced, opened, clicked, etc.) for a date range. With no options, returns totals only. Optionally broken down by one or more dimensions: period, domain, email, broadcast.
email and broadcast are mutually exclusive — as dimensions, and as filters (emailId/broadcastId). Requesting both, in either form, is rejected.
SDK Methods
| Operation | Node.js | Python |
|---|---|---|
| Get metrics | resend.emails.metrics(options) |
resend.Emails.metrics(params) |
options/params (all optional):
| Field (Node.js / Python) | Type | Notes |
|---|---|---|
startDate / start_date |
string | ISO 8601 date or datetime. Defaults to 6 days before endDate |
endDate / end_date |
string | ISO 8601 date or datetime. Defaults to now |
timezone |
string | IANA timezone, e.g. America/New_York. Defaults to UTC |
granularity |
string | hourly, daily, weekly, or monthly — bucket size when period is a dimension. Defaults to daily |
metrics |
string[] | Which metrics to include. Defaults to all |
dimensions |
string[] | period, domain, email, broadcast — combinable except email+broadcast |
domainId / domain_id |
string[] | Restrict to these sending domain IDs (max 100) |
emailId / email_id |
string[] | Restrict to these email IDs (max 100). Cannot combine with broadcast dimension/broadcastId |
broadcastId / broadcast_id |
string[] | Restrict to these broadcast IDs (max 100). Cannot combine with email dimension/emailId |
Examples
// Totals only, default 6-day window
const { data, error } = await resend.emails.metrics();
if (error) {
console.error(error);
return;
}
console.log(data.totals.sent, data.totals.delivered);
# Totals only, default 6-day window
metrics = resend.Emails.metrics({})
print(metrics["totals"]["sent"], metrics["totals"]["delivered"])
// Broken down by period and broadcast, filtered to one broadcast
const { data, error } = await resend.emails.metrics({
startDate: '2026-07-01',
endDate: '2026-07-08',
dimensions: ['period', 'broadcast'],
broadcastId: ['bc_abc123'],
});
if (error) console.error(error);
for (const row of data.data ?? []) {
console.log(row.period, row.broadcast_name, row.delivered);
}
metrics = resend.Emails.metrics({
"start_date": "2026-07-01",
"end_date": "2026-07-08",
"dimensions": ["period", "broadcast"],
"broadcast_id": ["bc_abc123"],
})
for row in metrics.get("data", []):
print(row["period"], row["broadcast_name"], row["delivered"])
Metrics Response Fields
| Field | Type | Description |
|---|---|---|
totals |
object | Metric totals for the whole date range, keyed by metric name |
data |
array | absent | Per-dimension breakdown rows. Absent when no dimensions were requested |
Common Mistakes
| Mistake | Fix |
|---|---|
Trying to update subject, html, or to |
Only scheduledAt can be updated — cancel and resend for content changes |
| Cancelling an already-sent email | Cancel only works on emails with scheduled status |
| Cancelling too late | Cancel before the scheduled_at time — there's a brief processing window before send |
Not checking error in Node.js |
SDK returns { data, error }, does not throw — always destructure and check |
Using .list() without pagination |
Pass limit and offset to paginate through results |
Combining email and broadcast in metrics |
These are mutually exclusive as dimensions and as filters — the request is rejected |
Expecting unique_opened/open_rate-style metrics without tracking enabled |
Open/click tracking must be enabled on the sending domain for these to be meaningful |
Assuming a longer expiresIn is possible |
48 hours is the maximum — requesting more returns a validation error |
| Treating share links as revocable | There's no revoke endpoint — the link is valid until it expires, no early invalidation |