Notix
Integrations

Send Supabase Auth and app emails through Notix.

Supabase’s built-in email service sends two messages an hour, to your own team’s addresses only, and is meant for testing. For real users you give Supabase Auth a provider: enter the Notix SMTP relay under Custom SMTP, or point the Send Email Hook at an Edge Function that calls the email API. App emails such as receipts go out from any Edge Function or database webhook with the same key. Either way, every message carries your DKIM signature, your suppression list and your webhooks. The free plan covers it, no card needed.

Two ways in

A settings change, or a function you own.

Both replace Supabase’s built-in sending. Pick by how much of the email you want to control.

  1. Custom SMTP: Supabase keeps rendering, Notix delivers.

    Supabase Auth builds the confirmation, magic link, recovery, invite and email-change messages from its templates and hands each one to the relay over port 465 or 587. Five fields in the dashboard, no code, and the existing templates and {{ .ConfirmationURL }} variables keep working.

  2. Send Email Hook: your function renders and sends.

    Supabase posts the user and the email data (the six-digit token, the token hash, the action type, the redirect) to an Edge Function. The function writes the email it wants and POSTs it to the Notix API. Use it when you need your own HTML, a code rather than a link, or per-language templates.

  3. Everything else: an Edge Function with the key.

    Receipts, order updates and team notifications are not auth emails. Send them from an Edge Function your app calls, or one a Database Webhook calls when a row changes. Same key, same endpoint, plus an idempotency key so retries are safe.

Custom SMTP

The relay values, three ways to set them.

The sender address must be on a domain you have verified in Notix, or the relay refuses the message. The username is literally notix; the password is an API key.

Authentication → Emails → SMTP Settings
Enable Custom SMTP:   on
Sender email:         no-reply@acme.com      # an address on a domain verified in Notix
Sender name:          Acme
Host:                 smtp.usenotix.dev
Port number:          465                    # implicit TLS; 587 for STARTTLS
Username:             notix
Password:             <your Notix API key>   # Notix → Developer settings → API keys

After saving, send yourself a password reset from your app and open it in the Notix dashboard: it appears in the log like any API send, with the same delivery events. Supabase’s own guide to custom SMTP is at supabase.com/docs/guides/auth/auth-smtp.

Send Email Hook

An Edge Function that verifies, renders and sends.

Create the hook under Authentication → Hooks, choose HTTPS and this function’s URL, and copy the secret it shows into the function’s secrets. Supabase expects a 200 with an empty JSON body; anything else fails the sign-up.

supabase/functions/send-auth-email/index.ts
// supabase/functions/send-auth-email/index.ts
import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";

const hookSecret = (Deno.env.get("SEND_EMAIL_HOOK_SECRET") as string)
  .replace("v1,whsec_", "");
const notixKey = Deno.env.get("NOTIX_API_KEY") as string;

const SUBJECTS: Record<string, string> = {
  signup: "Confirm your Acme account",
  magiclink: "Your Acme sign-in link",
  recovery: "Reset your Acme password",
  invite: "You have been invited to Acme",
  email_change: "Confirm your new email address",
};

Deno.serve(async (req) => {
  if (req.method !== "POST") return new Response("not allowed", { status: 400 });

  const payload = await req.text();
  const headers = Object.fromEntries(req.headers);

  try {
    // Supabase signs the hook with Standard Webhooks; a bad signature throws.
    const { user, email_data } = new Webhook(hookSecret).verify(payload, headers) as {
      user: { email: string };
      email_data: {
        token: string;
        token_hash: string;
        redirect_to: string;
        email_action_type: string;
        site_url: string;
      };
    };

    const type = email_data.email_action_type;
    const link =
      `${email_data.site_url}/auth/confirm?token_hash=${email_data.token_hash}` +
      `&type=${type}&next=${encodeURIComponent(email_data.redirect_to)}`;

    const res = await fetch("https://app.usenotix.dev/api/v1/emails", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${notixKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        from: "Acme <no-reply@acme.com>",
        to: user.email,
        subject: SUBJECTS[type] ?? "Your Acme account",
        html: `<p><a href="${link}">Continue to Acme</a></p>
               <p>Or enter this code: <strong>${email_data.token}</strong></p>`,
      }),
    });

    if (!res.ok) {
      const body = await res.text();
      throw new Error(`notix ${res.status}: ${body}`);
    }
  } catch (error) {
    // Anything but 200 makes Supabase report the sign-up as failed.
    return Response.json(
      { error: { http_code: 500, message: (error as Error).message } },
      { status: 500 },
    );
  }

  return Response.json({});
});
Secrets and deploy
# Never put the key in code or in a NEXT_PUBLIC_ / VITE_ variable.
supabase secrets set NOTIX_API_KEY=<your Notix API key>
supabase secrets set SEND_EMAIL_HOOK_SECRET="v1,whsec_<from Authentication → Hooks>"
supabase functions deploy send-auth-email --no-verify-jwt

