Skip to content

Set up email OTP and magic links

Test with your own address first, then add your domain and its DNS records, send and check codes, and switch to sign-in links if you prefer.

OTPUpdated 26 October 20268 min read

Email OTP sends a 6-digit code from your own domain and checks it for you: the code is made, hashed, emailed and expired by SignOps. Use it to confirm an email at sign-up, for password resets, and as a sign-in that needs no password. This guide gets you from test codes in your own inbox to live codes from your domain. Allow an hour, most of it waiting for DNS.

1. Test with your own address first

You can build the whole flow before touching DNS:

  1. In the dashboard, open Email OTP, Settings, and add your own email under Test addresses.
  2. Use your test key (sk_test_...). Test codes go from codes@auth.signops.dev to your test addresses only, up to 100 a day (50 on Free), and are not billed.

2. Send a code

// Your server: the user typed their email and asked for a code
const { otp_id } = await signops.otp.send({
  email: "ananya@example.in",
  app_name: "Kirana Express",   // shown in the email, up to 40 characters
  ip: req.ip,                   // for the per-IP limit
  // language: "hi" for Hindi
});
// Keep otp_id with the sign-in attempt, such as in the session, not in the page

The email holds the code, your app_name and your brand colour, and no links, so it cannot be used to phish.

3. Check the code

const { valid } = await signops.otp.verify({ otp_id, code: req.body.code });
if (valid) startSession(res, user.id);   // or mark the email confirmed

A right code answers valid: true once, then it is gone. A wrong one answers valid: false with tries_left. After five wrong tries the code is dead (too_many_tries); after 10 minutes it expires (expired). Either way, offer Send a new code.

Use inputmode="numeric" and autocomplete="one-time-code" on the field, so phones offer the code from the email.

4. Add your domain for live codes

Live codes go from your own domain. In Email OTP, Domain:

  1. Type your main domain (kiranaexpress.in, not a subdomain), the from name (Kirana Express) and the address before the @ (no-reply). Codes will come from Kirana Express <no-reply@kiranaexpress.in>.
  2. The page shows the DNS records. Add each at your DNS host:
Type Name Value What it is for
CNAME (three of them) <token>._domainkey.kiranaexpress.in <token>.dkim.amazonses.com Signs every code with your domain
MX, priority 10 auth.kiranaexpress.in feedback-smtp.us-east-2.amazonses.com Undelivered codes come back here
TXT auth.kiranaexpress.in v=spf1 include:amazonses.com ~all SPF for the bounce domain
TXT, only if you have none _dmarc.kiranaexpress.in v=DMARC1; p=none; Gmail and Yahoo expect a DMARC record

Copy the exact names and values from the dashboard: the <token> parts are your own.

  • Cloudflare: set each record to DNS only (grey cloud).
  • GoDaddy, Hostinger, Namecheap and others add your domain to the name by themselves. Type only the part before it, such as auth or abc123._domainkey.

The page shows Waiting for DNS, then Verified. From then on your live key sends from your domain. Live keys work once your SignOps account is approved.

5. Keep your sending healthy

Each app starts at 1,000 codes a day and moves up as it sends cleanly (10,000 after 7 days and 1,000 clean codes, then 1 lakh). Three numbers decide that, and they are on the Email OTP page:

  • Bounces: check the address format before sending, and never send to addresses you bought or guessed.
  • Spam complaints: send codes only when the user asked for one.
  • Codes typed back: a code nobody types back is often a bot filling your form. Put a captcha or a rate limit on the form if that number drops.

Past the warning line your webhook gets email.warning; past the pause line, codes stop for 24 hours (sending_paused). An address that bounces or complains is put on your do-not-send list and answers address_suppressed; remove it there if it was a mistake.

Limits per address and IP: 5 codes an hour to one address, 30 seconds apart, and 20 an hour from one IP when you pass ip. Change them under Email OTP, Settings.

A link signs the user in with one tap. It uses the same domain, limits and price:

await signops.magicLinks.send({
  email: "ananya@example.in",
  redirect_url: "https://kiranaexpress.in/auth/email",   // your page
  ip: req.ip,
});

// /auth/email?signops_token=... : your page shows a "Continue" button that POSTs the token
const { valid, email } = await signops.magicLinks.verify({ token: req.body.signops_token });

redirect_url must be https on your verified domain or a subdomain, or a host you list under Email OTP, Settings, Magic links (and http://localhost with a test key). Check the token from a button press, not when the page loads: some mail scanners open links before the user does.

Using a login system?

Frequently asked questions

More questions? Email us and the answer goes on this page.

hello@signops.dev
Why do codes have to come from my own domain?

Inboxes trust mail signed by the domain the user knows. Codes from your domain, signed with its DKIM key, land in the inbox and look like yours. It also keeps one customer's sending from affecting another's.

Will adding the DNS records change my existing email?

No. The records go on names Amazon SES gives (ending in ._domainkey) and on auth. under your domain. Your MX records, Google Workspace or Zoho mail are not touched. Add the DMARC record only if your domain has none.

Can I send from a subdomain such as mail.example.com?

Use your main domain. SignOps adds auth. under it for bounces, and users trust the domain they know.

How long does verification take?

Most DNS hosts take a few minutes. SignOps checks every 10 minutes and shows Verified when all records are found. If they are not found in 72 hours, it shows Records not found; check them and add the domain again.

Code or magic link?

A code works when the user reads email on another device, such as a laptop sign-in with mail on the phone. A link is one tap when they read email on the same device. Many apps send a code and offer a link as well.

Send your first push today.

Free for 1,000 push devices, 100 approvals and 1,000 passkey sign-ins a month, and 500 email codes to try. No card until you pass a free amount.