Email API for Developers: Send, Track and Manage Email
One REST API to send single emails or batches of up to 1,000, with templates, attachments, scheduling and HMAC-signed webhooks. If you already use our SMS API, the same key works for email.

Our email API is a REST interface at /api/v2/email. You POST to /send with a From address on your verified domain, recipients, a subject and HTML, text or a template ID. It replies with HTTP 202 and a message ID per recipient. Delivery, bounce and complaint events are posted to your webhook, signed with HMAC SHA-256.
Send your first email from code
POST JSON to /email/send with your API key. The reply is HTTP 202 with a message ID per recipient. Delivery events arrive at your webhook, signed with HMAC SHA-256.
# Send one transactional email from your verified domain curl -X POST "https://whitelabled.securesmsc.com/api/v2/email/send" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-KM2431-invoice" \ -d '{"from": {"email": "billing@kovaimart.in", "name": "Kovai Mart"}, "to": "priya@example.in", "subject": "Invoice KM/2431 for your order", "template_id": "tpl_12", "variables": {"name": "Priya", "order": "KM2431", "amount": "2,360"}, "category": "transactional"}'
$ch = curl_init("https://whitelabled.securesmsc.com/api/v2/email/send"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("SMS_API_KEY"), "Content-Type: application/json", "Idempotency-Key: order-KM2431-invoice", ], CURLOPT_POSTFIELDS => json_encode([ "from" => ["email" => "billing@kovaimart.in", "name" => "Kovai Mart"], "to" => "priya@example.in", "subject" => "Invoice KM/2431 for your order", "html" => "<p>Hi Priya, your GST invoice for order KM2431 is attached.</p>", "attachments" => [["filename" => "KM2431.pdf", "content_type" => "application/pdf", "content" => base64_encode(file_get_contents("KM2431.pdf"))]], "category" => "transactional", ]), ]); $res = json_decode(curl_exec($ch), true); echo $res["data"]["messages"][0]["message_id"] ?? $res["code"];
const res = await fetch("https://whitelabled.securesmsc.com/api/v2/email/send", { method: "POST", headers: { Authorization: `Bearer ${process.env.SMS_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "order-KM2431-invoice", }, body: JSON.stringify({ from: { email: "billing@kovaimart.in", name: "Kovai Mart" }, to: "priya@example.in", subject: "Invoice KM/2431 for your order", html: "<p>Hi Priya, your GST invoice for order KM2431 is attached.</p>", category: "transactional", }), }); const { data } = await res.json(); // HTTP 202 console.log(data.messages[0].message_id);
import hmac, hashlib, os, requests r = requests.post( "https://whitelabled.securesmsc.com/api/v2/email/send", headers={"Authorization": f"Bearer {os.environ['SMS_API_KEY']}", "Idempotency-Key": "order-KM2431-invoice"}, json={"from": "billing@kovaimart.in", "to": "priya@example.in", "subject": "Invoice KM/2431 for your order", "html": "<p>Hi Priya, your GST invoice for order KM2431 is attached.</p>", "category": "transactional"}, timeout=15, ) print(r.status_code, r.json()["data"]["messages"][0]["message_id"]) # Verify a webhook event (Flask-style request object) def valid(req, secret): ts = req.headers["X-SMSC-Timestamp"] mac = hmac.new(secret.encode(), f"{ts}.".encode() + req.get_data(), hashlib.sha256).hexdigest() return hmac.compare_digest("sha256=" + mac, req.headers["X-SMSC-Signature"])
Common error codes
| HTTP | Code | What it means |
|---|---|---|
400 | invalid_json | The body is not valid JSON. |
402 | insufficient_credits | Email credit balance is too low for this send. |
403 | email_not_enabled | Email is not switched on for the account yet. |
422 | from_domain_not_verified | The From domain is not verified. Publish its DNS records and verify it. |
422 | too_many_recipients | More than 50 recipients on /send. Use /batch. |
422 | all_recipients_suppressed | Every recipient is on your suppression list. Nothing was sent or charged. |
429 | rate_limited / daily_limit_reached | Slow down and retry later, or ask us to review your limits. |
503 | no_route_available | Temporary. Retry with back-off. |
Send or batch
Up to 50 recipients per send call, or 1,000 with personal variables per batch.
Idempotent requests
Pass an Idempotency-Key header so a retry never sends the same email twice.
Signed webhooks
Verify every event with the X-SMSC-Signature header and your webhook secret.
Manage it all by API
Domains, templates, suppressions, balance and message search have their own endpoints.
Authentication and your first request
Send your API key as a Bearer token in the Authorization header. It is the same key as the SMS API, created in the panel. Email is switched on per account, so ask us to enable it, then verify your sending domain before your first call.
POST a JSON body to /api/v2/email/send. The required fields are from, to and subject, plus html, text or template_id. Add category set to transactional or marketing, reply_to, cc and bcc, tags, metadata and custom headers as needed. A successful call returns HTTP 202 with a batch ID, the credits charged and a message ID for each recipient.
Templates, batches and attachments
Save templates through the API or the panel and send with template_id plus a variables object. Use double braces such as {{name}} for values that should be HTML-escaped, and triple braces for trusted HTML. For campaigns or digests, POST to /batch with up to 1,000 recipients, each with their own variables and metadata.
Attachments go in the attachments array as base64 with a filename and content type, up to 10 files of 2 MB each and 5 MB in total. Mark an image as inline with a content ID to show it in the body. To send later, set send_at with a timezone, up to 30 days ahead. You can cancel a scheduled message or batch.

Webhook events
Set one webhook URL for your account, or pass webhook_url on a single send. By default we post email.delivered, email.bounced, email.complained, email.failed and email.rejected. You can also subscribe to deferred, opened, clicked and unsubscribed events.
Each POST carries an X-SMSC-Signature header: an HMAC SHA-256 of the timestamp and the raw body, keyed with your webhook secret. Reject any event whose signature does not match or whose timestamp is old. If your server does not answer with a 2xx status, we retry after 10 seconds, 1 minute and 5 minutes, so make your handler idempotent on the event ID.
Errors and limits
Errors return a JSON body with a machine-readable code. The ones you will meet most are from_domain_not_verified, insufficient_credits, too_many_recipients, all_recipients_suppressed, rate_limited and daily_limit_reached. Treat 429 and 503 responses as temporary and retry with back-off. Treat 422 responses as permanent and fix the request.
New accounts start with a daily limit and a per-minute limit that rise as your sending history builds. Check your email credit balance with GET /api/v2/email/balance before a large batch.
Domains and suppressions by API
Add and verify sending domains with the /domains endpoints. The response lists the DNS records to publish, and POST /domains/{domain}/verify re-checks them on demand. Free-mail domains cannot be added.
The /suppressions endpoints list, add and remove suppressed addresses, so you can sync unsubscribes from your CRM. POST /validate checks a list of addresses before you send, at one credit per address.
How it works
Enable email
Ask us to switch email on for your account and agree a rate for your volume.
Verify a domain
Add it with POST /domains, publish the records, then call the verify endpoint.
Send a test
POST to /send with your key and check the message ID and webhook event.
Go live
Move templates to the API, add the idempotency key and watch your error codes.
Frequently asked questions
What is an email API?
An email API lets your software send email with an HTTPS request instead of connecting to a mail server. You send the recipient, subject and content as JSON, and the API returns a message ID. Delivery results come back as webhook events, so your app knows which emails arrived, bounced or were marked as spam.
Is the email API key the same as the SMS API key?
Yes. One API key works for the SMS and email APIs on the same account, sent as a Bearer token. Email has its own credit balance, which you can check with the balance endpoint. A key is locked to the server IP addresses allowed for it, so if email will go out from a different server, ask us to add that IP.
How do I verify webhook signatures?
Each webhook request has X-SMSC-Timestamp and X-SMSC-Signature headers. Compute an HMAC SHA-256 of the timestamp, a dot and the raw request body, using your webhook secret, and compare it with the signature value after sha256=. Reject requests that do not match. You can rotate the secret through the API.
Can I send email over SMTP instead?
Not at present. Sending is through the REST API only, which gives you message IDs, idempotency, scheduling and signed events that SMTP cannot return. If an older system can only send over SMTP, talk to us about the options before you plan the integration.
How many emails can one API call send?
A call to /send can address up to 50 recipients, counting cc and bcc. For larger sends, use /batch with up to 1,000 recipients, each with their own variables. Attachments are allowed only on smaller sends. New accounts also have a daily limit that rises over time.
What happens when an address bounces?
A hard bounce adds the address to your suppression list, and later sends to it are skipped and not charged. Repeated soft bounces suppress an address for a few days. You receive an email.bounced webhook with the bounce type, so you can update the customer record in your own system.
Get email enabled on your API key
Tell us your volume and sending domain. We switch email on, and your existing SMS key starts working for email.

