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.
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:
- In the dashboard, open Email OTP, Settings, and add your own email under Test addresses.
- Use your test key (
sk_test_...). Test codes go fromcodes@auth.signops.devto 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:
- Type your main domain (
kiranaexpress.in, not a subdomain), the from name (Kirana Express) and the address before the@(no-reply). Codes will come fromKirana Express <no-reply@kiranaexpress.in>. - 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
authorabc123._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.
6. Magic links instead of codes
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.