Notix
Use cases

Notifications from one API: template, batch, webhook.

A product notification is a template, a recipient and a few variables. Notix sends it through the transactional email API with an idempotency key so a retried worker never sends twice, fans an event out to up to 100 people in one batch call, holds low-priority notices for a scheduled digest, and reports delivery, bounces and complaints back through signed webhooks so your own activity log can show what happened.

The flow

Event in, email out, state back.

Three calls cover every notification a product sends.

  1. Send one.

    When the event fires, call POST /v1/emails with the template for that notification type, the variables it names, and an idempotency key built from the event and the recipient. Store the emailId on your notification row.

  2. Send many.

    When the event fans out, call POST /v1/emails/batch with up to 100 emails and one key. Put a scheduledAt on the ones that can wait for the morning digest.

  3. Listen.

    A webhook endpoint receives email.delivered, email.bounced and the rest, each carrying the email id, so your notification row updates by primary key and a bounced address gets fixed in the product.

Code

One notification.

A template per type, variables from the event, a key that names both.

curl
# One notification, one template, the variables the template names.
curl -X POST https://app.usenotix.dev/api/v1/emails \
  -H "Authorization: Bearer $NOTIX_API_KEY" \
  -H "Idempotency-Key: comment-cmt_8812-notify-usr_123" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <notifications@acme.com>",
    "to": "user@example.com",
    "templateId": "tpl_new_comment",
    "variables": {
      "commenter": "Amara",
      "excerpt": "Can we move the deadline to Friday?",
      "threadUrl": "https://acme.com/t/8812"
    }
  }'

# 200 { "emailId": "eml_abc123" }

Fan-out and digests.

Batches of 100, and a scheduledAt for the notices that can wait.

curl
# An event that fans out to many people: one call, up to 100 emails,
# one idempotency key for the whole batch.
curl -X POST https://app.usenotix.dev/api/v1/emails/batch \
  -H "Authorization: Bearer $NOTIX_API_KEY" \
  -H "Idempotency-Key: deploy-dpl_551-notify" \
  -H "Content-Type: application/json" \
  -d '[
    { "from": "Acme <notifications@acme.com>", "to": "ada@example.com",
      "templateId": "tpl_deploy_finished", "variables": { "app": "api", "version": "2.14.0" } },
    { "from": "Acme <notifications@acme.com>", "to": "tunde@example.com",
      "templateId": "tpl_deploy_finished", "variables": { "app": "api", "version": "2.14.0" } },
    { "from": "Acme <notifications@acme.com>", "to": "chidi@example.com",
      "templateId": "tpl_weekly_digest", "variables": { "count": "12" },
      "scheduledAt": "2026-09-15T08:00:00+01:00" }
  ]'

# 200 { "data": [ { "emailId": "eml_1" }, { "emailId": "eml_2" }, { "emailId": "eml_3" } ] }

Delivery state back.

The event’s data.id is the email id from the send, so the update is a primary-key write.

TypeScript
// Next.js route handler. The event id is the email id you stored, so
// the notification row updates without a lookup by address.
import { notix } from "@/lib/notix";

export async function POST(request: Request) {
  const raw = await request.text();
  const event = notix.webhooks(process.env.NOTIX_WEBHOOK_SECRET!).constructEvent(raw, {
    headers: request.headers,
  });

  const state = {
    "email.delivered": "delivered",
    "email.bounced": "bounced",
    "email.complained": "complained",
    "email.suppressed": "suppressed",
    "email.opened": "opened",
  }[event.type];

  if (state) {
    await db.notification.updateMany({ where: { emailId: event.data.id }, data: { state } });
    if (event.type === "email.bounced" && event.data.bounce.type === "Permanent") {
      // The address is now suppressed; ask for a new one in the product.
      await db.user.update({ where: { email: event.data.to[0] }, data: { emailInvalid: true } });
    }
  }
  return new Response("ok");
}
Limits

The numbers that apply.

Documented limits of the send and batch endpoints and the event catalogue. Change one there and it changes here.

