Developers
API reference
Base URL https://transmitly.io/v1. Authenticate with a bearer API key. All mutating endpoints are tenant-scoped and audited.
Quickstart
- 1. Create a workspace and verify a sending domain.
- 2. Publish the ownership TXT, SPF, DKIM and DMARC records shown in the dashboard.
- 3. Click verify.
ACTIVEmeans SPF and DKIM are aligned. - 4. Create an API key with the
sendscope. - 5. POST a message and watch delivery events in the dashboard.
curl -X POST https://transmitly.io/v1/emails \
-H "Authorization: Bearer $TRANSMITLY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: welcome-8f2c" \
-d '{
"from": "Notify <[email protected]>",
"to": ["[email protected]"],
"subject": "Welcome to Acme",
"html": "<p>Your workspace is ready.</p>",
"stream": "transactional",
"tags": {"flow": "onboarding"}
}'POST /v1/emails
Accepts a message and returns 202 only after the message, per-recipient deliveries, idempotency key and outbox event commit in one transaction.
| Field | Type | Notes |
|---|---|---|
| from | string | Must be on a verified domain in your workspace |
| to | string | string[] | Max 50 recipients per message |
| subject | string | Required, ≤ 500 characters |
| html / text | string | At least one required; html ≤ 1 MiB |
| stream | string | transactional (default) or marketing |
| tags | object | String key/value pairs, echoed in events |
| headers | object | Additional headers; Message-ID is generated |
{"id": "msg_2f9c…", "status": "accepted", "recipients": 1}Fetch state with GET /v1/emails/{id} orGET /v1/messages/{id}/deliveries.
Idempotency
Send an Idempotency-Key header with every message. Replaying the same key and identical body returns the original message id with"idempotent": true. Reusing a key with a different body returns 409 idempotency_conflict.
SMTP cannot guarantee exactly-once delivery. We reconcile uncertain submit outcomes before retry, but ambiguous network failures may still produce duplicates. Design receivers to be idempotent.
Domain & DNS
Each domain gets a 2048-bit RSA DKIM key pair and a verification token. Publish:
_transmitly.mail.acme.com TXT "transmitly-verify=<token>"
mail.acme.com TXT "v=spf1 include:spf.transmitly.io ~all"
transmitly._domainkey.mail.acme.com TXT "v=DKIM1; k=rsa; p=<public key>"
_dmarc.mail.acme.com TXT "v=DMARC1; p=none; rua=mailto:[email protected]"
mail.acme.com MX 10 inbound.transmitly.ioStatus transitions: PENDING → VERIFIED → ACTIVE → DEGRADED → SUSPENDED. Ownership loss on an active domain degrades it and pauses sending pending review. Private DKIM keys never leave the control plane and are never logged or sent to a model.
Webhooks
Events: email.accepted,email.deferred,email.delivered_to_mx,email.bounced,email.complained,email.suppressed,domain.degraded,autopilot.action.
const [ts, sig] = req.headers["transmitly-signature"]
.split(",").map(v => v.split("=")[1]);
const expected = crypto.createHmac("sha256", secret)
.update(ts + "." + rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
return res.status(400).end();
if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return res.status(400).end();Retries use exponential backoff; after 8 attempts the delivery is dead-lettered and visible in the dashboard. Event ids are stable across retries — deduplicate on id. Delivery order is not guaranteed.
Suppressions & unsubscribe
GET/POST /v1/suppressions manages tenant-level entries. Hard bounces and complaints are added automatically. Marketing messages includeList-Unsubscribe and one-clickList-Unsubscribe-Post headers; one-click endpoints posts to /v1/marketing/unsubscribe?token=….
Suppression is enforced at intake and re-checked immediately before submission. No autopilot action can override it.
Limits & retries
- • 50 recipients per message; 1 MiB HTML body.
- • 50,000 messages/month on the pilot plan, hard limit.
- • 4xx/5xx classified by enhanced status; hard bounces are never retried.
- • Exponential backoff with jitter, capped at 45 minutes and 6 attempts.
- • Per-tenant token buckets and per-provider budgets prevent monopolisation.
Errors
Errors use a stable envelope. code is machine-readable;message is safe to surface to operators.
{
"error": {
"code": "domain_not_verified",
"message": "sender domain is not verified; complete DNS setup first",
"details": { "status": "PENDING" }
}
}401 invalid_api_key
Key missing, revoked or malformed
403 domain_not_owned
From domain is not in this workspace
403 domain_not_verified
DNS verification incomplete
409 idempotency_conflict
Key reused with a different body
429 quota_exceeded
Monthly plan limit reached
Acceptable use
Permission-based sending only. Purchased, rented or scraped lists are prohibited, as is any attempt to evade mailbox-provider enforcement, forge authentication or route rejected traffic through alternative pools. Marketing streams require documented consent provenance and functioning unsubscribe.
We verify domain ownership, review use cases during onboarding, enforce graduated quotas, and suspend on complaint or abuse escalation. Appeals are reviewed by a human with the underlying evidence preserved.
Need a hand?
Deliverability and integration questions are answered by engineers, not a bot.
[email protected]