How to Send Magic Link, OTP & Verification Emails
Send email verification, magic links, and one-time passcodes with Notify — you own the tokens, we deliver the message.
Auth emails are the same job: generate a short-lived secret in your app, put it in a message, send it. Notify delivers the email. You verify the token when the user returns.
This guide covers email verification, magic links, and OTP / one-time codes with the same Notify send call.
Prerequisites
- API key from Credentials
- A way to store hashed tokens (database or cache) with expiry
- For production
fromaddresses: a verified domain
Shared pattern
- Create a random token or numeric OTP in your backend
- Store a hash of it with
user_idandexpires_at(never store the raw secret long-term if you can avoid it) - Build HTML or plain text that includes the link or code
POST https://notify.cx/api/email/sendwithto,subject,message- On submit/click, validate the secret against your store, then invalidate it
Notify does not issue tokens or manage sessions — only delivery.
Email verification
async function sendVerificationEmail(user: { email: string; id: string }) {
const token = crypto.randomUUID();
await db.emailTokens.create({
userId: user.id,
type: 'verify',
tokenHash: await hash(token),
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24)
});
const verifyUrl = `https://yourapp.com/verify-email?token=${token}`;
await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
to: user.email,
subject: 'Verify your email',
message: `<p>Confirm your email:</p><p><a href="${verifyUrl}">Verify email</a></p><p>This link expires in 24 hours.</p>`,
from: 'noreply@your-verified-domain.com'
})
});
}
Magic link (passwordless sign-in)
Same as verification, but the callback creates a session instead of flipping email_verified.
const magicUrl = `https://yourapp.com/auth/magic?token=${token}`;
await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
to: email,
subject: 'Your sign-in link',
message: `<p><a href="${magicUrl}">Sign in to Your App</a></p><p>Expires in 15 minutes. If you did not request this, ignore this email.</p>`,
from: 'noreply@your-verified-domain.com'
})
});
Use short TTLs (5–15 minutes). Single-use tokens only.
OTP / one-time code
Prefer 6-digit codes for mobile-friendly flows. Hash before storage.
const code = String(Math.floor(100000 + Math.random() * 900000));
await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
to: email,
subject: `${code} is your verification code`,
message: `<p>Your code is <strong style="font-size:24px;letter-spacing:4px">${code}</strong></p><p>It expires in 10 minutes.</p>`,
from: 'noreply@your-verified-domain.com'
})
});
Putting the code in the subject helps users who only glance at the inbox list.
Deliverability tips
- Send from a verified domain with aligned SPF/DKIM
- Keep copy short; avoid marketing language in auth mail
- Include why the email arrived (“you signed up” / “you requested a sign-in”)
- Rate-limit requests per email to slow abuse
- Watch logs and webhooks for bounces