Ship email that always delivers.
One API. Every major provider. Automatic failover, BYOK routing, and suppressions built in — so a SendGrid outage never reaches your users.
Introduction
CourierX gives you a single API to send email through any provider — SendGrid, AWS SES, Mailgun, Postmark, Resend, or SMTP. If a provider goes down, your emails automatically route through the next one.
Why CourierX?
No vendor lock-in
Switch providers without changing your code. Add a backup provider in one API call.
Automatic failover
When SendGrid is down, your emails still get delivered through SES or Mailgun.
Bring your own keys
Use your own provider accounts and API keys. Your reputation, your deliverability.
Built-in suppressions
Bounces, complaints, and unsubscribes are handled automatically before they hurt your sender score.
How it works
You make one API call. CourierX handles the rest:
Supported providers
Quickstart
Send your first email in under 2 minutes.
Get your API key
Create an account and generate an API key from the dashboard, or via the API:
curl -X POST https://api.courierx.dev/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "My App",
"email": "you@example.com",
"password": "your-password",
"password_confirmation": "your-password"
}'Save the token from the response. Then create an API key:
curl -X POST https://api.courierx.dev/api/v1/api_keys \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "My first key"}'
# Save the raw_key — it's only shown once:
# "raw_key": "cxk_live_abc123..."
Install the SDK (optional)
npm install @courierx/nodeOr use the REST API directly with fetch, axios, or any HTTP client.
Send your first email
import { CourierX } from "@courierx/node"
const courierx = new CourierX({ apiKey: "cxk_live_abc123..." })
const { email } = await courierx.emails.send({
from: "hello@yourapp.com",
to: "user@example.com",
subject: "Hello from CourierX!",
html: "<h1>It works!</h1><p>Your first email via CourierX.</p>",
})
console.log(email.id, email.status) // "uuid", "queued"Connect a provider
Connect your SendGrid, SES, or any supported provider so CourierX knows where to deliver your emails:
curl -X POST https://api.courierx.dev/api/v1/provider_connections \
-H "Authorization: Bearer cxk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"provider": "sendgrid",
"display_name": "SendGrid Primary",
"priority": 1,
"api_key": "SG.your-sendgrid-key"
}'That's it. Emails you send will now route through SendGrid. Add a second provider to enable automatic failover.
Sending Emails
Everything you need to know about sending emails through the CourierX API.
Basic send
Send an email by providing a sender, recipient, subject, and body. The email is queued and delivered asynchronously.
{
"from_email": "hello@yourapp.com",
"from_name": "Your App",
"to_email": "user@example.com",
"to_name": "Alice",
"subject": "Welcome to Your App",
"html_body": "<h1>Welcome, Alice!</h1><p>Thanks for signing up.</p>",
"text_body": "Welcome, Alice! Thanks for signing up.",
"tags": ["welcome", "onboarding"]
}Response
Returns 202 Accepted with the queued email:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"from_email": "hello@yourapp.com",
"to_email": "user@example.com",
"subject": "Welcome to Your App",
"status": "queued",
"tags": ["welcome", "onboarding"],
"queued_at": "2026-03-22T10:30:00Z",
"created_at": "2026-03-22T10:30:00Z"
}Parameters
| Field | Type | Description |
|---|---|---|
| from_email | string | Sender email address (required) |
| from_name | string | Sender display name |
| to_email | string | Recipient email address (required) |
| to_name | string | Recipient display name |
| reply_to | string | Reply-to email address |
| subject | string | Email subject line (required) |
| html_body | string | HTML email body |
| text_body | string | Plain text email body |
| tags | string[] | Tags for categorization and filtering |
| metadata | object | Custom key-value metadata. Include idempotency_key for deduplication. |
Idempotency
To prevent duplicate sends, include an idempotency_key in your metadata. If the same key is sent within 24 hours, CourierX returns the original email instead of creating a new one.
{
"to_email": "user@example.com",
"from_email": "hello@yourapp.com",
"subject": "Order confirmation",
"html_body": "<p>Your order #1234 is confirmed.</p>",
"metadata": {
"idempotency_key": "order-confirmation-1234"
}
}Tracking email status
Retrieve an email with its full event timeline:
{
"id": "550e8400-...",
"status": "delivered",
"events": [
{ "event_type": "sent", "occurred_at": "2026-03-22T10:30:01Z", "provider": "sendgrid" },
{ "event_type": "delivered", "occurred_at": "2026-03-22T10:30:03Z", "provider": "sendgrid" },
{ "event_type": "opened", "occurred_at": "2026-03-22T11:15:00Z" }
]
}Possible statuses: queued sent delivered bounced failed suppressed
List emails
Filter and paginate through your sent emails:
GET /api/v1/emails?status=delivered&recipient=alice&page=1&per_page=50Filters: status, recipient (partial match), from / to (date range). Pagination info is returned in X-Total-Count, X-Page, X-Per-Page headers.
Providers
Connect your own email provider accounts. CourierX uses your credentials to send on your behalf — your reputation, your deliverability, zero lock-in.
Connect a provider
Pass your provider type and API credentials. CourierX encrypts them at rest and verifies they're valid:
curl -X POST https://api.courierx.dev/api/v1/provider_connections \
-H "Authorization: Bearer cxk_live_..." \
-H "Content-Type: application/json" \
-d '{
"provider": "sendgrid",
"display_name": "SendGrid Primary",
"priority": 1,
"api_key": "SG.your-sendgrid-api-key"
}'Provider credentials
Each provider requires different credentials:
- sendgrid
api_key - mailgun
api_keysmtp_host(domain)region(us / eu) - aws_ses
api_key(access key ID)secret(secret key)region - postmark
api_key(server token) - resend
api_key - smtp
smtp_hostsmtp_portapi_key(username)secret(password)
Failover
Add multiple providers with different priorities. If the primary provider fails with a transient error (timeout, rate limit, 5xx), CourierX automatically tries the next one:
// Primary — tried first
{ "provider": "sendgrid", "priority": 1, "api_key": "SG.xxx" }
// Fallback — used if SendGrid fails
{ "provider": "aws_ses", "priority": 2, "api_key": "AKIA...", "secret": "...", "region": "us-east-1" }
// Last resort
{ "provider": "mailgun", "priority": 3, "api_key": "key-xxx", "smtp_host": "mg.yourapp.com" }Permanent errors (invalid recipient, auth failure) stop immediately — they won't be retried on another provider.
Verify credentials
Test that your credentials are valid without sending an email:
// Response
{
"verification": {
"status": "verified",
"message": "API key is valid"
}
}Other operations
List all connected providers, sorted by priority.
Update settings or rotate credentials.
Disconnect a provider.
Domains
Register and verify your sending domains to improve deliverability and prove ownership to email providers.
Add a domain
curl -X POST https://api.courierx.dev/api/v1/domains \
-H "Authorization: Bearer cxk_live_..." \
-H "Content-Type: application/json" \
-d '{"domain": "mail.yourapp.com"}'
// Response
{
"id": "domain-uuid",
"domain": "mail.yourapp.com",
"status": "pending",
"verification_token": "dns_abc123..."
}Verify via DNS
Add a TXT record to your DNS with the verification token:
# DNS TXT record
Host: _courierx.mail.yourapp.com
Value: dns_abc123...Then trigger verification:
// Response (on success)
{
"status": "verified",
"verified_at": "2026-03-22T10:30:00Z"
}Other operations
List all domains with verification status.
Remove a domain.
Webhooks
Get notified when emails are delivered, bounced, opened, or clicked. CourierX sends events to your webhook URL in real time.
Register a webhook
curl -X POST https://api.courierx.dev/api/v1/webhook_endpoints \
-H "Authorization: Bearer cxk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/email",
"description": "Email delivery events",
"events": ["delivered", "bounced", "opened", "clicked"]
}'A signing secret is auto-generated. All payloads are signed with HMAC-SHA256 so you can verify they came from CourierX.
Event types
| Event | Description |
|---|---|
| delivered | Email was accepted by the recipient's mail server |
| bounced | Email bounced (hard or soft) |
| complained | Recipient marked the email as spam |
| opened | Recipient opened the email |
| clicked | Recipient clicked a link in the email |
| unsubscribed | Recipient unsubscribed |
| failed | Email failed to send after all provider attempts |
Leave events empty to receive all event types.
Webhook payload
{
"event": "delivered",
"email_id": "550e8400-...",
"recipient": "user@example.com",
"provider": "sendgrid",
"timestamp": "2026-03-22T10:30:03Z",
"metadata": {
"user_id": "usr_123"
}
}Other operations
List all registered webhooks.
Update URL, events, or active status.
Remove a webhook endpoint.
Suppressions
CourierX automatically blocks emails to addresses that have bounced, complained, or been manually suppressed — protecting your sender reputation.
How it works
When you send an email, CourierX checks the recipient against your suppression list. If suppressed, the send is blocked immediately and the email is marked as suppressed — no provider is ever contacted.
Suppressions are created automatically from bounce and complaint events, or you can add them manually.
Add a suppression
{
"email": "bad-address@example.com",
"reason": "manual",
"note": "Customer requested removal"
}Reasons: bounce, complaint, unsubscribe, manual
Other operations
List suppressions. Filter by reason.
Remove a suppression to allow sending to that address again.
API Reference
All endpoints at a glance. Authenticate every request with Authorization: Bearer cxk_live_...
Authentication
Create a new account. Returns a JWT token.
Log in and get a JWT token (24h expiry).
Get your account info.
Emails
Send an email (202 Accepted).
List emails with filters and pagination.
Get an email with its event timeline.
API Keys
Create a key. The raw key is shown once.
List all keys (prefix only).
Revoke a key.
Delete a key permanently.
Provider Connections
Connect a provider with credentials.
List connected providers.
Update or rotate credentials.
Test credentials.
Disconnect a provider.
Domains
Register a sending domain.
List domains.
Verify via DNS.
Remove a domain.
Routing Rules
Create a routing rule (priority, weighted, round_robin, failover_only).
List rules.
Update a rule.
Delete a rule.
Analytics
Delivery metrics (totals, rates, daily breakdown). Query: period (7d, 30d, 90d).
Daily usage stats by provider. Query: from, to.
Errors
All errors return a consistent JSON format:
// 422 Validation error
{ "errors": ["Email can't be blank", "Subject is required"] }
// 401 Unauthorized
{ "error": "Unauthorized" }
// 404 Not found
{ "error": "Not found" }
// 429 Rate limited
{ "error": "Rate limit exceeded" }Rate-limit info is in response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
SDKs
Official client libraries and code examples in popular languages.
Node.js / TypeScript
@courierx/nodenpm install @courierx/nodeimport { CourierX } from "@courierx/node"
const courierx = new CourierX({ apiKey: "cxk_live_..." })
// Send
const { email } = await courierx.emails.send({
from: "hello@yourapp.com",
to: "user@example.com",
subject: "Welcome!",
html: "<h1>Welcome</h1>",
})
// List
const emails = await courierx.emails.list({ status: "delivered" })
// Get with events
const detail = await courierx.emails.get("email-uuid")
// Domains
await courierx.domains.verify("domain-uuid")
// API keys
const key = await courierx.apiKeys.create({ name: "Staging" })
console.log(key.key) // shown oncecURL
curl -X POST https://api.courierx.dev/api/v1/emails \
-H "Authorization: Bearer cxk_live_..." \
-H "Content-Type: application/json" \
-d '{
"from_email": "hello@yourapp.com",
"to_email": "user@example.com",
"subject": "Hello from CourierX",
"html_body": "<h1>It works!</h1>"
}'Python
import requests
resp = requests.post(
"https://api.courierx.dev/api/v1/emails",
headers={"Authorization": "Bearer cxk_live_..."},
json={
"from_email": "hello@yourapp.com",
"to_email": "user@example.com",
"subject": "Hello from CourierX",
"html_body": "<h1>It works!</h1>",
},
)
print(resp.json()["id"], resp.json()["status"])fetch (JavaScript)
const res = await fetch("https://api.courierx.dev/api/v1/emails", {
method: "POST",
headers: {
Authorization: "Bearer cxk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
from_email: "hello@yourapp.com",
to_email: "user@example.com",
subject: "Hello from CourierX",
html_body: "<h1>It works!</h1>",
}),
})
const { id, status } = await res.json()