An API key.
Created in the dashboard under API keys, shown once. A sending access key is enough for this guide and cannot read contacts or delete domains if it leaks.
Deno ships fetch and denies network and environment access until you allow it, so a Notix send is one POST to the email API and two permission flags. This guide sends from a script, then from a Deno.serve handler, then notes what changes on Deno Deploy. The SDK is available through an npm: specifier when you want it; the request is identical.
Created in the dashboard under API keys, shown once. A sending access key is enough for this guide and cannot read contacts or delete domains if it leaks.
Add your domain, publish the SPF, DKIM and DMARC records Notix gives you, and the from address on it becomes valid. The quickstart walks through it.
Nothing to install for the fetch path. The run command grants exactly what the script uses:
# Outbound requests and the environment are both off until you allow them.
deno run --allow-net=app.usenotix.dev --allow-env=NOTIX_API_KEY send.ts
The free plan covers 5,000 emails a month and 200 a day, and the free plan does not ask for a card.
Export NOTIX_API_KEY in the shell that runs the script, or keep it in a .env file and pass --env-file=.env to deno run. Deno.env.get returns undefined rather than throwing when a variable is missing, so check it before the first send. The permission flag --allow-env=NOTIX_API_KEY exposes that one variable and nothing else.
One POST to /api/v1/emails with a Bearer token and from, to, subject and html or text (send both; some clients only show one). The Idempotency-Key header makes a retry safe: the same key and body return the original message. The SDK tab is the same call through npm:notix-js, which Deno fetches and caches on first run.
// No dependency: Deno's fetch and the JSON API are enough.
const apiKey = Deno.env.get("NOTIX_API_KEY");
const response = await fetch("https://app.usenotix.dev/api/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
// A retried request with the same key returns the original send.
"Idempotency-Key": "order-4471-receipt",
},
body: JSON.stringify({
from: "receipts@acme.com",
to: "customer@example.com",
subject: "Your receipt for order 4471",
html: "<p>Thanks for your order.</p>",
text: "Thanks for your order.",
}),
});
const body = await response.json();
if (!response.ok) {
// Every error is { error: { code, message } }.
console.error(body.error.code, body.error.message);
} else {
console.log("queued", body.emailId);
}
// The npm: specifier pulls the same package Node uses; Deno caches it.
import { Notix } from "npm:notix-js";
const notix = new Notix(Deno.env.get("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>",
text: "Thanks for your order.",
},
{ idempotencyKey: "order-4471-receipt" },
);
if (error) {
console.error(error.code, error.message);
} else {
console.log("queued", data.emailId);
}
The response carries the email’s id. Store it next to the order or the user that caused the send: it is the key you read the message back with (GET /api/v1/emails/{id}) and the data.id every webhook event for that message carries.
In an app the send sits behind a route. Deno.serve takes a handler of (request) => Response; match the method and path yourself or with a router. A server needs --allow-net without a host list, or the listening host added to it, because listening is network access too.
const apiKey = Deno.env.get("NOTIX_API_KEY");
async function sendReceipt(orderId: string, to: string) {
const response = await fetch("https://app.usenotix.dev/api/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${orderId}-receipt`,
},
body: JSON.stringify({
from: "receipts@acme.com",
to,
subject: `Your receipt for order ${orderId}`,
html: `<p>Thanks for order ${orderId}.</p>`,
text: `Thanks for order ${orderId}.`,
}),
});
return { ok: response.ok, body: await response.json() };
}
Deno.serve(async (request) => {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname === "/api/receipts") {
const { orderId, to } = await request.json();
const { ok, body } = await sendReceipt(orderId, to);
return Response.json(ok ? { id: body.emailId } : { error: body.error }, {
status: ok ? 200 : 502,
});
}
return new Response("Not found", { status: 404 });
});
// deno run --allow-net --allow-env=NOTIX_API_KEY server.ts
// --allow-net without a host list: the server listens and the send goes out.
On Deno Deploy the handler above runs as it is. Set NOTIX_API_KEY and NOTIX_WEBHOOK_SECRET as environment variables in the project’s settings, not in a file in the repository; Deno.env.get reads them and no permission flags are involved on the platform.
An idempotency key, a look at the error envelope, and a webhook for delivery status. The samples above already carry the key and check the envelope; the webhook is one more Deno.serve handler that verifies the signature over the raw body. The reasoning behind each is in the Node.js guide and applies unchanged.
import { Notix } from "npm:notix-js";
const notix = new Notix(Deno.env.get("NOTIX_API_KEY"));
const webhooks = notix.webhooks(Deno.env.get("NOTIX_WEBHOOK_SECRET"));
Deno.serve(async (request) => {
if (request.method !== "POST") return new Response("Not found", { status: 404 });
// Verify over the raw body, not a parsed object.
const rawBody = await request.text();
const event = webhooks.constructEvent(rawBody, { headers: request.headers });
switch (event.type) {
case "email.delivered":
case "email.bounced":
case "email.complained":
// event.data.id is the emailId you were given at send time.
break;
}
return new Response("ok");
});
The verifier uses createHmac and timingSafeEqual from Node’s crypto module, which Deno implements for npm packages, so constructEvent works here as it does on Node. Without the SDK, the check is HMAC-SHA256 over timestamp.rawBody compared with the X-Notix-Signature header; the signature page shows the raw check step by step. Batch sends, scheduling and templates are in the production guide for transactional email; the runtime does not change any of it.
One JSON API for transactional and marketing email: send, batch, schedule, webhooks, typed SDKs.
GuidesThe SDK, plain fetch, or Nodemailer pointed at the relay: three ways to send from Node in a few minutes.
GuidesA Hono route that sends through Notix on Node, Bun, Deno or Cloudflare Workers, the runtime Notix's own API is written in.
Use casesSend one-time codes by email or SMS with two API calls, with expiry, length and risk scoring handled for you.
LearnWhy a retried request must not send twice, how the Idempotency-Key header works, and how to choose a key.
NotixA free plan with no card, and Pro at $15 a month for 50,000 emails. Only sent volume is metered.
Verify a domain, copy an API key, deno run with two flags. The free plan does not ask for a card.