Postchi API Documentation
Base URL: http://localhost:3000
Authentication
All API endpoints except /auth/register and /auth/login require authentication via JWT token.
Include the token in the Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN
Register
Create a new user and organization.
Endpoint: POST /v1/auth/register
Request Body:
{
"email": "user@example.com",
"password": "SecurePass123",
"name": "John Doe",
"organizationName": "My Company"
}
Password Requirements:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
Response: 201 Created
{
"success": true,
"data": {
"user": {
"id": "clx1...",
"email": "user@example.com",
"name": "John Doe",
"role": "ADMIN"
},
"organization": {
"id": "clx2...",
"name": "My Company",
"slug": "my-company-1699..."
},
"tokens": {
"accessToken": "eyJhbGciOiJIUzI1...",
"refreshToken": "eyJhbGciOiJIUzI1..."
}
}
}
Login
Authenticate an existing user.
Endpoint: POST /v1/auth/login
Request Body:
{
"email": "user@example.com",
"password": "SecurePass123"
}
Response: 200 OK
{
"success": true,
"data": {
"user": {
"id": "clx1...",
"email": "user@example.com",
"name": "John Doe",
"role": "ADMIN"
},
"organization": {
"id": "clx2...",
"name": "My Company",
"slug": "my-company-1699..."
},
"tokens": {
"accessToken": "eyJhbGciOiJIUzI1...",
"refreshToken": "eyJhbGciOiJIUzI1..."
}
}
}
Get Current User
Get the authenticated user's information.
Endpoint: GET /v1/auth/me
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"user": {
"id": "clx1...",
"email": "user@example.com",
"name": "John Doe",
"role": "ADMIN"
},
"organization": {
"id": "clx2...",
"name": "My Company",
"slug": "my-company-1699..."
}
}
}
Domains
Create Domain
Add a new domain for sending emails. This will generate DNS records that need to be added to your domain's DNS settings.
Endpoint: POST /v1/domains
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"domain": "example.com"
}
Response: 201 Created
{
"success": true,
"data": {
"id": "clx3...",
"domain": "example.com",
"verificationStatus": "PENDING",
"verificationToken": "postchi-verify-a1b2c3...",
"dnsRecords": {
"verification": {
"type": "TXT",
"name": "example.com",
"value": "postchi-verify-a1b2c3..."
},
"spf": {
"type": "TXT",
"name": "example.com",
"value": "v=spf1 include:_spf.mail.postchi.io ~all"
},
"dkim": {
"type": "TXT",
"name": "default._domainkey.example.com",
"value": "v=DKIM1; k=rsa; p=MIIBIjANBg..."
},
"dmarc": {
"type": "TXT",
"name": "_dmarc.example.com",
"value": "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
}
}
}
}
List Domains
Get all domains for your organization.
Endpoint: GET /v1/domains
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": [
{
"id": "clx3...",
"domain": "example.com",
"verificationStatus": "VERIFIED",
"verificationToken": "postchi-verify-a1b2c3...",
"dnsRecords": { ... }
}
],
"count": 1
}
Get Domain
Get a specific domain by ID.
Endpoint: GET /v1/domains/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"id": "clx3...",
"domain": "example.com",
"verificationStatus": "PENDING",
"verificationToken": "postchi-verify-a1b2c3...",
"dnsRecords": { ... }
}
}
Verify Domain
Verify DNS records for a domain.
Endpoint: POST /v1/domains/:id/verify
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"verified": true,
"checks": {
"ownership": {
"verified": true,
"found": true,
"expected": "postchi-verify-a1b2c3...",
"actual": "postchi-verify-a1b2c3..."
},
"spf": {
"verified": true,
"found": true,
"expected": "v=spf1 record",
"actual": "v=spf1 include:_spf.mail.postchi.io ~all"
},
"dkim": {
"verified": true,
"found": true,
"expected": "v=DKIM1 record",
"actual": "v=DKIM1; k=rsa; p=MIIBIjANBg..."
},
"dmarc": {
"verified": true,
"found": true,
"expected": "v=DMARC1 record",
"actual": "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
},
"mx": {
"verified": true,
"found": true,
"expected": "MX records",
"actual": "mail.example.com (priority: 10)"
}
}
}
}
Delete Domain
Delete a domain.
Endpoint: DELETE /v1/domains/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"message": "Domain deleted successfully"
}
Error Responses
All endpoints may return error responses in the following format:
Response: 4xx or 5xx
{
"status": "error",
"message": "Error description"
}
Common error codes:
400- Bad Request (validation error)401- Unauthorized (missing or invalid token)403- Forbidden (insufficient permissions)404- Not Found409- Conflict (resource already exists)429- Too Many Requests (rate limit exceeded)500- Internal Server Error
Rate Limiting
- Auth endpoints: 5 requests per 15 minutes per email/IP
- API endpoints: 100 requests per minute per API key/token
cURL Examples
Complete Workflow
# 1. Register
curl -X POST http://localhost:3000/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test1234",
"name": "Test User",
"organizationName": "Test Org"
}'
# Save the accessToken from response
# 2. Login (alternative to register)
curl -X POST http://localhost:3000/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test1234"
}'
# 3. Get current user
curl http://localhost:3000/v1/auth/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# 4. Create a domain
curl -X POST http://localhost:3000/v1/domains \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{"domain": "example.com"}'
# 5. List domains
curl http://localhost:3000/v1/domains \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# 6. Verify domain DNS
curl -X POST http://localhost:3000/v1/domains/DOMAIN_ID/verify \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# 7. Delete domain
curl -X DELETE http://localhost:3000/v1/domains/DOMAIN_ID \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Messages
Send Email
Send a transactional email. The email will be queued for asynchronous delivery.
Endpoint: POST /v1/messages
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"from": {
"email": "sender@example.com",
"name": "Sender Name"
},
"to": [
{
"email": "recipient@example.com",
"name": "Recipient Name"
}
],
"subject": "Welcome to Postchi",
"html": "<h1>Hello!</h1><p>This is a test email.</p>",
"text": "Hello! This is a test email.",
"replyTo": {
"email": "support@example.com",
"name": "Support"
},
"tags": ["welcome", "onboarding"],
"metadata": {
"userId": "12345",
"campaignId": "welcome-2024"
}
}
Field Descriptions:
from.email(required): Must be from a verified domainto(required): Array of recipients (max 1000)cc(optional): Carbon copy recipientsbcc(optional): Blind carbon copy recipientssubject(required): Email subject (max 998 characters)html(optional): HTML email bodytext(optional): Plain text email body (either html or text required)replyTo(optional): Reply-to addressheaders(optional): Custom email headerstags(optional): Array of tags for categorization (max 10)metadata(optional): Custom metadata for tracking
Response: 202 Accepted
{
"success": true,
"data": {
"id": "msg_abc123",
"status": "queued",
"message": "1 email(s) queued for delivery"
}
}
Get Message Status
Get the status and delivery information for a specific message.
Endpoint: GET /v1/messages/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"id": "msg_abc123",
"from": "sender@example.com",
"to": "recipient@example.com",
"subject": "Welcome to Postchi",
"status": "SENT",
"sentAt": "2024-11-07T10:30:00.000Z",
"events": [
{
"type": "QUEUED",
"createdAt": "2024-11-07T10:29:00.000Z"
},
{
"type": "PROCESSING",
"createdAt": "2024-11-07T10:29:30.000Z"
},
{
"type": "SENT",
"createdAt": "2024-11-07T10:30:00.000Z",
"metadata": {
"smtpMessageId": "<abc123@example.com>"
}
}
]
}
}
List Messages
Get a list of messages with filtering and pagination.
Endpoint: GET /v1/messages
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Query Parameters:
status(optional): Filter by status (QUEUED, PROCESSING, SENT, FAILED, BOUNCED)domain(optional): Filter by domainfrom(optional): Filter by sender emailto(optional): Filter by recipient emaillimit(optional): Number of results (default: 50, max: 100)offset(optional): Pagination offset (default: 0)
Example:
GET /v1/messages?status=SENT&limit=20&offset=0
Response: 200 OK
{
"success": true,
"data": [
{
"id": "msg_abc123",
"from": "sender@example.com",
"to": "recipient@example.com",
"subject": "Welcome to Postchi",
"status": "SENT",
"createdAt": "2024-11-07T10:29:00.000Z",
"sentAt": "2024-11-07T10:30:00.000Z",
"domain": {
"domain": "example.com"
}
}
],
"pagination": {
"total": 150,
"limit": 20,
"offset": 0
}
}
cURL Examples
Sending an Email
# Send a simple email
curl -X POST http://localhost:3000/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"from": {
"email": "hello@example.com",
"name": "My App"
},
"to": [
{
"email": "user@test.com",
"name": "Test User"
}
],
"subject": "Welcome!",
"html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
"text": "Welcome! Thanks for signing up.",
"tags": ["welcome"],
"metadata": {
"userId": "123"
}
}'
# Get message status
curl http://localhost:3000/v1/messages/msg_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# List sent messages
curl "http://localhost:3000/v1/messages?status=SENT&limit=10" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Suppression List
The suppression list prevents emails from being sent to specific addresses. This is used for managing bounces, complaints, unsubscribes, and manual blocks.
Add Email to Suppression List
Add a single email address to the suppression list.
Endpoint: POST /v1/suppressions
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"email": "user@example.com",
"reason": "HARD_BOUNCE",
"reasonDetail": "Hard bounce - mailbox does not exist"
}
Field Descriptions:
email(required): Email address to suppressreason(required): Reason for suppression - one of:HARD_BOUNCE,SPAM_COMPLAINT,MANUAL,UNSUBSCRIBEreasonDetail(optional): Additional details about the suppression
Response: 201 Created
{
"success": true,
"data": {
"id": "sup_abc123",
"email": "user@example.com",
"reason": "HARD_BOUNCE",
"reasonDetail": "Hard bounce - mailbox does not exist",
"createdAt": "2024-11-07T10:30:00.000Z"
}
}
Bulk Add Emails to Suppression List
Add multiple email addresses to the suppression list at once.
Endpoint: POST /v1/suppressions/bulk
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"emails": [
"bounce1@example.com",
"bounce2@example.com",
"bounce3@example.com"
],
"reason": "HARD_BOUNCE"
}
Response: 201 Created
{
"success": true,
"data": {
"added": 3,
"skipped": 0,
"message": "Added 3 email(s) to suppression list"
}
}
List Suppression Entries
Get a list of suppressed email addresses with filtering and pagination.
Endpoint: GET /v1/suppressions
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Query Parameters:
reason(optional): Filter by reason (HARD_BOUNCE, SPAM_COMPLAINT, MANUAL, UNSUBSCRIBE)email(optional): Search for specific email (partial match)limit(optional): Number of results (default: 50, max: 100)offset(optional): Pagination offset (default: 0)
Example:
GET /v1/suppressions?reason=HARD_BOUNCE&limit=20&offset=0
Response: 200 OK
{
"success": true,
"data": [
{
"id": "sup_abc123",
"email": "user@example.com",
"reason": "HARD_BOUNCE",
"reasonDetail": "Hard bounce - mailbox does not exist",
"createdAt": "2024-11-07T10:30:00.000Z"
}
],
"pagination": {
"total": 45,
"limit": 20,
"offset": 0
}
}
Check if Email is Suppressed
Check whether a specific email address is in the suppression list.
Endpoint: GET /v1/suppressions/check/:email
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Example:
GET /v1/suppressions/check/user@example.com
Response: 200 OK
{
"success": true,
"data": {
"email": "user@example.com",
"suppressed": true
}
}
Remove Email from Suppression List
Remove an email address from the suppression list.
Endpoint: DELETE /v1/suppressions/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"success": true,
"message": "Email removed from suppression list"
}
}
cURL Examples
Managing Suppression List
# Add single email to suppression list
curl -X POST http://localhost:3000/v1/suppressions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"email": "bounce@example.com",
"reason": "HARD_BOUNCE",
"reasonDetail": "Hard bounce - mailbox does not exist"
}'
# Bulk add emails
curl -X POST http://localhost:3000/v1/suppressions/bulk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"emails": ["bounce1@example.com", "bounce2@example.com"],
"reason": "HARD_BOUNCE"
}'
# List suppressed emails
curl "http://localhost:3000/v1/suppressions?reason=HARD_BOUNCE&limit=10" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Check if email is suppressed
curl http://localhost:3000/v1/suppressions/check/user@example.com \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Remove email from suppression list
curl -X DELETE http://localhost:3000/v1/suppressions/sup_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your Postchi account. When an event happens, Postchi will send an HTTP POST request to the webhook URL you configure.
Webhook Events
The following events are available:
message.queued- Email has been queued for sendingmessage.sent- Email has been successfully sentmessage.failed- Email failed to sendmessage.bounced- Email bouncedmessage.delivered- Email was delivered to recipientmessage.opened- Recipient opened the emailmessage.clicked- Recipient clicked a link in the emailmessage.complained- Recipient marked email as spammessage.unsubscribed- Recipient unsubscribed
Webhook Security
All webhook requests include an X-Postchi-Signature header containing an HMAC SHA256 signature. Use your webhook's signing secret to verify the authenticity of webhook requests.
Create Webhook
Create a new webhook endpoint to receive event notifications.
Endpoint: POST /v1/webhooks
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"url": "https://your-app.com/webhooks/postchi",
"events": [
"message.sent",
"message.failed",
"message.bounced"
],
"enabled": true,
"description": "Production webhook for email events"
}
Field Descriptions:
url(required): HTTPS URL where webhook events will be sentevents(required): Array of events to subscribe toenabled(optional): Whether webhook is enabled (default: true)description(optional): Description of the webhook
Response: 201 Created
{
"success": true,
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/postchi",
"events": ["message.sent", "message.failed", "message.bounced"],
"enabled": true,
"description": "Production webhook for email events",
"signingSecret": "whsec_a1b2c3d4e5f6...",
"createdAt": "2024-11-07T10:30:00.000Z"
}
}
List Webhooks
Get all webhooks for your organization.
Endpoint: GET /v1/webhooks
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": [
{
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/postchi",
"events": ["message.sent", "message.failed"],
"enabled": true,
"description": "Production webhook",
"createdAt": "2024-11-07T10:30:00.000Z",
"lastDeliveryAt": "2024-11-07T11:00:00.000Z",
"successCount": 42,
"failureCount": 0
}
],
"count": 1
}
Get Webhook
Get a specific webhook by ID.
Endpoint: GET /v1/webhooks/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/postchi",
"events": ["message.sent", "message.failed"],
"enabled": true,
"signingSecret": "whsec_a1b2c3d4e5f6...",
"createdAt": "2024-11-07T10:30:00.000Z"
}
}
Update Webhook
Update webhook configuration.
Endpoint: PATCH /v1/webhooks/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Request Body:
{
"url": "https://your-app.com/new-webhook-url",
"events": ["message.sent", "message.bounced"],
"enabled": false
}
All fields are optional. Only include fields you want to update.
Response: 200 OK
{
"success": true,
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/new-webhook-url",
"events": ["message.sent", "message.bounced"],
"enabled": false,
"updatedAt": "2024-11-07T11:00:00.000Z"
}
}
Delete Webhook
Delete a webhook.
Endpoint: DELETE /v1/webhooks/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"success": true,
"message": "Webhook deleted successfully"
}
}
Regenerate Signing Secret
Regenerate the signing secret for a webhook. Useful if the secret has been compromised.
Endpoint: POST /v1/webhooks/:id/regenerate-secret
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"id": "wh_abc123",
"signingSecret": "whsec_new_secret_xyz...",
"updatedAt": "2024-11-07T11:00:00.000Z"
}
}
Get Webhook Delivery Logs
View delivery attempts for a specific webhook.
Endpoint: GET /v1/webhooks/:id/deliveries
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Query Parameters:
status(optional): Filter by status (successorfailed)limit(optional): Number of results (default: 50, max: 100)offset(optional): Pagination offset (default: 0)
Example:
GET /v1/webhooks/wh_abc123/deliveries?status=failed&limit=20
Response: 200 OK
{
"success": true,
"data": [
{
"id": "whd_xyz789",
"event": "message.sent",
"statusCode": 200,
"responseTime": 245,
"success": true,
"createdAt": "2024-11-07T11:00:00.000Z"
},
{
"id": "whd_abc456",
"event": "message.failed",
"statusCode": 500,
"responseBody": "Internal Server Error",
"responseTime": 1200,
"success": false,
"createdAt": "2024-11-07T10:55:00.000Z"
}
],
"pagination": {
"total": 42,
"limit": 20,
"offset": 0
}
}
Webhook Payload Format
When an event occurs, Postchi will send a POST request to your webhook URL with the following format:
{
"id": "evt_abc123",
"event": "message.sent",
"timestamp": "2024-11-07T11:00:00.000Z",
"data": {
"messageId": "msg_xyz789",
"rfcMessageId": "<1234567890.abc@example.com>",
"from": "sender@example.com",
"to": ["recipient@example.com"],
"subject": "Welcome to Postchi",
"status": "SENT",
"sentAt": "2024-11-07T11:00:00.000Z"
}
}
Verifying Webhook Signatures
To verify webhook authenticity, compare the HMAC signature:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
// In your webhook handler:
app.post('/webhooks/postchi', (req, res) => {
const signature = req.headers['x-postchi-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});
cURL Examples
Managing Webhooks
# Create webhook
curl -X POST http://localhost:3000/v1/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"url": "https://your-app.com/webhooks/postchi",
"events": ["message.sent", "message.failed"],
"description": "Production webhook"
}'
# List webhooks
curl http://localhost:3000/v1/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Get webhook
curl http://localhost:3000/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Update webhook
curl -X PATCH http://localhost:3000/v1/webhooks/wh_abc123 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-d '{
"enabled": false
}'
# Regenerate signing secret
curl -X POST http://localhost:3000/v1/webhooks/wh_abc123/regenerate-secret \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Get delivery logs
curl "http://localhost:3000/v1/webhooks/wh_abc123/deliveries?status=failed" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Delete webhook
curl -X DELETE http://localhost:3000/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Batch Sending
Batch sending allows you to send the same template to multiple recipients with personalized variables. Batches are processed asynchronously and provide real-time progress tracking.
Key Features
- Template-based only: All batch sends must use a pre-created template
- Idempotency required: All batch requests must include an
Idempotency-Keyheader - Max 100 recipients per batch: For larger sends, split into multiple batches
- Progress tracking: Real-time counters for queued, sent, delivered, and failed messages
- Automatic suppression checking: Suppressed emails are automatically skipped
Create Batch Send
Send a template to multiple recipients with personalized variables.
Endpoint: POST /v1/batches
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Idempotency-Key: your-unique-key-here
Content-Type: application/json
Request Body:
{
"name": "Monthly Newsletter - January 2025",
"description": "January newsletter for active users",
"templateId": "tmpl_abc123",
"recipients": [
{
"to": "user1@example.com",
"variables": {
"firstName": "John",
"accountType": "premium",
"expiryDate": "2025-02-01"
}
},
{
"to": "user2@example.com",
"variables": {
"firstName": "Jane",
"accountType": "free",
"expiryDate": "2025-01-15"
}
}
],
"from": {
"email": "newsletter@example.com",
"name": "Company Newsletter"
},
"replyTo": "support@example.com"
}
Field Descriptions:
name(optional): Human-readable name for the batchdescription(optional): Description of the batch purposetemplateId(required): ID of the template to userecipients(required): Array of recipients (min: 1, max: 100)to(required): Recipient email addressvariables(optional): Template variables for personalization
from(optional): Override template's from settingsemail(optional): Override from emailname(optional): Override from name
replyTo(optional): Override reply-to address
Idempotency-Key Requirements:
- Required header for all batch requests
- Must be between 1-255 characters
- Same key + same payload = returns cached response
- Same key + different payload = returns 409 Conflict error
- Keys expire after 24 hours
Response: 201 Created
{
"success": true,
"data": {
"id": "batch_xyz789",
"status": "PROCESSING",
"totalCount": 2,
"queuedCount": 2,
"sentCount": 0,
"deliveredCount": 0,
"failedCount": 0,
"progressPercentage": 0,
"createdAt": "2025-01-15T10:00:00.000Z",
"startedAt": "2025-01-15T10:00:00.000Z",
"messages": [
{
"id": "msg_abc123",
"to": "user1@example.com",
"status": "QUEUED"
},
{
"id": "msg_def456",
"to": "user2@example.com",
"status": "QUEUED"
}
]
}
}
Batch Statuses:
PENDING- Created but not startedPROCESSING- Currently sendingCOMPLETED- All messages processedFAILED- Batch failedCANCELLED- Manually cancelledPAUSED- Temporarily paused
Get Batch Progress
Get real-time progress for a batch send.
Endpoint: GET /v1/batches/:id
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Response: 200 OK
{
"success": true,
"data": {
"id": "batch_xyz789",
"status": "PROCESSING",
"totalCount": 100,
"queuedCount": 100,
"sentCount": 75,
"deliveredCount": 70,
"failedCount": 5,
"bouncedCount": 0,
"progressPercentage": 75.0,
"createdAt": "2025-01-15T10:00:00.000Z",
"startedAt": "2025-01-15T10:00:00.000Z",
"completedAt": null
}
}
Progress Fields:
totalCount: Total recipients in batchqueuedCount: Messages successfully queuedsentCount: Messages successfully sentdeliveredCount: Messages delivered to recipientfailedCount: Messages that failed to sendbouncedCount: Messages that bouncedprogressPercentage: Percentage complete (0-100)
List Batches
Get a paginated list of all batches for your organization.
Endpoint: GET /v1/batches
Headers:
Authorization: Bearer YOUR_ACCESS_TOKEN
Query Parameters:
limit(optional): Number of results (default: 20, max: 100)offset(optional): Pagination offset (default: 0)
Example:
GET /v1/batches?limit=10&offset=0
Response: 200 OK
{
"success": true,
"data": [
{
"id": "batch_xyz789",
"status": "COMPLETED",
"totalCount": 100,
"queuedCount": 100,
"sentCount": 95,
"deliveredCount": 92,
"failedCount": 5,
"bouncedCount": 3,
"progressPercentage": 100.0,
"createdAt": "2025-01-15T10:00:00.000Z",
"startedAt": "2025-01-15T10:00:00.000Z",
"completedAt": "2025-01-15T10:15:00.000Z"
}
],
"pagination": {
"total": 25,
"limit": 10,
"offset": 0
}
}
cURL Examples
Batch Sending
# Create a batch send with idempotency
curl -X POST http://localhost:3000/v1/batches \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: batch-newsletter-2025-01" \
-d '{
"name": "January Newsletter",
"templateId": "tmpl_abc123",
"recipients": [
{
"to": "user1@example.com",
"variables": {
"firstName": "John",
"accountType": "premium"
}
},
{
"to": "user2@example.com",
"variables": {
"firstName": "Jane",
"accountType": "free"
}
}
],
"from": {
"email": "newsletter@example.com",
"name": "Company Newsletter"
}
}'
# Get batch progress
curl http://localhost:3000/v1/batches/batch_xyz789 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# List all batches
curl "http://localhost:3000/v1/batches?limit=10&offset=0" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Retry with same idempotency key (returns cached response)
curl -X POST http://localhost:3000/v1/batches \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: batch-newsletter-2025-01" \
-d '{
"name": "January Newsletter",
"templateId": "tmpl_abc123",
"recipients": [
{
"to": "user1@example.com",
"variables": {"firstName": "John"}
}
]
}'
Best Practices for Batch Sending
-
Use unique idempotency keys - Include date, campaign ID, or UUID
- Good:
batch-welcome-2025-01-15-uuid123 - Bad:
batch-1
- Good:
-
Split large sends - Max 100 recipients per batch
# For 1000 recipients, create 10 batches
for i in {1..10}; do
curl -X POST .../batches \
-H "Idempotency-Key: campaign-2025-batch-$i" \
-d '{ recipients: [...] }'
done -
Monitor progress - Poll the progress endpoint
# Poll every 5 seconds
while true; do
curl .../batches/$BATCH_ID | jq '.data.progressPercentage'
sleep 5
done -
Handle errors gracefully - Check for 409 Conflict on retries
# Will return 409 if payload differs with same key
curl -X POST .../batches \
-H "Idempotency-Key: same-key" \
-d '{ different: "payload" }' -
Clean template variables - Remove recipients from suppression list first
- Check suppression list before sending
- Template variables are automatically substituted