Idempotency keys on every send.
Retry a request with the same key and you get the original message back, not a second copy. A timeout during checkout stops being a reason to send two receipts.
The Notix transactional email API sends one message to one person in response to something they did, and makes the send safe to retry. Every request can carry an idempotency key, every message can be checked for deliverability before it leaves, and every bounce, complaint and unsubscribe lands on one suppression list that the email API and the SMTP relay both honour. The free plan does not ask for a card.
A receipt sent twice, a reset link that never arrived, an alert that went to someone who unsubscribed: each is a support ticket. The API is shaped to keep them from happening.
Retry a request with the same key and you get the original message back, not a second copy. A timeout during checkout stops being a reason to send two receipts.
One call scores a message for the things that get transactional mail filtered, so a broken template is caught in staging rather than in a customer's spam folder.
Bounces, complaints and unsubscribes are recorded once and honoured by every send, transactional and marketing, from the API and from the SMTP relay.
Sent, delivered, bounced, complained: each posts to your endpoint with a signature you verify, so your own records match what happened to the message.
Send many messages in one request, or set a time and let the API hold the message until then. Both use the same body as a single send.
Typed SDKs for TypeScript, Python, Go and PHP where they help. Everywhere else, one POST with a JSON body and a message id in the response.
The same call in five languages. The Idempotency-Key is your own identifier for the event; use the order id, the reset token or the sign-up id, and a retry can never double-send.
curl -X POST https://app.usenotix.dev/api/v1/emails \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4471-receipt" \
-d '{
"from": "receipts@acme.com",
"to": "customer@example.com",
"subject": "Your receipt for order 4471",
"html": "<p>Thanks for your order.</p>"
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.emails.send(
{
from: "receipts@acme.com",
to: "customer@example.com",
subject: "Your receipt for order 4471",
html: "<p>Thanks for your order.</p>",
},
{ idempotencyKey: "order-4471-receipt" },
);
import os
from notix import Notix
notix = Notix(os.environ["NOTIX_API_KEY"])
data, error = notix.emails.send(
{
"from": "receipts@acme.com",
"to": "customer@example.com",
"subject": "Your receipt for order 4471",
"html": "<p>Thanks for your order.</p>",
},
{"idempotency_key": "order-4471-receipt"},
)
package main
import (
"bytes"
"net/http"
"os"
)
func main() {
body := bytes.NewBufferString(`{
"from": "receipts@acme.com",
"to": "customer@example.com",
"subject": "Your receipt for order 4471",
"html": "<p>Thanks for your order.</p>"
}`)
req, _ := http.NewRequest("POST", "https://app.usenotix.dev/api/v1/emails", body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+os.Getenv("NOTIX_API_KEY"))
req.Header.Set("Idempotency-Key", "order-4471-receipt")
http.DefaultClient.Do(req)
}
<?php
$ch = curl_init('https://app.usenotix.dev/api/v1/emails');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . getenv('NOTIX_API_KEY'),
'Idempotency-Key: order-4471-receipt',
],
CURLOPT_POSTFIELDS => json_encode([
'from' => 'receipts@acme.com',
'to' => 'customer@example.com',
'subject' => 'Your receipt for order 4471',
'html' => '<p>Thanks for your order.</p>',
]),
]);
curl_exec($ch);
The API accepts the request and returns an emailId.
The message waits in line for delivery.
The message is sent to the recipient's mail server.
The message is delivered, or it bounces with a reason.
Every step posts to your webhook as it happens.

Networks time out, payment webhooks fire twice, and users click buttons more than once. Any of those can re-run the code that sends a receipt. Notix treats the Idempotency-Key header as the identity of the send: the first request with a key creates the message, and every later request with the same key and the same body returns that message, id and all. If the body differs, the API answers 409 rather than guessing which version you meant. Choose keys from your own data, such as order-4471-receipt, so the guarantee follows the event and not the process that happened to run.
Transactional mail fails quietly. A template edit adds a pattern a filter dislikes, a DNS change drops DKIM, and the first sign is a customer saying the code never came. The pre-send check takes a message and a sending domain and returns a score with the reasons behind it: authentication state, HTML structure, link and text patterns, and whether the recipient is already suppressed. Run it in CI against your templates, or call it before an unusual send, and the report tells you what to change.
A hard bounce, a spam complaint or an unsubscribe is recorded once, on a list that every send checks: transactional and marketing, API and SMTP relay. A recipient who complained about a campaign does not receive a receipt from the same domain the next day, and an address that bounced is not attempted again until you clear it. You can import an existing suppression list when you move a domain to Notix, so history from a previous provider carries over, and you can export it at any time.
Free: 5,000 emails a month, 200 a day, 1 domain. No card, no end date.
Pro: $15 a month for 50,000 emails, more domains and team members, your own SMS sender ID.
See the full pricing pageEach one is a single send triggered by a single event. The quickstart shows the request for the first of them and the rest follow the same shape.
A code and a short expiry, sent the moment the user asks. Notix also has a verification API that generates and checks the code for you, over email or SMS.
A single-use link, sent once. The idempotency key keeps a double-clicked form from sending two.
Send from your order's own id as the key, and a retried webhook from your payment provider can never produce a second receipt.
Sign-in from a new device, an invoice due, a comment on a thread. Scheduled sends let you batch the non-urgent ones.
The first message a new account gets. Templates keep the copy in one place; the shared suppression list keeps it away from anyone who has opted out.
| Item | Limit | Where it is set |
|---|---|---|
| Recipients | 50 per message across to, cc and bcc; each one counts as an email | POST /v1/emails, and RCPT TO on the SMTP relay |
| Attachments | 10 files, 7 MB each, 10 MB per email | Request body, measured after base64 decoding |
| Batch | 100 messages per call, 40 MB of attachments across the batch | POST /v1/emails/batch |
| Idempotency key | 256 characters, kept 24 hours | Idempotency-Key header |
One JSON API for transactional and marketing email: send, batch, schedule, webhooks, typed SDKs.
ProductPoint any framework's mailer at one host and port; the relay turns SMTP into the same tracked, suppressed send.
NotixA free plan with no card, and Pro at $15 a month for 50,000 emails. Only sent volume is metered.
Use casesSend one-time codes by email or SMS with two API calls, with expiry, length and risk scoring handled for you.
GuidesIdempotency keys, batch sends, a webhook handler, templates, the error envelope and retries on the typed SDK.
CompareEvery vendor in one table with read dates, and a straight answer to which transactional email API a startup should pick.
Verify a domain, copy an API key, make one call with an idempotency key. The free plan does not ask for a card.