Overview
SwayamMail exposes a developer API for sending email. Use API keys in the x-api-key header and choose between sandbox and production keys depending on your testing needs.
Auth
API keys via x-api-key header.
Verification
Production keys require a verified domain and approved From address.
Sandbox
Test keys mark jobs delivered without touching SES.
Quickstart
Step 1
Create a sending domain and publish DNS records.
Step 2
Approve From addresses under the verified domain.
Step 3
Issue API keys for sandbox and production.
Step 4
Send your first email using POST /v1/send.
Authentication
Developer API
Authenticate by sending your API key in the header.
x-api-key: sk_test_...
API keys
Create up to 5 active keys per tenant in the dashboard. Keys are shown once on creation and stored as hashes.
Create key
Go to the Dashboard → API keys to create a new key.
List keys
Manage keys inside the dashboard UI.
Revoke key
Use the dashboard to revoke a compromised key.
Preview
Store only the preview in logs.
Send emails
Send to direct emails, contact IDs, or list IDs. You must provide at least one recipient array.
POST /v1/send
{
"from": "hello@yourdomain.in",
"subject": "Welcome to SwayamMail",
"html": "<h1>Hello there</h1>",
"toEmails": ["customer@example.com"],
"contactIds": [],
"listIds": []
}Response
{
"accepted": 1,
"jobIds": ["f7d..."],
"publishedJobs": []
}Headers
x-api-key: sk_prod_...
Transactional email API
For reports, invoices and other one-to-one mail sent from your own app or dashboard. You send ready-made HTML; no unsubscribe footer is added and links are not rewritten. Opens are tracked if your plan includes it (set track_opens: false to skip). Files are sent as links and shown as attachment cards at the bottom of the email. Send an Idempotency-Keyheader so a retried request never sends twice.
POST /v1/emails
x-api-key: sk_prod_...
Idempotency-Key: report-acme-2026-10
{
"from": "Agency Reports <reports@yourdomain.in>",
"to": ["client@acme.com"],
"cc": ["pm@yourdomain.in"],
"reply_to": "pm@yourdomain.in",
"subject": "Your October report",
"html": "<h1>October at a glance</h1>...",
"text": "October at a glance...",
"attachments": [
{ "filename": "Invoice-Oct.pdf", "url": "https://files.yourdomain.in/inv-oct.pdf" }
],
"tags": { "client": "acme", "kind": "monthly-report" }
}Response (202)
{
"id": "f7d1...",
"status": "queued",
"to": ["client@acme.com"],
"subject": "Your October report",
"tags": { "client": "acme" }
}Other endpoints
POST /v1/emails/batch
{ "emails": [ {...}, {...} ] } // up to 100
GET /v1/emails/:id
// status, timestamps and event historyLimits: 50 recipients across to/cc/bcc, 200 KB of HTML, 10 attachment links, 10 tags. Reusing an Idempotency-Key with the same body returns the original email (HTTP 200); with a different body it returns 409. In a batch, item i uses key <key>:i.
Email OTP
Send and check one-time login or signup codes without building any of it yourself. We generate the code, email it from a fast lane that never waits behind campaigns, and check it for you. Codes are stored only as hashes, work once, expire after 10 minutes and lock after 5 wrong tries (all adjustable in OTP settings). The email is plain, with the code in the subject and no tracking.
POST /v1/otp/send
x-api-key: sk_prod_...
{
"email": "user@example.com",
"ip": "203.0.113.7", // your user's IP: enables per-IP limits
"captcha_token": "0.Xa...", // when CAPTCHA is on in OTP settings
"purpose": "login" // optional label
}
202 { "id": "8c1e...", "email": "user@example.com", "expires_at": "2026-10-30T10:10:00Z" }POST /v1/otp/verify
x-api-key: sk_prod_...
{ "email": "user@example.com", "code": "482913", "id": "8c1e..." } // id is optional
200 { "valid": true, "id": "8c1e...", "email": "user@example.com" }
200 { "valid": false, "reason": "invalid", "attempts_remaining": 3 }
// reason: invalid | expired | too_many_attempts | already_used | superseded | not_foundSender: codes come from the address set as from_email in OTP settings (use a verified subdomain such as otp.yourbrand.com), or by default from noreply@yourcompany.<our OTP domain>. Limits: one code per address per minute and 5 per hour, 20 per IP per hour, and your account's per-minute rate. Asking for a new code cancels the previous one. Sandbox keys send nothing and return the code in the response so you can test your flow. If too many of your codes bounce, OTP sending pauses until support reviews the account, so protect public forms with CAPTCHA (Cloudflare Turnstile or hCaptcha).
Webhooks
Set one URL per account under Webhooks in the dashboard. Events: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked, email.failed. Respond with any 2xx within 10 seconds. Other responses are retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h.
POST https://your-app.com/webhooks/swayam
Swayam-Event: email.delivered
Swayam-Delivery-Id: 0b6e...
Swayam-Signature: t=1761815000,v1=5f2c...
{
"id": "0b6e...",
"type": "email.delivered",
"created_at": "2026-10-30T09:30:00Z",
"data": {
"email_id": "f7d1...",
"from": "reports@yourdomain.in",
"to": ["client@acme.com"],
"subject": "Your October report",
"email_type": "transactional",
"tags": { "client": "acme" },
"details": { "email": "client@acme.com" }
}
}Verify the signature before trusting a request (Node.js):
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET)
.update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));Delivery status
Check a single send by job ID. Responses omit HTML for privacy.
GET /v1/sends/:jobId
Response
{
"jobId": "uuid",
"status": "delivered",
"to": "user@example.com",
"from": "hello@yourdomain.in",
"subject": "Welcome",
"createdAt": "2026-04-29T13:00:00Z",
"sentAt": "2026-04-29T13:00:02Z",
"failedAt": null
}List sends
Fetch recent sends with filters. Supports status, to, from, subject, startDate, endDate, page, and limit.
GET /v1/sends?status=delivered&limit=20&page=1
Response
{
"page": 1,
"limit": 20,
"total": 120,
"items": [
{
"jobId": "uuid",
"status": "queued",
"to": "user@example.com",
"from": "hello@yourdomain.in",
"subject": "Welcome",
"createdAt": "2026-04-29T13:00:00Z",
"sentAt": null,
"failedAt": null
}
]
}Sandbox behavior
sk_test_
Jobs are marked delivered immediately. No SQS, no SES.
sk_prod_
Jobs are queued and delivered via SES.
Errors
Errors return JSON with an error field and optional details.
{
"error": "Invalid payload",
"issues": {
"fieldErrors": {
"from": ["Invalid email"]
}
}
}Rate limits
Beta limits are generous. If you receive 429, reduce send throughput or reach out for a higher allowance.
Changelog
April 2026
Added API keys with sandbox mode and production prefixes.
March 2026
Domain verification flow and sending addresses released.