A transactional email API lets your backend request an email when a user or system event occurs. Your app submits message details over HTTP, saves the returned message ID, and follows delivery separately. A successful request does not by itself confirm that an email reached an inbox.
Before you start #
- For
- Developers choosing or connecting an email API.
- Bring
- Basic HTTP and JSON knowledge. Node.js 22+ and cURL for the optional local exercise.
- Scope
- The fixture needs no account, API key or sending domain. It never sends email.
What is a transactional email API used for? #
A signup may need an email-verification link. A completed payment may need a receipt. An account change may need a notification. These emails are tied to a specific event and recipient. A promotional newsletter has a different purpose, audience, and subscription workflow.
The API is the boundary between your product and its email delivery system. Your app decides why a message is needed, who should receive it, and which content is appropriate. The email service validates the request and handles the next delivery steps. Keep the business event ID alongside the message ID so a support question can be traced back to the original action.
Follow the request, queue, and delivery as separate steps #
In Stampwing’s implementation, POST /api/v1/emails accepts a message for processing and returns an ID, status, and mode. The message and its queued event are stored together in PostgreSQL. A worker checks sending eligibility before submitting to AWS SES. This architecture is implemented in the application; the public live service is still in development.
HTTP 202 means processing has been accepted, not completed. Save the response before showing a status to your user. An interface can say that an email was requested while a background task follows its progress; it should not call a queued request delivered.
- Business event ID: your application’s reason for sending. Keep it stable across network retries.
- API request ID: useful to trace one HTTP attempt; it may change on a retry.
- Message ID: the service’s stored email record. Keep the provider’s ID separately if another service dispatches it.
- Webhook event ID: one observation about a message. Several events can refer to the same message.
| Evidence | What it means | Useful next step |
|---|---|---|
| 202 and a message ID | Stampwing queued the request. | Store the ID with your business event. |
| Submitted | Provider submission has started or been acknowledged. | Wait for a delivery outcome. |
| Accepted | The receiving mail server accepted the email. | Investigate filtering if the recipient cannot find it. |
| Bounced or complained | A permanent failure or complaint was reported. | Respect suppression and investigate the reason. |
| Uncertain | A submission result could not be confirmed. | Reconcile the existing message before a new send. |
Try a cURL request without sending email #
Download the local API fixture, save it as email-mock-server.mjs, and run node email-mock-server.mjs with Node.js 22 or later. Keep that terminal open and run the following command in another terminal. The fixture binds to your computer’s loopback address, requires no account or API key, and stores its records in memory until you stop it.
curl -i http://127.0.0.1:3027/api/v1/emails \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: receipt-42' \
-d '{"to":"reader@example.test","subject":"Your receipt","text":"A simulated receipt"}'Keep API keys on the server and separate Test from Live #
The application API uses Authorization: Bearer with a project API key. A key determines the project and environment; request parameters do not grant access to another project. Give sending code only the permissions it needs, revoke unused keys, and avoid printing credentials in logs.
A browser button should call your own authenticated backend, which then decides whether to request an email. Putting a secret API key in browser JavaScript would let visitors extract it. A server-side environment variable is one way to supply it to your backend without embedding it in a public bundle.
Stampwing’s Test environment simulates delivery. The downloadable fixture is smaller still: it has no authentication, persistent storage, or real provider. Do not expose that fixture as a public service or use its successful response as evidence that production sending is configured.
Retry a logical send with the same idempotency key #
A network timeout can happen after the service stores a message but before your app receives the response. Retrying with a new key can then create a second email. Generate the key for the business operation, persist it, and reuse it with an unchanged payload when the result is unknown. Consult your provider’s retention window and retry policy.
Run the cURL command above twice while the fixture is running. Both responses should contain the same ID. Change the text but keep receipt-42 and the fixture returns HTTP 409. Restarting the fixture clears its memory, including those keys. This demonstrates request deduplication; it does not guarantee exactly-once delivery across every system.
Classify the response before scheduling recovery. A documented validation or authorization rejection needs a fix; a rate limit needs a delay; a timeout can leave acceptance unknown. HTTP 5xx alone is not proof that the provider did nothing. Check its idempotency contract before retrying the same operation, and put unresolved cases into a review state when that contract cannot protect the retry.
Design retries and an application outbox to prevent duplicate emails →
Look up a message ID or receive delivery webhooks #
Copy the ID from the local response and replace MESSAGE_ID below. The fixture returns the stored simulated record. The application’s real status endpoint also requires a project key and enforces the key’s project and environment boundaries.
For production, follow status at a modest interval or process signed delivery webhooks. Verify the signature before accepting a webhook, persist the event before acknowledging it, and handle duplicates and out-of-order events. A healthy HTTP endpoint and a healthy background processing queue are separate things to monitor.
curl http://127.0.0.1:3027/api/v1/emails/MESSAGE_IDHow does MCP connect Codex and Claude to email workflows? #
Model Context Protocol (MCP) lets assistants such as Codex and Claude Code use a service’s tools through a defined interface. Stampwing’s hosted connector is designed for inspecting templates and workflow runs, previewing email content, and creating or editing drafts. It uses Streamable HTTP and OAuth with PKCE (S256) to grant access to selected projects and environments, with separate permissions for reading, draft editing, and publishing.
Draft changes support JSON Patch and check the expected revision so an assistant cannot silently overwrite a newer edit. Published template and workflow versions are immutable, and running workflows keep their pinned versions. Previewing, changing a draft, and publishing are distinct operations.
For example, you could ask an assistant to preview a password-reset template or draft a welcome-email workflow. Those are examples of intended tasks, not a claim that the public connection is available today. Stampwing’s hosted MCP integration is still being prepared for launch. It complements the application API; a template preview or workflow simulation does not send real email.
What needs to be ready before live sending? #
Verify the sending domain and configure its authentication records. Check credential scope, environment, sending limits, suppressions, message content, and the application’s response to failures. Keep a recipient’s address, tokens, and message body out of unnecessary logs. Use the delivery evidence you actually have when showing status to customers.
Stampwing’s public website currently provides guides, downloadable templates, a header analyzer, and local examples. Customer accounts and live API sending are not available yet. You can work through the integration concepts now without creating an account or sending a real message.
Define a small set of operational measures before launch: oldest eligible queue age, attempts per logical message, and the count of unresolved submissions. Measure receiving-server acceptance separately. Set alert thresholds from your message lifetimes and traffic, and link each alert to an owner and a recovery action.
Understand SPF, DKIM, and DMARC for your sending domain →
Plan tests for transactional email without real recipients →
Size the queue before a traffic spike #
Work in recipients, not HTTP requests. A batch of 10,000 recipients at a sustained 50 recipients per second takes about 200 seconds to submit with an empty queue. Another 5,000 recipients ahead of it adds about 100 seconds. This is a capacity estimate, not an arrival-time promise.
Keep the application response independent of this batch duration: save the business operation, enqueue the email, and expose the last confirmed state. Compare queue age with the useful lifetime of the content. A password reset that is already expired needs a different recovery path from a delayed receipt.
Sources and further reading
- RFC 9110: HTTP 202 Accepted
- AWS SES: monitoring sending activity and delivery events
- OpenAI: Model Context Protocol in Codex
- Anthropic: connect Claude Code to tools via MCP
- Amazon SES: sending rates and quotas
Written for Stampwing with AI assistance and checked against the linked documentation. Examples are educational; simulated results are labelled. How these resources are maintained.