BoomSauce API

Programmatic email infrastructure. Same pipeline as the dashboard, same rate limits, same compliance guarantees.

Contents

Base URL: https://app.boomsauce.com/api/v1. JSON only. OpenAPI 3.1 spec at /api/v1/openapi.json.

Authentication

Create keys at Account → API Keys. Shown once; we store a SHA-256 hash only. Live keys send real email and debit your wallet; test keys (bs_test_) validate and render but skip SMTP + wallet.

httpAuthorization: Bearer bs_live_abcd1234...

Rate limits + per-mailbox cap

300 req/min per tenant across all keys + endpoints. Every response carries X-RateLimit-Limit/Remaining/Reset. 429 responses include retry_after_seconds.

Per-mailbox daily cap: product policy max 300/day, default 75/day. API + UI + warmup all share one counter per mailbox. Cap-exceeded sends fail with 429 mailbox_cap_reached. Set mailbox_rotation: true to rotate automatically.

Idempotency

Include Idempotency-Key on any POST to make retries safe. We cache the response 24h keyed on (api_key, idempotency_key).

Sends

POST/api/v1/sends

Send one email. Applies merge tags, spintax, link rewriting, open pixel, and the CAN-SPAM footer.

curlcurl -X POST https://app.boomsauce.com/api/v1/sends \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: campaign-42-contact-8821" \
  -d '{
    "from_brand_id": 7,
    "to": { "email": "alex@example.com", "first_name": "Alex" },
    "subject": "Hi {{first_name}}",
    "html": "<p>Hey {{first_name}}, ...</p>",
    "spintax": true,
    "mailbox_rotation": true
  }'
POST/api/v1/sends/batch

Up to 500 recipients per request. The endpoint validates every recipient, reserves an mt_sends shell row for the accepted ones, and returns immediately. Each accepted send is dispatched asynchronously; poll /sends/:id or /sends?since=... for status.

curlcurl -X POST https://app.boomsauce.com/api/v1/sends/batch \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from_brand_id": 7,
    "subject": "Quick intro for {{first_name}}",
    "html": "<p>Hey {{first_name}}...</p>",
    "mailbox_rotation": true,
    "recipients": [
      { "email": "a@ex.com", "first_name": "A" },
      { "email": "b@ex.com", "first_name": "B" }
    ]
  }'
json{
  "batch_id": "f5b2...",
  "accepted": [
    { "index": 0, "send_id": 914221, "email": "a@ex.com" },
    { "index": 1, "send_id": 914222, "email": "b@ex.com" }
  ],
  "rejected": [],
  "counts": { "accepted": 2, "rejected": 0, "total": 2 },
  "mode": "live",
  "queued": true
}
GET/api/v1/sends/:id

Lookup one send. Scoped by tenant.

GET/api/v1/sends

List sends with filters: since, status, brand_id, contact_email. Cursor pagination with cursor + limit (max 200).

Replies

GET/api/v1/replies

Poll inbound replies. Excludes warmup and unsolicited mail by default. Filter by classification or pass include_warmup=true.

curlcurl "https://app.boomsauce.com/api/v1/replies?since=2026-04-20T00:00:00Z&classification=interested" \
  -H "Authorization: Bearer bs_live_..."
POST/api/v1/replies

Reply on a thread. We set In-Reply-To + References headers from the prior message.

curlcurl -X POST https://app.boomsauce.com/api/v1/replies \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "in_reply_to_reply_id": 38211,
    "html": "<p>Thanks, {{first_name}}. Tuesday 10am work?</p>"
  }'

Contacts

GET/api/v1/contacts

Search, filter by status, cursor paginate.

POST/api/v1/contacts

Create or upsert (merges non-null fields on conflict). Returns 201 on new, 200 on upsert.

curlcurl -X POST https://app.boomsauce.com/api/v1/contacts \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email":"alex@acme.com","first_name":"Alex","company":"Acme" }'
GET/api/v1/contacts/:id

Lookup by numeric id or by email (e.g. /contacts/alex@acme.com).

PATCH/api/v1/contacts/:id

Partial update. metadata is shallow-merged. Send an explicit null to clear a field.

DELETE/api/v1/contacts/:id

Soft-delete (sets status="deleted"). We never hard-delete contact records.

Webhooks

Register a URL + event list. We POST a signed JSON payload to your URL whenever a matching event fires. Retry schedule: 5 attempts with exponential backoff (4s → 8 → 16 → 32 → 64s). After 10 consecutive failures we auto-pause the subscription.

POST/api/v1/webhooks
curlcurl -X POST https://app.boomsauce.com/api/v1/webhooks \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-service.example.com/boomsauce",
    "label": "prod replies",
    "events": ["email.replied","email.bounced","email.opened"]
  }'

