What it means to send email programmatically
Your application sends the email. A signup fires a confirmation, a payment fires a receipt, a failure fires an alert. No inbox, no compose window, no human in the middle. The code makes an authenticated request and delivery is handled.
There are two ways to do it, and one of them is why this page exists.
Sending a static newsletter is a solved problem. Generating unique messages for every recipient creates an entirely different load profile. The architecture usually works on paper. It breaks when a real user hits send. George, our co-founder, faced this exact bottleneck with lists of half a million subscribers. The sudden strain of generating unique messages was, as he put it on Developer Podcast, a challenge "we thought we'd handled in theory until we got our first massive customer".
Generating unique messages at scale breaks standard systems. A static newsletter goes to a mailing service once. A programmatic campaign requires building distinct assets for every recipient. George, our co-founder, defined the bottleneck on Developer Podcast as the sheer weight of "creating half a million different emails with different content" for a single send. We thought we had a theoretical solution for that volume. Then a large customer actually pushed the button.
SMTP vs the API path
The old path is SMTP: smtplib in Python, Nodemailer in Node, JavaMail in Java. You configure a host, pick port 587 or 465, generate an app password for Gmail, and hold a connection open. Every language has its own library and every library has its own way of failing.
The API path is one HTTP POST. No SMTP library, no ports, no app passwords, no connection management. You get a message ID back, and delivery events after that.
I've been doing email for a decade and my companies have sent over 6 billion emails. Almost none of the pain in that decade came from the send itself. It came from everything wrapped around it. The API path deletes most of the wrapping.
And the wrapping is where the speed lives now. In one week we shipped a rebuilt visual email builder that Claude and Codex can prompt, five provider integrations, OAuth agent connect, and thirty-plus fixes from live user sessions. A protocol from 1982 was never going to keep that pace.
One call, any language
import requests requests.post( "https://api.nitrosend.com/v1/my/messages", headers={"Authorization": "Bearer $NITROSEND_API_KEY"}, json={ "from": "[email protected]", "to": "[email protected]", "subject": "Welcome", "html": "<p>You're in.</p>", }, )
Swap requests for fetch, curl_init, Net::HTTP, HttpClient, or reqwest and the call is the same. Bearer token, JSON body, message ID back. That is the whole contract, in every language we cover.
One field note from running the receiving end: Cloudflare's WAF blocks the default Python-urllib user agent, so a bare urllib script gets a cryptic 403 with error code 1010 while the identical call through requests sails through. Set a real User-Agent and move on. We learned to watch for this the interesting way: an audit of our own traffic turned up 3,228 tool calls with a blank user agent, almost all of them OAuth requests from Codex, which sends no User-Agent at all.
My take: the language guides are where most providers hide their worst docs. Ours are the first thing agents read, so they have to be exact. Whichever language you pick, the delivery questions are the same: how to set up SMTP if you are staying on SMTP, and SPF, DKIM and DMARC authentication before you send to a real list.