Sending Auth emails in the user's language

SupaHost has one template per Auth email type, with no per-language versions. Three workarounds: bilingual text, localized landing pages, or sending your own sign-in emails with generateLink.

There's one template per email type (Confirm sign-up, Magic link, Reset password, Invite, Change email), and templates can't switch language based on the user. The fields accept plain text and four placeholders: {{ .Email }}, {{ .NewEmail }}, {{ .Token }} and {{ .SiteURL }}. There's no placeholder for user metadata and no conditional logic. You can still give users a localized experience in one of three ways.

Option 1: write each template in two languages

If most users speak one of two or three languages, put short versions of each in the same email. Dashboard → your project → Auth tab → Auth emails, choose the template, and write something like:

Subject: Your sign-in link / Votre lien de connexion

Message:
Use the button below to sign in to Bookly. The link works once.
Utilisez le bouton ci-dessous pour vous connecter à Bookly. Le lien ne fonctionne qu'une fois.

Keep it short; the Subject is limited to 150 characters and the Button text to 40.

Option 2: send users to a page in their language

The email stays the same, but where the link lands can follow the user's language. Pass a localized redirect when you start the flow:

const locale = navigator.language.startsWith("fr") ? "fr" : "en";

await supabase.auth.signInWithOtp({
  email,
  options: {
    emailRedirectTo: `https://bookings.example.com/${locale}/auth/callback`,
    data: { locale }, // saved in user_metadata if this creates the user
  },
});

Add https://bookings.example.com/** to Redirect URLs so every locale path is allowed. Storing locale in user metadata lets your app, and any emails you send yourself, use the same language later.

Option 3: send the email yourself

For fully translated emails, generate the sign-in link on the server and send it through your own email provider. auth.admin.generateLink() returns the link and one-time code without sending anything. This Edge Function sends a magic link in English or French:

import { createClient } from "npm:@supabase/supabase-js@2";

const admin = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, {
  auth: { autoRefreshToken: false, persistSession: false },
});

const copy = {
  en: { subject: "Your Bookly sign-in link", body: "Click to sign in:" },
  fr: { subject: "Votre lien de connexion Bookly", body: "Cliquez pour vous connecter :" },
};

Deno.serve(async (req) => {
  const { email, locale } = await req.json();
  const lang = locale === "fr" ? "fr" : "en";
  const t = copy[lang];

  const { data, error } = await admin.auth.admin.generateLink({
    type: "magiclink",
    email,
    options: { redirectTo: `https://bookings.example.com/${lang}/auth/callback` },
  });
  if (error) return Response.json({ error: "Could not send link" }, { status: 400 });

  await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${Deno.env.get("RESEND_API_KEY")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: "Bookly <[email protected]>",
      to: email,
      subject: t.subject,
      html: `<p>${t.body}</p><p><a href="${data.properties.action_link}">${data.properties.action_link}</a></p>
             <p>Code: <b>${data.properties.email_otp}</b></p>`,
    }),
  });

  return Response.json({ sent: true });
});

Things to get right:

  • Never return the link or code in the response. That would let anyone sign in as any address.
  • Add your own rate limiting, per address and per IP. Auth's email send limit doesn't apply, because Auth isn't sending.
  • Use it for people who already have an account. For new sign-ups, create the user first with admin.createUser(), or keep using Auth's Confirm sign-up email.
  • Store RESEND_API_KEY on the Functions tab's Secrets card. Any provider with an HTTP API works the same way.

On SupaHost

Auth's own emails always come from [email protected] with your App name as the sender name, and custom SMTP isn't available. Option 3 is also how to send sign-in emails from your own domain.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us