Documentation

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:

1
You call
POST /api/v1/emails
2
We check
Suppressions, rate limits
3
We route
Primary provider, then fallbacks
4
You track
Webhooks, analytics, logs

Supported providers

SendGrid
AWS SES
Mailgun
Postmark
Resend
SMTP

Quickstart

Send your first email in under 2 minutes.

1

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..."
2

Install the SDK (optional)

npm install @courierx/node

Or use the REST API directly with fetch, axios, or any HTTP client.

3

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"
4

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

POST/api/v1/emails

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

FieldTypeDescription
from_emailstringSender email address (required)
from_namestringSender display name
to_emailstringRecipient email address (required)
to_namestringRecipient display name
reply_tostringReply-to email address
subjectstringEmail subject line (required)
html_bodystringHTML email body
text_bodystringPlain text email body
tagsstring[]Tags for categorization and filtering
metadataobjectCustom 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

GET/api/v1/emails/:id

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

GET/api/v1/emails

Filter and paginate through your sent emails:

GET /api/v1/emails?status=delivered&recipient=alice&page=1&per_page=50

Filters: 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

POST/api/v1/provider_connections

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:

  • sendgridapi_key
  • mailgunapi_keysmtp_host(domain)region(us / eu)
  • aws_sesapi_key(access key ID)secret(secret key)region
  • postmarkapi_key(server token)
  • resendapi_key
  • smtpsmtp_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

POST/api/v1/provider_connections/:id/verify

Test that your credentials are valid without sending an email:

// Response
{
  "verification": {
    "status": "verified",
    "message": "API key is valid"
  }
}

Other operations

GET/api/v1/provider_connections

List all connected providers, sorted by priority.

PATCH/api/v1/provider_connections/:id

Update settings or rotate credentials.

DELETE/api/v1/provider_connections/:id

Disconnect a provider.

Domains

Register and verify your sending domains to improve deliverability and prove ownership to email providers.

Add a domain

POST/api/v1/domains
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:

POST/api/v1/domains/:id/verify
// Response (on success)
{
  "status": "verified",
  "verified_at": "2026-03-22T10:30:00Z"
}

Other operations

GET/api/v1/domains

List all domains with verification status.

DELETE/api/v1/domains/:id

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

POST/api/v1/webhook_endpoints
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

EventDescription
deliveredEmail was accepted by the recipient's mail server
bouncedEmail bounced (hard or soft)
complainedRecipient marked the email as spam
openedRecipient opened the email
clickedRecipient clicked a link in the email
unsubscribedRecipient unsubscribed
failedEmail 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

GET/api/v1/webhook_endpoints

List all registered webhooks.

PATCH/api/v1/webhook_endpoints/:id

Update URL, events, or active status.

DELETE/api/v1/webhook_endpoints/:id

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

POST/api/v1/suppressions
{
  "email": "bad-address@example.com",
  "reason": "manual",
  "note": "Customer requested removal"
}

Reasons: bounce, complaint, unsubscribe, manual

Other operations

GET/api/v1/suppressions

List suppressions. Filter by reason.

DELETE/api/v1/suppressions/:id

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

POST/api/v1/auth/register

Create a new account. Returns a JWT token.

POST/api/v1/auth/login

Log in and get a JWT token (24h expiry).

GET/api/v1/auth/me

Get your account info.

Emails

POST/api/v1/emails

Send an email (202 Accepted).

GET/api/v1/emails

List emails with filters and pagination.

GET/api/v1/emails/:id

Get an email with its event timeline.

API Keys

POST/api/v1/api_keys

Create a key. The raw key is shown once.

GET/api/v1/api_keys

List all keys (prefix only).

PATCH/api/v1/api_keys/:id/revoke

Revoke a key.

DELETE/api/v1/api_keys/:id

Delete a key permanently.

Provider Connections

POST/api/v1/provider_connections

Connect a provider with credentials.

GET/api/v1/provider_connections

List connected providers.

PATCH/api/v1/provider_connections/:id

Update or rotate credentials.

POST/api/v1/provider_connections/:id/verify

Test credentials.

DELETE/api/v1/provider_connections/:id

Disconnect a provider.

Domains

POST/api/v1/domains

Register a sending domain.

GET/api/v1/domains

List domains.

POST/api/v1/domains/:id/verify

Verify via DNS.

DELETE/api/v1/domains/:id

Remove a domain.

Routing Rules

POST/api/v1/routing_rules

Create a routing rule (priority, weighted, round_robin, failover_only).

GET/api/v1/routing_rules

List rules.

PATCH/api/v1/routing_rules/:id

Update a rule.

DELETE/api/v1/routing_rules/:id

Delete a rule.

Analytics

GET/api/v1/dashboard/metrics

Delivery metrics (totals, rates, daily breakdown). Query: period (7d, 30d, 90d).

GET/api/v1/usage_stats

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/node
npm install @courierx/node
import { 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 once

cURL

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()