Starter
For your first email program.
$192 billed yearly.
20,000 emails / month
Choose Starter- 5 brands · 3 seats
- 5 custom sending domains
- 500 email verifications/month
- $1.50 / 1,000 extra emails
The response tells you the message was accepted. Delivery is decided later, somewhere else, and in an integration an agent wrote there is nobody rostered to go and look.
A transactional email API is an HTTP endpoint your application calls to send one message to one person because something just happened: an account that needs confirming, a password that needs resetting, an order that needs a receipt. You post a recipient, a subject and a body, and you get an identifier back. That response means the message was accepted for sending, not delivered. On Nitrosend the Free plan includes 5,000 emails up front, then 500 a month, and a new account starts at 100 recipients per rolling 24 hours, rising to 5,000 with sender standing. The same call works from Node, curl or Python.
What changed is who writes the call. A developer used to read the docs, write the integration, ship it, and come back a week later to see whether it had worked. Increasingly the integration is written inside Claude, Codex, Cursor or ChatGPT, in one session, from a sentence describing what should happen. The code that comes out is usually fine. The problem is that the session ends.
A transactional email API cannot tell you whether the email arrived. It tells you it took the message. Those are two different facts separated by an unbounded amount of time, and the gap between them used to be closed by a person who eventually went and looked at a log. Agent-written integrations quietly remove that person.
Nitrosend's documentation for the send endpoint says it plainly. POST /v1/my/messages "queues a single transactional email or SMS and returns the message resource. Delivery happens through Nitrosend's normal background sender, so the response means 'accepted for sending', not 'delivered'." (Nitrosend REST API docs.)
Then look at the shape of what comes back. The send response carries { id, status, accepted_at }. There is no delivered_at on it, because at the moment that response is written nobody knows. delivered is a value you filter for afterwards, on GET /v1/my/messages?status=delivered, which is a separate call somebody has to decide to make.
This is not a Nitrosend quirk. It is what the protocols say. HTTP defines 202 Accepted as meaning "the request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place." Then it adds the sentence that belongs on a wall in every team shipping email: "There is no facility in HTTP for re-sending a status code from an asynchronous operation." (RFC 9110, section 15.3.3.)
The real answer travels on a different channel, and it always has. Email's standard way of reporting how a message actually went is a Delivery Status Notification, a separate machine-readable message whose stated job is to "report the result of an attempt to deliver a message to one or more recipients" (RFC 3464). Separate message. Later. Different direction of travel. No API design changes that.
So every transactional email API that returns quickly, which is all of them, hands you half an answer by construction. Feature tables will not tell you which one to pick, because on this they are identical. The question that actually separates them is what happens to the other half. For how providers compare on everything else, see the email API service reviews.
An agent writing a transactional integration differs from a developer in a few concrete ways. It works in one session and then stops. It retries a call by itself when a request times out. It writes the API key into the code or the environment as it goes. And it treats an accepted response as done, because nothing in the response says otherwise.
In the dashboard era the other half had a reader. Not a diligent one, but a real one. Something felt off, or a customer wrote in, and a human opened the activity log and scrolled. That habit was never in anyone’s runbook, and it was load bearing.
An integration written by an agent in a single session has no such person. The agent wrote the call, the call returned a success, the session closed. Nothing in that sequence rosters anybody to come back in three weeks and confirm that receipts are still going out. The failure that follows is silence: an integration that stops sending raises no error anyone sees, so the useful alert describes an absence, along the lines of these transactional emails have not sent for a while.
Two checks follow for anyone running an integration an agent wrote.
The prompt, in the user’s words: Send a receipt email from our checkout service when an order is paid, and make sure we know if it arrives.
Before. Unguided, an agent writes the Node SDK call below, runs it once, sees an id come back and reports the job done. Nothing checked the sending domain, nobody saw the receipt render, and the call carries no idempotency key, so the agent’s own retry after a timeout can send the receipt twice.
import { Nitrosend } from '@nitrosend/sdk';
const ns = new Nitrosend({ apiKey: process.env.NITROSEND_API_KEY });
await ns.messages.send({
channel: 'email',
from: '[email protected]',
to: order.customerEmail,
subject: `Your receipt for order ${order.id}`,
html: renderReceipt(order),
}, `receipt-${order.id}`);After. With the Nitrosend MCP server connected, the same request runs as a gated sequence, one call per step.
nitro_get_status gets the current account context, sender identity and readiness, so the agent knows whether the account can send at all.nitro_manage_domains verifies or diagnoses the customer sending domain the receipt will come from.nitro_review_delivery runs a read-only review of the receipt template, with validation, spam analysis, preflight and previews. It never approves or sends.nitro_send_test_message sends one real test of that template to the team. It never sends to an audience.nitro_send_message sends the live transactional email for the paid order, with an idempotency_key taken from the order id.Over REST, the live send with the key looks like this.
curl -X POST https://api.nitrosend.com/v1/my/messages \
-H "Authorization: Bearer $NITROSEND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1042" \
-d '{
"from": "[email protected]",
"to": "[email protected]",
"subject": "Your receipt for order 1042",
"html": "<p>Thanks for your order.</p>"
}'The retry is where the key earns its place. If the live send times out, the agent cannot tell whether the first attempt committed, so it sends again with the same key and the same payload, and gets the original message back instead of a second receipt. The tool description is specific that the key is reused only for an exact retry. A new order gets a new key. The full version of that argument lives on the email API page.
Agents get the same surface rather than a lesser one. Campaigns, flows, templates, segments, approvals and delivery review are exposed as typed tools over MCP, and a single transactional send is available there too as nitro_send_message. REST and the SDK are the recommendation for mail your application triggers, not a restriction on what an agent may call. The rest of the endpoints are on the email API page. The same call from Python, Go, Java, C# and the command line is covered on its own page, and other languages are listed on the email API languages page.
Treat the read path as part of the integration rather than as observability you will add later. If an agent is writing the send, have it write this at the same time, in the same session, while the context is still loaded.
| Channel | Push or pull | Endpoint or tool | Use it when |
|---|---|---|---|
| Events | Pushed | Signed webhooks | Your code should react as a message is delivered, bounces or fails |
| Messages | Pulled | GET /v1/my/messages?status= | You need to know whether a specific send landed, or you may have missed a push |
| Suppressions | Pulled | GET /v1/my/suppressions | A specific customer stopped receiving mail |
| Capacity | Pulled | GET /v1/my/delivery/status | You want pacing and a bounded reason before a burst |
| Per person | Pulled | Contact timeline | The question is about one person rather than one message |
| Trends | Asked | nitro_get_insights | Someone asks how receipts are doing |
Events. Webhooks carry email.sent, email.delivered, email.opened, email.clicked, email.bounced, email.complained and email.failed. Payloads are signed with HMAC-SHA256 in the Standard Webhooks format, retried with exponential backoff, and include the message id, recipient, timestamp and event detail. Setup and signature checks are on the email webhooks page.
Suppressions. Hard bounces, soft-bounce suppressions, complaints and manual suppressions surface here, with the bounded provider diagnostic text kept from the original feedback event.
Capacity. The delivery projection never authorizes or reserves a send, and the documentation tells clients not to calculate headroom from a plan name.
Trends, from the agent. nitro_get_insights gets email analytics with trends, benchmarks and recommendations. An agent can call it at the end of the session that wrote the integration, and again whenever someone asks how receipts are doing, so the read-back becomes a question anyone can ask instead of a log someone has to open.
One caution about counters. Sent and delivered are written by two different things. Sent is written when the message is handed off. Delivered is written when the sending provider reports back, which only happens if the feedback path is wired. A healthy sent count next to a delivered count of exactly zero is a plumbing symptom, so check that feedback is wired before touching content. A low delivered count is a deliverability symptom. They look similar on a chart and they need opposite responses.
Then there is the half that faces your user, which most APIs leave to you. A transactional email that fails should be visible to the person waiting for it, with a way to act, such as correcting the address and sending again. Route email.failed and email.bounced to that path.
Four failure modes are specific to an agent owning the send path, and each one has a check.
idempotency_key every retry is a new message, so one order can produce several receipts. Take the key from the event, such as the order id, and reuse it only for an exact retry.to address. A model asked to fill in a recipient it does not have will produce a plausible one. The recipient has to come from the application's own data at send time, never from the prompt or the model's guess, and test sends go to the team's own test recipients.nitro_compose_campaign, which creates a draft and never delivers it.email.delivered event reached the endpoint before the session ends.Transactional and marketing email run on separate sending infrastructure here, so a promotional send cannot take receipts and password resets down with it. What counts as transactional in the first place is covered on the transactional email hub.
The less ordinary part is what a transactional lane is for, and therefore what it does not carry. One message goes to one person who caused it, so there is no audience to pace, no subscription to manage and no campaign report at the end, because none of those ideas apply to a receipt. Kam Low, Co-founder and CTO, describes the transactional_plain design mode in the same terms: it "is for receipts and verifications, requiring transaction details and a support path, and it rejects marketing copy."
That thinness is usually discussed as an abuse problem, because a path with fewer guard rails is the path abuse walks down, which the email API page covers. The consequence for honest senders is the inverse: the lane with the least supervision is the lane that owes you the most feedback.
A campaign has a report, an audience you chose on purpose, and somebody who will notice if the numbers look wrong. A transactional integration has none of those. It is wired up once, usually in an afternoon, and then it is expected to run untouched for years while the people who wrote it move on. Every property that makes the transactional path fast and quiet also makes it the part of your email that can fail longest before anyone finds out. Design the instrumentation in at the start.
The defaults act without a reader. Hard bounces auto-suppress and complaints auto-unsubscribe, as behavior rather than as a setting somebody has to find. Neither requires you to have read a webhook, which is the point when the integration was written in a session that has since closed. Mailbox providers price the alternative directly. Google tells senders to "keep spam rates reported in Postmaster Tools below 0.10% and avoid ever reaching a spam rate of 0.30% or higher" (Google, Email sender guidelines). A complaint nobody read still counts against that number.
Sending allowance is a standing, not a plan tier. Sender standing is Probation, Standard or Trusted, and it combines attributable delivery history, current positive list verification, domain strength and account signals. Qualifying evidence can earn Standard or Trusted on day one, and payment alone does not earn trust. It is evidence based rather than time based, which is why the capacity endpoint tells callers not to work headroom out from a plan name.
One account covers both kinds of mail. Transactional through REST or the SDK, campaigns and flows through the agent, the same contacts and the same deliverability underneath. If you want the argument for why the two are different disciplines rather than two products, it is on the transactional versus marketing page, and the wider case for running email from an agent is on the email marketing hub.
Unlimited contacts, billed per send. A contact you never email costs nothing to keep, so charging for it would only teach you to delete people you might want later. The Free plan includes 5,000 emails up front, then 500 a month, with 100 to 5,000 recipients per rolling 24 hours depending on sender standing. A new account starts at the low end and the allowance rises as standing improves. See pricing.
Transactional email is still wasted real estate at most companies. It is the mail a customer is actually waiting for, and it is the mail most brands phone in.
Is there a free tier, and does it work from Python? Yes. The Free plan includes 5,000 emails up front, then 500 a month. A new account starts at 100 recipients per rolling 24 hours, and the allowance rises to as many as 5,000 as sender standing improves. From Python you call the same REST endpoint with a bearer key.
How do I send transactional email from Node or TypeScript? Install the Node SDK, @nitrosend/sdk, create a client with your API key from an environment variable, and call messages.send with a sender, a recipient, a subject and the HTML body. It is one call, and it returns the message id with an accepted_at time for you to store.
Can I send transactional email over SMTP? No. Nitrosend does not offer an SMTP relay. The supported server-side path for signup verification, password resets and receipts is the REST API, one POST to /v1/my/messages, or the Node SDK. An agent can send the same single message through MCP with nitro_send_message.
What does accepted mean in the response? It means the message is queued for the background sender. The response carries id, status and accepted_at, with no delivered_at, because delivery is not known yet. It arrives later as an email.delivered webhook, or as a status you filter for on the messages endpoint.
Every plan includes full stack emailing: Flows, Newsletter Campaigns and Transactional Email, plus our NitroWheel LLM and all agent integrations (Claude, ChatGPT, Codex, Cursor and others). Pay for what you send, not who you store.
Every paid plan includes 3 agent inboxes and no Nitrosend footer.
For your first email program.
$192 billed yearly.
20,000 emails / month
Choose StarterFor a growing audience.
$480 billed yearly.
60,000 emails / month
Choose ProFor teams and agencies.
$1,440 billed yearly.
200,000 emails / month
Choose GrowthFor high-volume sending.
$2,880 billed yearly.
500,000 emails / month
Choose ScaleSending 500,000+ emails a month? Get a custom plan designed for you, with volume pricing for sending, verification and enrichment.
5,000 emails to start, then 500 emails every month. No expiry on your starter emails.
Daily allowances depend on your plan and sender standing. Strong list, domain and delivery evidence can raise standing, including on day one. Trusted receives the full plan allowance; available email credits, safety checks and delivery pacing still apply.
Free forever. No credit card required. See full comparison →