Emails
Send transactional emails, batch sends, scheduled emails, and retrieve email status and delivery events. Supports sender names, CC/BCC, attachments, tags, templates, idempotency keys, and open/click tracking.
/emailsSend a single email. Supports sender names, CC/BCC, attachments, scheduling, templates, and idempotency keys.
{
"from": "PostStack <you@yourdomain.com>",
"to": ["user@example.com"],
"cc": ["teammate@example.com"],
"bcc": ["archive@yourdomain.com"],
"subject": "Welcome to PostStack",
"html": "<h1>Hello!</h1><p>Welcome aboard.</p>",
"text": "Hello! Welcome aboard.",
"reply_to": "support@yourdomain.com",
"headers": {
"X-Custom-Header": "value"
},
"tags": ["welcome", "onboarding"],
"attachments": [
{
"filename": "invoice.pdf",
"content": "base64-encoded-content...",
"content_type": "application/pdf"
}
],
"idempotency_key": "order_123_welcome",
"scheduled_at": "2026-03-25T09:00:00.000Z",
"unsubscribe": false
}/emails/batchSend up to 100 emails in a single request. Each email is queued independently and supports all the same options as a single send.
{
"emails": [
{
"from": "PostStack <you@yourdomain.com>",
"to": ["alice@example.com"],
"subject": "Hello Alice",
"html": "<p>Welcome, Alice!</p>"
},
{
"from": "PostStack <you@yourdomain.com>",
"to": ["bob@example.com"],
"subject": "Hello Bob",
"html": "<p>Welcome, Bob!</p>"
}
]
}/emailsList emails with pagination. Filter by status, domain, recipient, tag, or date range.
{
"emails": [
{
"id": "em_abc123def456ghi789",
"from": "you@yourdomain.com",
"to": ["user@example.com"],
"subject": "Welcome to PostStack",
"status": "delivered",
"created_at": "2026-03-23T10:00:00.000Z"
}
],
"pagination": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
}
}/emails/:idRetrieve a single email by ID, including delivery events and metadata.
{
"id": "em_abc123def456ghi789",
"from": "PostStack <you@yourdomain.com>",
"to": ["user@example.com"],
"subject": "Welcome to PostStack",
"status": "delivered",
"html": "<h1>Hello!</h1><p>Welcome aboard.</p>",
"text": "Hello! Welcome aboard.",
"tags": ["welcome"],
"created_at": "2026-03-23T10:00:00.000Z",
"sent_at": "2026-03-23T10:00:01.000Z",
"delivered_at": "2026-03-23T10:00:02.000Z",
"events": [
{ "type": "queued", "timestamp": "2026-03-23T10:00:00.000Z" },
{ "type": "sent", "timestamp": "2026-03-23T10:00:01.000Z" },
{ "type": "delivered", "timestamp": "2026-03-23T10:00:02.000Z" },
{ "type": "opened", "timestamp": "2026-03-23T10:01:15.000Z" }
]
}/emails/:id/eventsRetrieve the full event history for an email, including timestamps for each status change.
{
"events": [
{ "type": "queued", "timestamp": "2026-03-23T10:00:00.000Z" },
{ "type": "sent", "timestamp": "2026-03-23T10:00:01.000Z" },
{ "type": "delivered", "timestamp": "2026-03-23T10:00:02.000Z" },
{ "type": "opened", "timestamp": "2026-03-23T10:01:15.000Z" },
{ "type": "clicked", "timestamp": "2026-03-23T10:02:30.000Z" }
]
}/emails/:id/insightsGet delivery insights and warnings for an email, such as missing authentication records or content issues.
{
"warnings": [
{
"type": "missing_dkim",
"message": "DKIM record not found for this domain",
"severity": "high"
},
{
"type": "large_html",
"message": "HTML content exceeds 100KB which may cause clipping",
"severity": "low"
}
]
}/emails/:idReschedule a scheduled email to a new send time. Only works for emails with 'scheduled' status.
{
"scheduled_at": "2026-03-26T14:00:00.000Z"
}/emails/:id/cancelCancel a queued or scheduled email before it is sent. Works for emails with 'queued' or 'scheduled' status.
{
"id": "em_abc123def456ghi789",
"status": "cancelled"
}Sender Name
The from field supports a display name using the Name <email> format. The display name appears in the recipient's inbox instead of the raw email address:
// With sender name — shows "PostStack" in the inbox
await poststack.emails.send({
from: 'PostStack <you@yourdomain.com>',
to: ['user@example.com'],
subject: 'Hello!',
html: '<p>Welcome!</p>',
});
// Without sender name — shows "you@yourdomain.com"
await poststack.emails.send({
from: 'you@yourdomain.com',
to: ['user@example.com'],
subject: 'Hello!',
html: '<p>Welcome!</p>',
});Attachments
Attach up to 10 files per email (max 25MB each). Content must be base64-encoded:
import { readFileSync } from 'fs';
const pdfContent = readFileSync('invoice.pdf').toString('base64');
await poststack.emails.send({
from: 'billing@yourdomain.com',
to: ['customer@example.com'],
subject: 'Your Invoice',
html: '<p>Please find your invoice attached.</p>',
attachments: [
{
filename: 'invoice.pdf',
content: pdfContent,
content_type: 'application/pdf',
},
],
});Templates
Send emails using a pre-built template. Pass the template ID and variables to substitute. When using a template, the subject, html, and text fields come from the template:
await poststack.emails.send({
from: 'PostStack <you@yourdomain.com>',
to: ['user@example.com'],
template_id: 'tpl_abc123',
variables: {
name: 'Alice',
action_url: 'https://example.com/activate',
},
});Scheduled Emails
Schedule emails for future delivery by including a scheduled_at timestamp (ISO 8601 format, UTC). Scheduled emails can be cancelled or rescheduled before their send time:
// Schedule an email for tomorrow at 9 AM UTC
const { id } = await poststack.emails.send({
from: 'you@yourdomain.com',
to: ['user@example.com'],
subject: 'Reminder',
html: '<p>Your trial ends tomorrow.</p>',
scheduled_at: '2026-03-25T09:00:00.000Z',
});
// Reschedule to a different time
await poststack.emails.reschedule(id, '2026-03-26T14:00:00.000Z');
// Or cancel before it sends
await poststack.emails.cancel(id);Tags
Add up to 10 tags per email for categorization and filtering. Tags are arbitrary strings (max 64 characters each) that help you organize and analyze your email activity:
await poststack.emails.send({
from: 'you@yourdomain.com',
to: ['user@example.com'],
subject: 'Order Confirmation',
html: '<p>Your order has been confirmed.</p>',
tags: ['transactional', 'order-confirmation', 'store-us'],
});
// Filter emails by tag when listing
const { emails } = await poststack.emails.list({
tag: 'order-confirmation',
});Idempotency
Include an idempotency_key (max 64 characters) to safely retry requests without sending duplicate emails. If a request with the same key has already been processed, the original send is replayed — the response includes replayed: true and the response carries an X-Idempotent-Replay: true header. The default deduplication window is 24 hours; override per-call with idempotency_window_hours up to 72:
await poststack.emails.send({
from: 'you@yourdomain.com',
to: ['user@example.com'],
subject: 'Order #123 Confirmation',
html: '<p>Your order has been confirmed.</p>',
idempotency_key: 'order_123_confirmation',
idempotency_window_hours: 48,
});Per-send Tracking Override
Open and click tracking are configured per domain. Override either or both per-message via tracking to suppress the pixel or link rewriting on a specific send (useful for password resets, magic links, and other transactional mail where tracking is unwanted):
await poststack.emails.send({
from: 'security@yourdomain.com',
to: ['user@example.com'],
subject: 'Reset your password',
html: '<p><a href="https://app.example.com/reset?t=...">Reset link</a></p>',
tracking: { opens: false, clicks: false },
});Spam Scoring
Every send is scored by rspamd before it enters the queue. The score comes back on the send response and is stored on the email record. Thresholds default to 5.0 (warn — delivered with warnings attached) and 10.0 (block — rejected with a 422 before the email is queued):
{
"id": "em_abc123def456ghi789",
"spam_score": 6.42,
"spam_warnings": [
{
"name": "HTML_SHORT_LINK_IMG_2",
"score": 3.0,
"description": "HTML: short link + image"
},
{
"name": "MIME_HTML_ONLY",
"score": 0.2,
"description": "Message has only HTML part"
}
]
}Hard-rejected sends return 422 Email rejected by spam filter and never consume a usage slot. If rspamd is unreachable, scoring is skipped and the send proceeds normally — an rspamd outage never blocks delivery.
Bulk Sender Compliance
Gmail and Yahoo require any sender hitting more than 5,000 messages a day to one of their users to honor RFC 8058 one-click unsubscribe. Set unsubscribe to true on any list-style send (broadcasts already opt in automatically) and PostStack will inject the required headers:
List-Unsubscribe: <mailto:unsub+TOKEN@unsubscribe.poststack.dev>, <https://app.poststack.dev/u/TOKEN>
List-Unsubscribe-Post: List-Unsubscribe=One-ClickWhen the recipient clicks "Unsubscribe" in Gmail, Apple Mail, Outlook, or any client that surfaces the header, the recipient address is added to your suppression list and never receives another send from your team. Tokens are signed and expire 60 days after issuance. Pure transactional sends (password resets, order confirmations) should leave unsubscribe unset — the spec only mandates the header on list-style mail.
Query Parameters
The list endpoint supports the following query parameters for filtering:
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
per_page | Results per page, 1-100 (default: 20) |
status | Filter by status: queued, sending, delivered, bounced, complained, failed |
domain_id | Filter by sending domain ID |
to | Filter by recipient email address |
tag | Filter by tag |
date_from | Filter emails created after this date (ISO 8601) |
date_to | Filter emails created before this date (ISO 8601) |