The confirmation link above uses the token-hash pattern from Supabase’s server-side auth guide, where a route at /auth/confirm calls verifyOtp. Adjust the path to whatever your app already handles. The hook payload and signature are documented at supabase.com/docs/guides/auth/auth-hooks/send-email-hook.

App emails

Receipts and notifications from an Edge Function.

One fetch to the email API with the key from Deno.env. Called from your app directly, or by a Database Webhook when the row that matters changes.

supabase/functions/send-receipt/index.ts
// Called from your app after an order is paid.
const notixKey = Deno.env.get("NOTIX_API_KEY") as string;

Deno.serve(async (req) => {
  const { orderId, email, total } = await req.json();

  const res = await fetch("https://app.usenotix.dev/api/v1/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${notixKey}`,
      "Content-Type": "application/json",
      // A retried invocation with the same key returns the first send
      // instead of emailing the customer twice.
      "Idempotency-Key": `order-${orderId}-receipt`,
    },
    body: JSON.stringify({
      from: "receipts@acme.com",
      to: email,
      subject: `Your Acme receipt for order ${orderId}`,
      html: `<p>Thanks for your order. Total: ${total}.</p>`,
    }),
  });

  if (!res.ok) {
    return Response.json({ error: await res.text() }, { status: 502 });
  }
  const { emailId } = await res.json();
  return Response.json({ emailId });
});

The response carries an emailId. Register a webhook in Notix and the delivered, bounced and complained events for that id post back to you; the docs show the event shapes and how to verify the signature.

Deliverability

Why Supabase auth emails land in spam, and the fix for each.

The built-in service is for testing.

Supabase's default email service sends two messages an hour, only to addresses of people on your project team, with no delivery guarantee. Anyone else signing up gets an error, not a slow email. Custom SMTP or the hook is the production path, not an optimisation.

The sender is not your domain.

A confirmation email from an address you do not own has no SPF or DKIM that names you, so a mailbox provider has nothing to trust. Set the sender to an address on a domain you have verified in Notix; the relay then signs every message with your DKIM key and passes DMARC alignment.

The hourly cap is still there.

Once custom SMTP is on, Supabase applies its own limit of 30 auth emails an hour by default. It protects your sender reputation on day one, but a launch or a bulk invite will hit it. Raise it under Authentication → Rate Limits before you need to, not after users start complaining.

The templates say nothing about you.

The stock subject lines and bodies are generic, and generic is a spam signal. Edit them under Authentication → Emails so the subject names your product, the body says why the email exists, and the link text says where it goes. With the hook, the email is entirely yours to write.

Not sure which one you have? Check your domain’s SPF, DKIM and DMARC first; it is the fix in most cases.

FAQ

Questions, answered.

How do I send Supabase Auth emails through my own provider?
Two ways. Turn on Custom SMTP under Authentication → Emails and enter the Notix relay: host smtp.usenotix.dev, port 465 or 587, username notix, your API key as the password, and a sender address on a domain you have verified in Notix. Or enable the Send Email Hook and point it at an Edge Function that builds the email and posts it to the Notix API. SMTP is a settings change; the hook gives you full control of the email.
Why do Supabase confirmation emails go to spam or never arrive?
Without custom SMTP, Supabase's built-in service sends two messages an hour, only to your project team's addresses, and offers no delivery guarantee. Once you switch to custom SMTP the usual causes are a sender address on a domain without SPF and DKIM, the 30 emails an hour default Supabase applies to custom SMTP, and the stock templates. Verify your domain in Notix, send from it, raise the rate limit, and rewrite the templates.
Should I use custom SMTP or the Send Email Hook?
Custom SMTP if you are happy with Supabase's templates and want to be done in five minutes; every message still gets Notix's suppression list, tracking and webhooks. The Send Email Hook if you want your own HTML, a code instead of a link, a different template per language, or logging of every send in your own system. You can move from one to the other later without touching your app.
How does the Edge Function know the request really came from Supabase?
The Send Email Hook is signed with the Standard Webhooks scheme. Supabase shows a secret of the form v1,whsec_… when you create the hook; store it as a function secret and verify the signature with the standardwebhooks library before you read the payload, as the sample on this page does. A database webhook is not signed the same way, so add a secret header to it in the dashboard and check that header in the function.
Can I send receipts and notifications from Supabase, not only auth emails?
Yes. Any Edge Function can POST to the Notix API with your key from Deno.env, and a Database Webhook can call that function when a row is inserted or updated, so an order marked paid sends its own receipt. Pass an Idempotency-Key built from the row id, so a retried webhook or a double invocation does not email the customer twice.
Is there a Notix package for Supabase?
No, and you do not need one. Auth emails go through the SMTP relay, which is a settings change, or the Send Email Hook, which is a fetch call. App emails from an Edge Function are the same fetch, or the notix-js SDK if you prefer typed calls; both run on Deno.

Give Supabase Auth a real sender this afternoon.

Verify your domain, copy an API key, paste five SMTP fields. The free plan's 5,000 emails a month cover the sign-ups; no card.