ItemValueNotes
Emails per batch call100POST /v1/emails/batch. Each item carries the same fields and limits as a single send.
Request body20 MBApplied at the edge; a larger body is refused with 413 and no JSON.
Attachments10 MB per email, 40 MB per batchMeasured after base64 decoding. 10 files per email, 7 MB each.
SchedulingscheduledAt per emailAn ISO 8601 timestamp with offset. A scheduled email can be moved with PATCH or cancelled until it is sent.
IdempotencyIdempotency-Key headerOne key per request, for single and batch sends alike. A replay within 24 hours returns the original ids.
Delivery events13 email eventsqueued, sent, delivery_delayed, delivered, bounced, rejected, rendering_failure, complained, failed, cancelled, suppressed, opened, clicked.

The full request and response shapes, the idempotency header and the event payloads are in the docs.

Preferences

What must always go out.

Three kinds of notification, three rules. Notix enforces the suppression list on all of them and the unsubscribe on the marketing kind; the middle row is yours.

KindExamplesRule
Must always go outPayment failed, security alert, invoice dueSend as transactional. It reaches a contact who has unsubscribed from marketing. Only a suppressed address (hard bounce or complaint) is dropped.
The user can turn offWeekly digest, comment replies, tipsKeep the preference in your app and do not call the API for people who opted out. Notix does not store per-notification preferences for transactional sends.
MarketingFeature launches, offersSend through a campaign or journey to a contact book. An unsubscribe stops these at send time, and List-Unsubscribe headers are set for you.
Design notes

Five habits for notifications people keep.

One template per notification type.

Comment, mention, deploy finished, payment failed: each gets a template with named variables, edited in the dashboard without a deploy. Your code passes data, never markup.

Key on the event and the recipient.

comment-8812-notify-usr_123 is stable across a retried worker, a redelivered queue message and a double-clicked button. The same key returns the same emailId and sends nothing new.

Batch the fan-out, schedule the noise.

An event with fifty watchers is one batch call. Low-priority notices get a scheduledAt for the next morning and arrive as one digest instead of eleven interruptions.

Store the email id, then listen.

The webhook event carries the id you were given at send time, so a notification's state updates by primary key. Show "delivered" and "bounced" in your own activity log.

Decide what must always go out.

A failed payment is not a preference. Send it as a transactional email, which reaches even a contact who unsubscribed from marketing, and keep optional notices behind a setting in your app.

FAQ

Questions, answered.

What is a notification email API?
An API your product calls when something happens to a user: a comment, a mention, a finished job, a failed payment. Notix takes a template id and variables, sends the email, and reports what happened to it through signed webhooks. Fan-outs go through the batch endpoint, up to 100 emails per call, and low-priority notices can be scheduled into a digest with scheduledAt.
How do I send the same notification to many people?
POST /api/v1/emails/batch with an array of up to 100 emails, each with its own recipient, template and variables, and one Idempotency-Key for the request. The response is a list of emailIds in the same order. For more than 100 recipients, page through in groups of 100 with a different key per page.
How do I know the notification was delivered?
Add a webhook endpoint and subscribe to the email events you care about: email.delivered, email.bounced, email.complained, email.suppressed, email.opened and email.clicked among them. Each event carries the emailId from the send response, the recipient and an occurredAt timestamp, and is signed with HMAC-SHA256 so your endpoint can verify it.
Can users choose which notifications they receive?
Per-notification preferences live in your app: keep a table of what each user wants and skip the API call for the rest. Notix stores one preference, the contact's subscribed flag, and applies it only to campaign and journey sends. A transactional send goes out regardless, which is what you want for alerts that must not be muted.
Can I batch low-priority notices into a digest?
Yes. Send the digest with a scheduledAt for the next morning in the user's time zone and an idempotency key that includes the date, so the first notice of the day creates the digest and later ones do not. The scheduled email can be updated or cancelled until it is sent.
Does Notix do push, Slack or in-app notifications?
No. Notix sends email, and SMS in Nigeria and Kenya through the SMS endpoint and the verification API. An in-app notification centre, push and chat channels are your app's or another service's job; the webhook events give you the delivery state to show alongside them.

Send your first notification in one call.

Templates, batch sends and signed webhooks on every plan, and the free plan's 5,000 emails a month to test with. No card.