Notix
Integrations

Email from an Astro site, without making it all dynamic.

Most of an Astro site can stay static. The route that sends cannot: it needs a server adapter and prerender turned off for that one endpoint. Everything else about the send is the usual notix-js call with the key read from the server environment.

The send

Send an email from Astro.

src/pages/api/send.ts
import type { APIRoute } from "astro";
import { Notix } from "notix-js";

export const prerender = false;

const notix = new Notix(import.meta.env.NOTIX_API_KEY);

export const POST: APIRoute = async ({ request }) => {
  const { to } = await request.json();
  const { data, error } = await notix.emails.send({
    from: "Acme <receipts@acme.com>",
    to,
    subject: "Your receipt",
    html: "<p>Thanks for your order.</p>",
  });
  if (error) return Response.json({ error }, { status: 502 });
  return Response.json({ id: data?.emailId });
};

The from address must be on a domain you have verified in Notix. Step by step: the Node.js quickstart and the longer guide to sending email from Astro.

Environment

One API key, kept on the server.

Create an API key on the API keys page of the Notix console and store it as NOTIX_API_KEY in .env, read with import.meta.env in server code. Never give it the PUBLIC_ prefix: variables with that prefix are built into the browser bundle, and anyone who opens the site can read them.

A Send only key is enough for code that only sends: if it leaks, it cannot read your contacts or delete a domain.

.env
NOTIX_API_KEY=notix_xxxxxxxxxxxx
NOTIX_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx
Webhooks

Verify delivery events over the raw body.

Notix signs every webhook call with the webhook's signing secret: an HMAC-SHA256 of the X-Notix-Timestamp header, a dot and the raw body, sent in X-Notix-Signature. The SDKs check the signature and refuse a call more than 5 minutes old.

Call request.text() before anything parses the body: the signature covers the exact bytes Notix sent.

src/pages/api/webhooks/notix.ts: export it as POST with prerender = false
import { Notix } from "notix-js";

const notix = new Notix(process.env.NOTIX_API_KEY!);
const webhooks = notix.webhooks(process.env.NOTIX_WEBHOOK_SECRET!);

export async function handleNotixWebhook(request: Request): Promise<Response> {
  const rawBody = await request.text();
  try {
    const event = webhooks.constructEvent(rawBody, { headers: request.headers });
    if (event.type === "email.bounced") {
      // Mark the address in your own records.
    }
    return new Response("ok");
  } catch {
    return new Response("invalid signature", { status: 400 });
  }
}
FAQ

Questions, answered.

Does Astro need an adapter to send email?
Yes, for the endpoint that sends. A static build has no server to run it, so add the adapter for your host and set prerender = false on the endpoint; the rest of the site can stay prerendered.
What if the same request runs twice?
Send an Idempotency-Key header (idempotencyKey in notix-js) with a value that names the thing being sent, such as order-4471-receipt. A repeat with the same key and body returns the first result and sends nothing new; the same key with a different body is refused with 409.
Why is my send refused with a from-address error?
The from address must be on a domain you have verified in Notix. Add the domain in the console, publish the DNS records it shows, and wait for verification; until then, sends from that domain are refused.

The free plan does not ask for a card.

Send your first email in five minutes.

Verify a domain, copy an API key, make one call. The free plan does not ask for a card.