Integrating Trusted Notifications
Trusted Notifications sends transactional emails — password resets, verifications, magic links, MFA codes and similar — from an authenticated domain, branded as you. You send us a small JSON request describing the email you want; we render it and deliver it. There's no SMTP, no template engine and no DNS to wire up on your side.
This whole page is deliberately self-contained, so you (or an AI assistant) have everything needed to integrate in one place.
Working with an AI coding assistant? You can hand it this page and let it do the integration. Paste the URL https://trustednotifications.com/docs into your assistant along with a prompt like:
Integrate Trusted Notifications into this codebase.
Read the docs at https://trustednotifications.com/docs.
Send transactional emails by POSTing JSON to
https://trustednotifications.com/v1/notifications
with header: Authorization: Bearer $TRUSTED_NOTIFICATIONS_API_KEY
Start with the "password-reset" type. Read the API key from an
environment variable and only ever call the API from server-side code.
Everything the assistant needs — the endpoint, authentication, the request shape, the full list of notification types, and the error format — is documented below.
How it works
Every email is described by a type (for example password-reset) plus a recipient and a few fields that the type needs (such as a reset_url). You don't write any HTML — we hold the templates, and you supply the values. All values are treated as plain text and escaped, so you can't inject markup into an email.
- Pick a notification type from the list below.
- POST a JSON body with that type, the
recipientaddress, and the type's required fields. - We render your branded template and hand it to our sending infrastructure for delivery.
Endpoint
There's a single endpoint for sending:
POSThttps://trustednotifications.com/v1/notifications
Requests and responses are JSON. Send Content-Type: application/json.
Authentication
Authenticate with an API key using a bearer token:
Authorization: Bearer tn_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are created per application in your dashboard. Sandbox keys start with tn_test_ and production keys with tn_live_. Treat a key like a password: keep it in an environment variable or secret store, use it only from server-side code, and never expose it in a browser, mobile app, or public repository.
Sending a notification
POST a JSON object with these fields:
| Field | Type | Description |
|---|---|---|
type | string | Required. One of the notification types listed below. |
recipient | string | Required. The email address to send to. |
| …fields | string | The fields required by the chosen type, sent as top-level keys alongside type and recipient. Any extra fields are ignored. |
A minimal password-reset request looks like this:
{
"type": "password-reset",
"recipient": "user@example.com",
"reset_url": "https://yourapp.com/reset?token=abc123"
}
Notification types
This is the full library of types you can send. It's intentionally focused on functional, transactional messages — there's no arbitrary HTML, no newsletters and no attachments, which is part of what keeps deliverability healthy for everyone.
| Type | What it's for | Required fields |
|---|---|---|
api-key-created |
Alert that a new API key was created. |
key_prefix |
deployment-completed |
Notify that a deployment or pipeline finished. |
environment |
email-changed |
Confirm a change of email address. |
new_email |
invitation |
Invite someone to a team or workspace. |
invite_url |
magic-login |
Send a passwordless sign-in link. |
login_url |
mfa-code |
Deliver a one-time passcode. |
code |
password-changed |
Confirm that an account password was changed. | none |
password-reset |
Send a secure, single-use password-reset link. |
reset_url |
payment-failed |
Tell a customer a payment failed and needs attention. |
update_url |
receipt |
Send a payment receipt. |
amount currency |
verify-email |
Confirm a new user's email address. |
verify_url |
Example request body for every type
Each block below is a complete, ready-to-send JSON body (swap in a real recipient and your own values):
{
"type": "api-key-created",
"recipient": "user@example.com",
"key_prefix": "tn_live_9f2a"
}
{
"type": "deployment-completed",
"recipient": "user@example.com",
"environment": "production"
}
{
"type": "email-changed",
"recipient": "user@example.com",
"new_email": "new.address@example.com"
}
{
"type": "invitation",
"recipient": "user@example.com",
"invite_url": "https://yourapp.com/invite?token=abc123"
}
{
"type": "magic-login",
"recipient": "user@example.com",
"login_url": "https://yourapp.com/magic?token=abc123"
}
{
"type": "mfa-code",
"recipient": "user@example.com",
"code": "492001"
}
{
"type": "password-changed",
"recipient": "user@example.com"
}
{
"type": "password-reset",
"recipient": "user@example.com",
"reset_url": "https://yourapp.com/reset?token=abc123"
}
{
"type": "payment-failed",
"recipient": "user@example.com",
"update_url": "https://yourapp.com/billing"
}
{
"type": "receipt",
"recipient": "user@example.com",
"amount": "49.00",
"currency": "USD"
}
{
"type": "verify-email",
"recipient": "user@example.com",
"verify_url": "https://yourapp.com/verify?token=abc123"
}
Responses
A successful request returns 202 Accepted with the notification's id and status:
{
"id": "b2c9f0e1-8a4d-4f7c-9b21-6e3f0a1d2c34",
"status": "sent"
}
If a recipient can't currently be delivered to (for example they previously complained about mail from your app), the request is still accepted but not delivered. This is a normal, healthy outcome, not an error — and your dashboard shows which of your sends were suppressed and why:
{
"id": "b2c9f0e1-8a4d-4f7c-9b21-6e3f0a1d2c34",
"status": "suppressed",
"reason": "complaint"
}
Some recipients are held back for platform-wide deliverability reasons that aren't specific to your app; in that case the response is simply { "status": "accepted" } with no reason.
Errors
Errors use standard HTTP status codes and a consistent envelope:
{
"error": {
"code": "invalid_request",
"message": "Both \"type\" and \"recipient\" are required."
}
}
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body wasn't a valid JSON object. |
| 401 | unauthorized | The API key is missing or invalid. |
| 403 | application_suspended | The application has been suspended. |
| 405 | method_not_allowed | Use POST. |
| 422 | invalid_request | A required field is missing or a value is invalid. |
| 422 | unknown_type | The type isn't a recognised notification type. |
| 429 | rate_limited | You've hit your rate or daily limit — retry after a short wait. |
| 502 | delivery_failed | The message couldn't be handed off for delivery. Safe to retry. |
Sandbox & production
New applications start in sandbox so you can build and test safely. Two rules are worth knowing up front, because they're the usual reason a send is rejected while you're getting started:
- In sandbox, you can only send to confirmed test recipients — addresses you've added and verified in the dashboard. Your own account email is added for you automatically. Sending to anyone else returns
422 invalid_request. - In production, any links in your fields must point to a domain you've verified for the application. This protects the shared sending domain from being used for phishing. Links to unverified domains return
422 invalid_request.
Applications also have a per-minute rate limit and a daily limit; exceeding either returns 429 rate_limited. Moving from sandbox to production is a quick review step in the dashboard once you've verified your domain.
Code examples
The same password-reset request in a few languages. In each, the API key comes from an environment variable rather than being hard-coded.
curl
curl -X POST https://trustednotifications.com/v1/notifications \
-H "Authorization: Bearer $TRUSTED_NOTIFICATIONS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "password-reset",
"recipient": "user@example.com",
"reset_url": "https://yourapp.com/reset?token=abc123"
}'
Node.js (fetch)
const res = await fetch("https://trustednotifications.com/v1/notifications", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TRUSTED_NOTIFICATIONS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "password-reset",
recipient: "user@example.com",
reset_url: "https://yourapp.com/reset?token=abc123",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message ?? "Send failed");
console.log(data.id, data.status);
PHP
$ch = curl_init("https://trustednotifications.com/v1/notifications");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("TRUSTED_NOTIFICATIONS_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"type" => "password-reset",
"recipient" => "user@example.com",
"reset_url" => "https://yourapp.com/reset?token=abc123",
]),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
Getting an API key
You'll need a free account to create an application and generate a key. It takes a couple of minutes, and you can send to your own verified address straight away in sandbox.
