KaiMail Now Has an HTTP API for Sending Email
KaiMail's new HTTP Sending API lets you send email from your custom domain over HTTPS. Same pipeline as SMTP port 587, but without needing an SMTP library. Works from serverless functions, blocked networks, and anywhere JSON over HTTPS is easier.
I have to admit something. When we launched SMTP sending back in March, I thought that was the end of the conversation. Port 587, STARTTLS, DKIM-signed, done. Every email client on the planet speaks SMTP. What more could anyone want?
It turns out, quite a lot.
In the months since, we keep on seeing feedback that. "My hosting provider blocks outbound port 587." "I am calling this from a serverless function and SMTP handshakes time out." "I just want to POST some JSON and be done with it." Every time, I found myself saying: "I hear you. We are working on it."
That work is now live. KaiMail has an HTTP Sending API.
What It Actually Is
The HTTP API is an alternative front door to the same submission pipeline that handles SMTP on port 587. When you send via HTTP, your message goes through the exact same validation, DKIM signing, quota enforcement, and delivery logging. The only thing that changes is how you talk to us.
You POST JSON. We queue the message and return a tracking ID. That is it.
This matters because SMTP is not always the right tool. Some platforms — Netlify Functions, AWS Lambda in restrictive VPCs — simply do not give you outbound SMTP. Even platforms like DigitalOcean block ports, or sometimes the libraries are heavy, or the connection timeouts make the whole thing unreliable. HTTP is almost always allowed. If your function can call a webhook, it can send email.
How It Works
The API lives at https://kaimail.net/api/v1. It uses HTTP Basic Auth with the same credentials as SMTP: your KaiMail account email and your SMTP password. This was a deliberate choice. If you are already sending through port 587, you do not need new keys, new dashboards, or new documentation to memorise.
There are two endpoints for sending:
POST /api/v1/email/send — You provide from, to, subject, and either text or html. The server builds the MIME message for you, including Date and Message-ID. This is what you want for 90% of cases.
POST /api/v1/email/send-raw — You supply a complete RFC 822 message, base64-encoded, plus the envelope recipients. This is for when you need attachments, custom headers, Bcc, or full control over the message structure.
Both return 202 Accepted immediately with a tracking ID. The message is queued, not delivered, so your application does not hang waiting for SMTP round-trips.
Here is the simplest possible example:
curl -X POST https://kaimail.net/api/v1/email/send \
-u "[email protected]:YOUR_SMTP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"from": "Support <[email protected]>",
"to": ["[email protected]"],
"subject": "Welcome aboard",
"text": "Hello — thanks for signing up."
}'
And the same thing in Python:
import requests
resp = requests.post(
"https://kaimail.net/api/v1/email/send",
auth=("[email protected]", "YOUR_SMTP_PASSWORD"),
json={
"from": "Support <[email protected]>",
"to": ["[email protected]"],
"subject": "Welcome aboard",
"text": "Hello — thanks for signing up.",
},
timeout=30,
)
tracking_id = resp.json()["data"]["tracking_id"]
The response envelope is consistent whether you succeed or fail. Every response carries a request_id for support, and errors include a machine-readable code alongside the human message. Switch on the code, not the text.
Tracking What Happens After You Send
One thing SMTP does not give you easily is status visibility. You hand the message to the server and hope. The HTTP API fixes this.
GET /api/v1/email/<tracking_id> returns the full status: queued, sending, sent, or failed. If a worker hits a transient error, it retries automatically up to three times before giving up. If the message does fail, you get the reason in the response.
There is also a GET /api/v1/usage endpoint that returns your quota and remaining sends. Useful for pre-flight checks or a simple health monitor.
The Same Rules Apply
Sender requirements are identical to SMTP. You can only send from addresses you own: the domain must be an active custom domain on your account, and the full address must be a registered mailbox. Messages are DKIM-signed with your domain key during submission.
If you have already set up SMTP sending, the DNS records are already in place. Nothing extra to configure.
Error Handling That Helps
We spent more time on error design than I expected. The API returns specific codes so your integration can decide what to do:
invalid_credentials(401) — Wrong password. Use your SMTP password, not your dashboard login.sending_not_allowed(403) — Your plan does not include outbound sending. Upgrade to a paid plan.domain_not_owned(403) — You are trying to send from a domain not on your account.quota_exceeded(429) — You have hit your monthly limit. Check/usageand back off.
The full error reference and limit values are in the HTTP Sending API documentation.
When to Use SMTP vs HTTP
Use SMTP when you are sending from a desktop email client, a mailing list, or any situation where you already have an SMTP-capable application. It is the standard. It works everywhere that allows port 587.
Use HTTP when:
- Your platform blocks outbound SMTP (common in serverless and some managed hosting).
- You are sending from code and JSON is easier than MIME generation.
- You want programmatic access to send status and quota without parsing SMTP responses.
- You are already using our webhook API for inbound email and want the same request/response model for outbound.
Availability
HTTP sending is available on all paid plans that include SMTP sending: Plus, Pro, and Business. It shares the same quota pool, so there are no separate limits to track. If you are already on a paid plan, the API is live in your account now.
Basic plan users can upgrade to get access. The upgrade path is the same as for SMTP: pick a paid plan, grab your SMTP password, and start sending.
What Is Next
This is the API we wished we had when we were building integrations ourselves. The documentation is live, the endpoints are stable, and we are using it in production for our own services. If you hit something unexpected, the request_id in every response makes it easy for us to trace exactly what happened.
Give it a try. Start with a cURL command, wrap it in your application, and let us know how it goes.
The full reference is at our HTTP API docs.
Iqbal Abdullah