Response includes a secret (whsec_...) returned ONCE. Store it safely.

Events you can subscribe to

fieldsemail.sent         email.bounced        sequence.step_sent
email.delivered    email.complained     sequence.completed
email.opened       email.replied        sequence.stopped
email.clicked                           campaign.paused
                                        campaign.completed
contact.created    contact.updated      domain.added

Signature verification

Each delivery has BoomSauce-Signature: t=<unix_ts>,v1=<hex_hmac>. The signed payload is "{timestamp}.{rawBody}". Use your stored secret to recompute the HMAC-SHA-256 and compare in constant time.

nodeimport crypto from 'crypto';
function verify(secret, rawBody, header) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const t = parseInt(parts.t, 10);
  const v1 = parts.v1;
  if (Math.abs(Date.now()/1000 - t) > 300) return false;  // 5min tolerance
  const expected = crypto.createHmac('sha256', secret)
    .update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(v1,'hex'), Buffer.from(expected,'hex'));
}
GET/api/v1/webhooks

List your subscriptions.

PATCH/api/v1/webhooks/:id

Update events list, label, or pause state. URL + secret are immutable — rotate by creating a new webhook.

DELETE/api/v1/webhooks/:id

Revoke. The worker stops dispatching immediately.

GET/api/v1/webhooks/:id/deliveries

Debug view: last N delivery attempts with HTTP status, response body preview, duration, and error message if any.

POST/api/v1/webhooks/:id/test

Enqueues a synthetic webhook.test delivery so you can verify your endpoint + signature verification without waiting for a real event.

Warmup

Enroll a mailbox in the CheddarInbox peer-to-peer warmup network. Each mailbox is tracked, billed, paused, and resumed independently.

GET/api/v1/warmup

List warmup status across all your mailboxes.

POST/api/v1/warmup

Enable warmup on a mailbox.

curlcurl -X POST https://app.boomsauce.com/api/v1/warmup \
  -H "Authorization: Bearer bs_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "mailbox_id": 42, "daily_volume": 30 }'
GET/api/v1/warmup/:mailbox_id

Full status including upstream CheddarInbox metrics.

PATCH/api/v1/warmup/:mailbox_id

Change daily_volume (5..1000) or status (active|paused|cancelled).

DELETE/api/v1/warmup/:mailbox_id

Cancel warmup entirely.

Merge tags + spintax

Merge tags use {{name}}. Built-ins: {{first_name}}, {{last_name}}, {{email}}, {{company}}, {{unsubscribe_url}}. Anything under metadata resolves as {{key}}.

Spintax uses {option1|option2|option3}. One option is chosen per send so every recipient gets a slightly different copy. Disable per-request with spintax: false.

TypeScript SDK

Single-file drop-in at src/sdk/boomsauce.ts in our repo. Grab the raw file and import. No npm package yet — intentional; one file avoids the versioning + vuln-tracking overhead.

typescriptimport BoomSauce from './boomsauce';

const bs = new BoomSauce(process.env.BS_API_KEY!);

const { send_id } = await bs.sends.create({
  from_brand_id: 7,
  to: { email: 'alex@example.com', first_name: 'Alex' },
  subject: 'Hi {{first_name}}',
  html: '<p>Hi {{first_name}}.</p>',
});

const status = await bs.sends.get(send_id);
const replies = await bs.replies.list({ since: '2026-04-20T00:00:00Z' });

// Webhook verification in your handler:
await BoomSauce.verifySignature(SECRET, rawBody, req.header('BoomSauce-Signature'));

Python + other languages: use the OpenAPI spec with openapi-generator until we ship an official Python SDK.

Errors

json{ "error": "mailbox_cap_reached",
  "message": "Mailbox sender@acme.com has hit today's cap (75/75)." }
fields401  unauthorized                bad/missing/revoked key
400  canspam_address_missing     set address in Account → Profile
400  invalid_body                malformed JSON or missing fields
400  invalid_events              unknown event name(s) on webhook
404  brand_not_found             from_brand_id not yours
404  send_not_found              reply target doesn't exist
409  contact_unsubscribed        recipient opted out
429  rate_limit_exceeded         300/min per tenant
429  mailbox_cap_reached         mailbox at daily cap
429  no_mailbox_available        all rotation mailboxes at cap
429  webhook_limit_reached       20 active webhooks per tenant
502  send_failed                 SMTP provider rejected
502  cheddar_error               upstream warmup API failure
503  queue_unavailable           Redis down — rare

Pricing

$1.50 per 1,000 sends on both single and batch endpoints. Replies bill the same. Test-mode sends are free. Warmup billing (per-mailbox CPM against CheddarInbox) continues to accrue against your wallet whether you toggle it via API or UI.

Support

Bug reports, feature requests, or rate-limit bumps: /support.