You start with a simple ticket in your backlog: "Add passwordless login." It sounds like an afternoon's work. You'll just send a tokenized link to the user's email, they click it, and they're in. No forgotten passwords, no friction. This is the promise of magic link authentication, but the gap between a prototype and a production-ready system is wider than most developers expect.
The technical reality hits when you realize you need to choose an identity provider, find a transactional email service, verify your domain with SPF and DKIM, and write the logic to bridge those two systems. You’re now managing two different API keys, two different uptime statuses, and two different billing cycles. If the email doesn't send, your auth logs look fine but the user is stuck. If the auth service is down, your email logs show success for a link that will never work. This is the "Glue Code Tax"—the hidden engineering overhead of forcing separate services to talk to each other just to perform a single logical action.
At Zyphr, we believe your auth provider should inherently know how to communicate. By unifying identity and messaging, we eliminate the glue code. You can trigger a magic link with a single SDK call that handles both the token generation and the physical delivery.
Why magic link authentication is harder than it looks
Magic links aren't just "long URLs." They are a cryptographic handshake designed to prove identity without requiring a pre-shared secret like a password. When you request a magic link, Zyphr generates a high-entropy, cryptographically secure token. This token is short-lived and, crucially, single-use.
The security of this flow relies on "out-of-band" verification. You’re trusting that only the person with access to the specific email address can retrieve the link. Once the user clicks that link, they are sent to your specified Redirect URL. This URL carries the token as a query parameter, which your application then exchanges for a session.
The logic is delicate. If the token is too short, it's susceptible to brute force. If it lasts too long, it’s a liability in an inbox. Zyphr handles this lifecycle automatically, mapping the token to a specific user and ensuring that as soon as the session is created, the token is invalidated. You can learn more about Zyphr’s unified authentication features to see how this integrates with our broader delivery pipeline.
Beyond the token itself, you have to worry about the "Link Prefetcher" problem. Modern email clients like Outlook and Gmail often use "safe link" scanners that automatically visit URLs in an email to check for malware before the user even opens the message. If your authentication logic simply expires the token on the first GET request, the user will click the link only to find an "Expired Token" error because the bot already used it. Solving this requires an intermediate landing page or a POST-based verification step, adding another layer of complexity to your "simple" ticket.
Initializing the SDK for magic link authentication
To get started, you’ll need the @zyphr/sdk package. We built it to be lean and predictable—there are no heavy dependencies or complex configuration objects.
In a typical Node.js or Next.js environment, you should initialize the Zyphr client as a singleton. This prevents multiple connection overheads and makes it easy to import across your server-side logic.
import { Zyphr } from '@zyphr/sdk';
/**
* Initialize the client with your secret API key.
* Keep this in your .env file—never expose it to the frontend.
* The SDK uses a single instance to manage both identity and delivery.
*/
const zyphr = new Zyphr(process.env.ZYPHR_API_KEY as string);
export default zyphr;
By keeping the instance in a shared utility file, you ensure that your auth, email, and subscriber management all use the same authenticated pipe. This centralization is what removes the need to synchronize state between different third-party vendors.
Triggering the magic link and automated email
In a traditional stack, you’d have to generate a token in your database, construct a URL, and then call a service like SendGrid or Postmark to deliver it. With Zyphr, that 50-line mess becomes a single method call.
The zyphr.auth.sendMagicLink method handles the heavy lifting. It identifies the user (or creates a new one), generates the secure token, and triggers the transactional email through our internal delivery infrastructure. There is no SMTP to configure and no third-party API to hook up.
// pages/api/auth/magic-link.ts
import zyphr from '@/lib/zyphr';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { email } = req.body;
if (!email || !email.includes('@')) {
return res.status(400).json({ error: 'Valid email is required' });
}
try {
// This call handles token generation AND physical email delivery
await zyphr.auth.sendMagicLink({
email: email,
redirectUrl: 'https://myapp.com/auth/callback',
templateId: 'welcome-magic-link',
expiresIn: '15m', // Short expiry is better for security
metadata: {
signup_source: 'web_app'
}
});
return res.status(200).json({ message: 'Check your inbox for a login link' });
} catch (error) {
console.error('Magic link delivery failed:', error);
return res.status(500).json({ error: 'Failed to process authentication request' });
}
}
This one call replaces the entire orchestration layer. Zyphr knows who the user is, which template to use, and how to route the email so it actually hits the inbox rather than the spam folder.
Customizing the delivery layer
A magic link email shouldn't look like a plain-text automated alert. It’s often the first touchpoint a user has with your app. Zyphr provides a visual editor that uses Handlebars for dynamic content, allowing you to design emails that match your brand identity.
Inside the editor, you use the {{magic_link}} variable. When zyphr.auth.sendMagicLink is called, we inject the unique, tokenized URL directly into that placeholder. You can also pass custom variables—like a user's name or a referral code—to personalize the experience.
Beyond aesthetics, you need to handle delivery logistics. In the Zyphr dashboard, you can configure your domain's SPF, DKIM, and DMARC settings. We give you the DNS records; you paste them into your provider. This ensures that your magic links carry the reputation of your own domain, which is vital for high delivery rates. If your links end up in the "Promotions" tab or spam, your login flow is effectively broken.
When you use separate services for email and auth, you often run into "reputation mismatch." An email sent from one domain with a link pointing to a different auth domain can trigger phishing filters in strict corporate environments. Zyphr’s unified approach ensures the sender domain and the link domain are aligned, which significantly improves inbox placement.
Handling the callback and session creation
When the user clicks the link in their inbox, they arrive at the redirectUrl you defined (e.g., https://myapp.com/auth/callback?token=xyz). Your frontend needs to capture this token and send it back to your server to verify it.
The zyphr.auth.verifyMagicLink method is the final step of the handshake. It validates that the token is authentic, hasn't expired, and hasn't been used before. If everything checks out, it returns a JWT session and the user's profile.
// pages/api/auth/callback.ts
import zyphr from '@/lib/zyphr';
export default async function handler(req, res) {
const { token } = req.query;
if (!token) {
return res.redirect('/login?error=missing_token');
}
try {
const { data, error } = await zyphr.auth.verifyMagicLink({
token: String(token)
});
if (error) throw new Error(error.message);
/**
* The data object contains:
* - access_token: The JWT for API requests
* - refresh_token: Used to generate new access tokens
* - user: The user profile (id, email, etc.)
*/
// Set these in secure, HTTP-only cookies
res.setHeader('Set-Cookie', [
`session=${data.access_token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600`,
`refresh=${data.refresh_token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=604800`
]);
return res.redirect('/dashboard');
} catch (error) {
console.error('Verification error:', error);
return res.redirect('/login?error=invalid_or_expired_token');
}
}
Zyphr’s session management handles the rotation of refresh tokens and configurable expiry windows. You don't have to worry about the underlying JWT logic unless you want to. For more complex scenarios—like custom claims or long-lived sessions—you can check out the full authentication documentation.
Security best practices for magic link authentication
Passwordless auth is resilient, but it isn't "set it and forget it." You need to protect your endpoints from abuse.
First, strictly enforce token expiry. We recommend 15 minutes or less. A magic link sitting in an inbox for 24 hours is a liability. If the user doesn't click it immediately, they can easily request another one. The short window minimizes the opportunity for an attacker to exploit a compromised email account.
Second, implement rate limiting. Without it, a malicious actor could script your /auth/magic-link endpoint to spam a user's inbox with hundreds of emails. This isn't just annoying; it can destroy your domain reputation and get your emails blacklisted by major providers. Zyphr includes built-in protection against rapid-fire requests for the same email address, but you should also implement IP-based rate limiting on your own API routes to prevent resource exhaustion.
Example of a basic rate limit header check:
// Add this logic to your magic-link trigger endpoint
const rateLimitReached = await checkRateLimit(req.headers['x-forwarded-for']);
if (rateLimitReached) {
return res.status(429).json({ error: 'Too many requests. Try again in an hour.' });
}
Finally, solve the "Link Prefetcher" issue mentioned earlier. Instead of having the email link point directly to your verification API, point it to a "Confirm Login" page on your frontend. This page should require a user action—like clicking a "Sign In" button—before calling the verifyMagicLink API. This ensures that a bot checking the link doesn't accidentally log the user in or expire the token prematurely.
Strengthening the flow with MFA
Magic links provide an excellent user experience, but for high-security applications, they should be the starting point, not the finish line. Because Zyphr is a multi-channel platform, adding a second factor is straightforward.
Once you have the magic link flow working, you can enable TOTP (Time-based One-Time Password) or SMS-based MFA directly in your dashboard. Since you're already using the Zyphr SDK, adding an MFA check to your login flow doesn't require a new provider—it’s just another namespace in the same library. You can verify the magic link first, and if the user has MFA enabled, prompt them for their code before issuing the final session cookie.
Stop fighting your infrastructure and adopt a system where your identity and messaging layers work as one. Whether you're sending your first ten magic links or ten million, the goal is the same: get the user into your app without the friction of passwords or the headache of glue code.
To see this in action, head over to your Zyphr dashboard and create a new email template. Once your template is ready, use the SDK code above to send a test link to yourself. You can then try layering on TOTP for your test user to see how easily security scales with your application.