Every login screen asks you the same thing, to prove it's you. For years, the correct answer was a password no one else knows. But passwords get reused, phished, forgotten, and dumped in breach lists, and each one you use has a chance of getting leaked. Another annoying scenario is when you click "Forgot password?" and end up with a password reset email that never reaches the inbox.
Magic links take a different route. Instead of requiring the user to remember a password, you email them a one-time link that signs them in when they click it. No password to type, store, or leak.
This guide will show what magic links are, how the authentication flow actually works under the hood, and how to send them reliably with UniOne over the Web API or SMTP, including a working Node.js demo you can run end to end.
What are magic links?
A magic link is a one-use URL emailed to a user that allows them in without a password. The link includes a unique token, and when the user clicks it, your backend checks the token and starts a session.
This is what people mean by passwordless authentication. You replace "something you know" (a password) with "something you have" (access to a mailbox). The same idea powers tools you already use, such as Slack, Notion, and Substack, which all offer the option to sign in this way, and we’ve highlighted some of the examples for such flows in our more detailed article explaining magic links and how they work.
Passwords vs OTP codes vs magic links
It helps to see passwordless login next to the two methods it competes with.
|
Passwords |
OTP codes |
Magic links |
|
|
What the user does |
Types in a memorized secret |
Copies a short code from email/SMS |
Clicks a link in their email |
|
Main strength |
Familiar, works for offline apps |
Quick, no link to misroute |
Lowest friction, nothing to type |
|
Main weakness |
Reused, phished, forgotten passwords |
Code can be read aloud to an attacker |
Needs fast, deliverable email |
|
Server stores |
Password hash, indefinitely |
Short-lived code |
Short-lived token hash |
|
Reset burden |
High (forgotten password flows) |
None |
None |
To learn more about sending OTP emails via UniOne, check out this guide.
Why startups are switching to passwordless login
The pitch for magic link authentication is mostly about removing friction at the moment a user decides that your product is worth their attention.
Benefits
Onboarding gets faster because there's no "create a password, retype it, meet these four rules". The user types in an email, and they're in. That removes the highest-friction field from a signup form, leading to fewer abandoned signups.
It also eliminates password-reset traffic entirely, because there's no password to reset. This removes one of the most common support requests and one of your highest-volume transactional emails.
Logins become mobile-friendly, since tapping a link beats typing a strong password on a phone, and the attack surface shrinks. No stored passwords means no password database to leak and nothing for credential-stuffing attacks to try.
Use case
Magic links shine in SaaS products and B2B platforms where logins are occasional and security matters.
They're also a clean fit for mobile apps and CMS-based projects. The one place to think twice is high-frequency consumer apps where users log in many times a day, and waiting for an email would annoy them. There, passkeys or persistent sessions may serve better.
How magic link authentication works (step by step)
Here's the magic link authentication flow from the user's first click to a live session:
- A user enters their email on your login screen and submits.
- Your backend generates a token. This is a long, cryptographically random string.
- You store the token securely with an expiry time, linked to that email. (Store a hash of it, not the raw value.)
- UniOne sends the email containing the link, with the token baked into the URL.
- The user clicks the link, which opens a verification route in your app.
- Your backend validates the token: does it exist, is it valid, has it been used before?
- A session is created, the token is marked as used, and the user is logged in.
The token's lifecycle
The token is the entire security model, so its properties matter:
- It expires fast: A short window, about 15 to 30 minutes, is a reasonable default as it limits how long a leaked link remains dangerous. Shorter expiry reduces risk but can frustrate users who don't check email immediately.
- It's single-use: The first successful click consumes it. Any subsequent click does nothing.
- It's unguessable: Current guidance is at least 256 bits of cryptographically random data, far beyond what brute force can reach.
- It's stored hashed: If your database leaks, attackers get hashes, not live tokens. The same reasoning is behind hashing passwords.
How does this prevent replay attacks?
A replay attack is when someone captures a valid link and reuses it. Single-use tokens battle this directly. Once the legitimate click consumes the token, any replayed copy is already dead. Add the short expiry time and HTTPS-only links (so the token never travels in plaintext), and the window for abuse is tiny.
Why transactional email infrastructure matters for magic links
With magic links, the email sent to the user acts as the initial login button. If the email is slow to arrive, the user sits on a "check your inbox" screen, wondering if your product is broken. If it lands in spam, they can't log in at all.
Deliverability and sender reputation
A few things emerge from treating email as the login button. A delayed email equals a broken login; even a 10 to 15 seconds delay can feel like a failure to someone mid-signup, so delivery speed is important.
Spam placement blocks authentication outright. An auth email landing in spam is a user locked out of your product. And your sender reputation determines whether your auth email lands in spam, because mailbox providers route your mail based on your domain's standing, so deliverability directly affects conversion.
This is why authentication mail should ride on infrastructure built for transactional traffic. If you want to go deeper on inbox placement, UniOne has a full guide on email deliverability.
SPF, DKIM, and DMARC
Reliable auth mail also depends on authenticating your sending domain with three dedicated DNS records:
- SPF lists which servers may send on behalf of your domain.
- DKIM cryptographically signs your email so recipients can verify it hasn't been altered.
- DMARC tells receivers what to do when SPF or DKIM fails, and provides reporting.
Another two practices keep the auth mail reliable:
- Send from a dedicated subdomain, like auth.yourapp.com
- Keep transactional and marketing traffic isolated, so a promotional campaign doesn't drag down the deliverability of the emails people need to log in.
How to send magic links with UniOne via the Web API
By the end of this section, you'll have a small Node.js app that sends a real-life magic link email through UniOne, verifies the click, and logs the user in without a password.
For an authentication system, the Web API is almost always the right integration choice over SMTP. It gives you:
Structured error handling;
- A returned job_id you can trace in your logs;
- Metadata you can attach to each send;
- An idempotence_key to stop accidental duplicate emails;
- Throughput to scale as signups grow.
Check out UniOne's full API method reference in Email API documentation.
Prerequisites
- Node.js v18 or newer installed.
- A UniOne account with a verified sender domain.
- Your UniOne API key, from the account settings page.
Before getting started with the implementation, do the following:
- Sign up for a free UniOne account here.
- Add and verify your domain name. For a detailed procedure, refer to our guide on Setting up the domain’s DNS records. Alternatively, as a new user, you can use the free, pre-verified sandbox domain, which is a temporary domain for testing your email sending functionality before connecting your production domain.
Step 1: Create the project
Open a terminal window and run these commands one at a time:
|
mkdir magic-link-demo && cd magic-link-demo |
That last line installs the only three dependencies we need. Now, create a file called .env in the project folder for your secrets:
|
UNIONE_API_KEY=your-unione-api-key-here |
- UNIONE_API_KEY: To get your API key, navigate to Settings -> SMTP Configuration. Click API key at the top of the page and copy your key into the project.
- FROM_EMAIL: For this tutorial, you can use the free, pre-verified sandbox domain provided by UniOne, for example, john@sandbox-7046866-f66e37.unionemailer.com.
Step 2: Generate and store the token
This is the security core of the system. Create a lib/tokens.js file, then paste in the code below:
|
// lib/tokens.js |
The raw token is what travels in the email link, but only its SHA-256 hash is ever stored. If your database is leaked, an attacker would get only hashes they can't practically reverse into working links. The used flag guarantees single use, and expiresAt enforces the 15-minute window.
You'll also need a place to store sessions once a login succeeds. Create lib/sessions.js file with the code below:
|
// lib/sessions.js |
This file keeps a user logged in after they click the magic link, so they aren't re-verified by email on every page.
createSession generates a long random ID, stores it server-side against the user's email, and returns the ID (which server.js puts in an HttpOnly cookie); the cookie holds only that meaningless ID, never the email.
getSession looks up the ID from an incoming request to confirm who's logged in, and destroySession deletes the record, which is logout. It's an in-memory Map with no real expiry, so it resets on restart, which is fine for the demo, but production would use Redis or a database.
Step 3: Send the email through UniOne Web API
This is where UniOne comes in. Create lib/email.js file, then paste in the code below:
|
// lib/email.js |
Four details in that payload are worth a closer look, because they are what differs a demo code from a production auth integration:
- substitutions are UniOne's template variables. We pass magic_link and expires_in, and UniOne inserts them into every {{magic_link}} and {{expires_in}} placeholder in the body. The substitution syntax uses double curly braces around the variable name.
- track_links: 0 turns off click tracking, and for an auth email this is non-negotiable. Link tracking converts your URL into a redirected one, which means your single-use token can be consumed by a link scanner or logged somewhere you don't want it. Always send authentication links untracked.
- idempotence_key is UniOne's safeguard against duplicate sends which usually occur when an app retries an API request. If UniOne receives two requests with the same key, the second one returns the result of the first without sending another email. We tie it to the token so a retried request for the same login can't be sent twice.
- job_id comes back on success. A successful send returns a JSON structure with status: "success", a job_id, and a list of accepted emails. Store it next to your login attempt so you can check whether that exact email was delivered, bounced, or marked as spam.
Step 4: Verify the click and create a session
Now create server.js, which ties everything together: the request route, the verify route, your protected page, and logout.
|
// server.js |
This is the main server that wires the whole flow together. It sets up Express, pulls in the token, session, and email helpers, and serves the login page from public.
The /auth/magic-link route handles a login request. It cleans up the email, rate-limits to one link per email a minute (always returning ok so no one can check whether an account for this email actually exists), creates a token, builds the verify link, and hands it to UniOne for sending, logging the returned job_id.
The /auth/verify route runs when the user clicks that link: it consumes the one-time token, and on success creates a session and drops the ID into an HttpOnly cookie before redirecting to the dashboard.
Finally, requireAuth gates the protected /dashboard by checking that cookie's session, /logout deletes the session and clears the cookie, and app.listen starts the server on port 3000.
Step 5: Create login page
Create a public/index.html folder, containing the code below:
|
<!doctype html> |
This is what the users see during login.
Now, to test our application, run the command below:
|
node server.js |
Open http://localhost:3000 in your browser, and you should see the demo app rendered. Type an email you can check, and click Email me a link.
Now, check your inbox. You should see the auth email arrive within a few seconds:
Click Sign in, and you’ll be automatically logged in without a password:
Click the same link a second time, and it's refused, because the token was consumed on the first click.
Tracking delivery with webhooks
Sending the email is only half the job. For auth traffic, you also want to know what happened to it. UniOne webhooks push delivery events to a URL you control. A callback typically carries the job_id, the recipient email, the status, the event time, and any metadata you attached at send time, so you can match an event back to a specific login attempt.
A job_id is created every time someone clicks Email me a link. You can see this in your terminal:
A minimal callback handler looks like this:
|
app.post("/webhooks/unione", express.json(), (req, res) => { |
You register that endpoint once, either in the UniOne dashboard or with the webhook/set.json API method, and from then on, every send reports back. The statuses that matter most for login email are delivered, soft_bounced, hard_bounced, and spam; a hard bounce is your cue to nudge the user toward a different email or login path.
Sending magic links via SMTP
Not every project has a backend you can add API calls to. If your page lives inside WordPress, a CMS, or an off-the-shelf plugin, SMTP is often the easier and faster route. You point the system at UniOne's SMTP server, and your emails start flowing through it.
When SMTP is enough
Reach for SMTP when the magic link logic is handled by software you cannot modify, such as a WordPress login plugin, a CMS membership module, an LMS, or a CRM that already has an SMTP settings panel. In such cases, you're not building the token flow yourself. The plugin does this for you. And you just need a reliable way to get its emails delivered.
For a brand-new application, though, the Web API from the previous section is better long-term, because of the metadata, idempotence, and tracking you get with it.
SMTP settings and TLS
UniOne exposes a standard SMTP interface, so most tools need only the following host, port, credentials, and encryption settings to complete setup:
- Host: smtp.eu1.unione.io or smtp.us1.unione.io (use the one matching your account region; confirm it on your dashboard's SMTP page)
- Port: 465 or 587
- Encryption: TLS; UniOne accepts only secure connections
- Username: your user ID or project ID
- Password: your API key or project API key
Configure those once, and now your platform's system mail (logins, magic links, resets, notifications, etc.) routes through UniOne. Further details can be found on the SMTP Service page.
The magic link email template, security, and common mistakes
A magic link email has exactly one job: offer the user a single button. Get rid of everything else. The fewer elements in the message, the better it looks on mobile and the cleaner it looks to spam filters.
A plaintext version should be just the essentials:
|
Sign in to Your App |
For the HTML version, the UX rules are pretty straightforward; make the call-to-action button the obvious focal point, state the expiry time plainly so the user knows the link won't wait forever, and include a phishing-awareness line, "if you didn't request this, ignore it", so a stray email doesn't alarm anyone.
Avoid marketing imagery, footers full of links, and anything that competes with the button.
Security checklist
Most of this was already discussed; here is a checklist you can audit against:
- Short expiry: 15 minutes is a sensible default.
- One-time use: consume the token on first click; reject every click after.
- HTTPS-only links: the token should never travel in plain text.
- Hash tokens at rest: store the SHA-256 hash, never the raw token.
- Rate limit requests: cap how often one email can request a link, to blunt abuse.
- Don't leak account existence: return the same response whether the email has an account or not.
- Avoid open redirects: never build the post-login destination from an unvalidated URL parameter.
- Log suspicious activity: repeated failures or requests from odd locations are worth flagging.
Common mistakes
The caveats that actually bite teams in production are:
- Expiry time too long: A link valid for hours is a link that an attacker has hours to use.
- No SPF/DKIM/DMARC, or sending from a shared domain: This is the fastest way into the spam folder, and for login email, the spam folder means locked-out users.
- Mixing auth and marketing traffic: One promotional campaign's complaint can sink the reputation your login emails depend on.
- Leaving link tracking on: Click tracking rewrites the URL and can make a scanner consume the one-time token before your user clicks.
- The cross-device trap: A user requests the link on their laptop but opens an email on their phone; if your flow assumes the same browser, the sign-in breaks. Either let the link create a session from wherever it's opened, or offer a short code as a fallback.
- Dead ends on expired links: An expired link should land on a friendly "request a new one" page, not a raw error.
How UniOne helps scale Magic Link infrastructure
This demo integration will keep working as login volume grows, because it runs on infrastructure built for high load transactional traffic.
UniOne can send up to 60 million emails per hour, and you stay flexible on how you connect. Templates keep email copy out of your code. You can keep them in UniOne and reference them by ID, with a substitution engine that handles both simple variables and complex Apache Velocity logic.
Visibility is what makes that volume manageable. UniOne logs every send, delivery, and bounce, each timestamped and tied to the metadata you attached, so your metadata and job_id help track any single login attempt.
Real-time webhooks push events the moment they happen, so you can nudge the user to another path if you see a hard bounce. Dashboards and CSV export cover reporting and debugging. The Projects feature isolates multiple environments under one account, each with its own API key, so staging never pollutes production.
Conclusions
Magic links take the password out of the login process, removing a whole category of friction and risk. The engineering is simple: a random token, short expiry, single use, and a session, but it only works if the email arrives fast and lands in the inbox, because here the email is the login.
That's the part UniOne handles, and the same integration scales without a rewrite. You get server-side templates, webhooks, and analytics for live delivery insight, metadata, and job_id for tracing any send, and Projects to keep products cleanly separated. Built for transactional volume, the service takes you from a handful of logins a day to thousands per minute.
Use the Web API when you're building a backend and want metadata and traceable job_ids; use SMTP when your CMS owns the flow. Keep the expiry short, tracking off, and tokens hashed, and you have passwordless login that's easier for users and harder to attack than the password mechanism it replaces.
Related Services
- Transactional Email Service: Send magic links, login alerts, password resets, and other transactional emails on infrastructure built for high deliverability and speed.
- Email API for Developers: Integrate sending directly into your backend, SaaS, or mobile app, with templates, metadata, webhooks, and analytics.
- SMTP Service: Connect WordPress, Drupal, online stores, CRMs, and other CMS platforms over SMTP, no custom API work required.
- Email Deliverability Services: Protect sender reputation and inbox placement so authentication email reaches users reliably.
- Transactional Email Templates: Ready-made templates for login links, OTP codes, resets, and other security notifications, editable by drag-and-drop or HTML.
FAQ
What is a magic link?
A magic link is a one-time-use URL emailed to a user that signs them in when clicked, with no password required. It carries a short-lived cryptographic token that your backend validates before creating a session.
Are magic links more secure than passwords?
In many ways, yes. There's no password to reuse across sites, phish, or expose in a breach, and the token is single-use and expires quickly. The security shifts to the user's email account, so the link's short lifespan and one-time use are what keep the attack window small.
Keep in mind, however, that this mechanism is as secure as the user’s mailbox; it’s up to the user to keep their mailbox access safe from malicious actors.
How long should a magic link remain valid?
A shorter window is best. 15 minutes is a common default, while most teams stay within 30 minutes. Long enough that a user who checks email in a few minutes isn't inconvenienced, short enough that a leaked link is rarely still usable.
Can magic links be sent through SMTP?
Yes. If your login is handled by WordPress, a CMS, or another SMTP-capable code, you can route those emails through UniOne over SMTP. For a custom backend, the Web API is generally preferable for its metadata, idempotence, and tracking.
Why are my magic link emails landing in spam?
The reason is usually missing or misconfigured SPF, DKIM, and DMARC records, sending from a shared or unverified domain, or mixing authentication mail with marketing traffic. Send from a verified, dedicated subdomain on transactional infrastructure to avoid most problems.
Can UniOne track magic link email delivery?
Yes. Real time webhooks and event logs show status data such as delivered, soft bounce, hard bounce, and spam complaint, and each send returns a job_id you can store and trace. For example, if a webhook signals a hard bounce, offer the user another way in.
What is the difference between OTP and magic link authentication?
Both deliver a short-lived credential by email, but the last step differs. With an OTP the user enters a code into your app; with a magic link, clicking the link does the verification. Magic links are smooth; OTP codes work better when the email is opened on a different device.