Email Link Scanners: Avoid Consuming One-Time Actions on a Preview

Email Link Scanners: Avoid Consuming One-Time Actions on a Preview

Design email action links so automated inspection does not consume a one-time action, with deliberate confirmation, authorization and clear recovery states.

SendDart Team

TL;DR

  • Do not treat an initial link fetch as the user's decision; design so passive scans cannot consume one-time tokens or perform sensitive state transitions that require deliberate user action.
  • Use a two-step flow: show an informative page on first navigation, then require an explicit, validated action (sign-in/accept) to complete the operation so scanning stays safe.
  • Define explicit token states and test the full recipient journey (passive visits, repeats, deliberate completion, expiry, revocation) to ensure scans do not accidentally change authoritative state.

A request to the link is not proof of a human decision

Email links can be inspected by security infrastructure before or around the time a recipient uses them. Microsoft documents URL scanning, rewriting and time-of-click checks in Safe Links. Other environments can behave differently, so the application should not equate the first request with the intended user's conscious action.

This matters when a URL consumes a one-time token, approves a change or performs another state transition. A design that changes business state merely because the link was fetched can produce confusing failures or unintended actions.

Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.

Separate viewing an action from performing it

For an ordinary account action, let the initial navigation show an appropriate page and require the intended operation through a deliberate, validated request. The HTTP semantics specification describes safe methods such as GET as essentially read-only in their requested semantics.

That does not mean a confirmation page alone solves every threat. Authentication, authorization, token validation and protections appropriate to the application still matter. The design principle is to avoid treating passive retrieval as sufficient evidence for a sensitive mutation.

Use this approach according to the workflow, not as an excuse to add unnecessary friction to every harmless link.

Work through a team-invitation example

Imagine an email inviting a person to join a workspace. The link opens an invitation page that identifies the workspace and explains the next step. It does not consume the invitation just because the page is viewed.

The recipient then signs in or establishes the required identity and deliberately accepts. The server checks that the invitation is valid, belongs to the intended relationship and has not already been revoked or used. Only the acceptance operation changes membership.

If a scanner visits the initial URL, the invitation remains available. If a person forwards the email, possession of the link should not silently bypass whatever identity and access policy the workspace requires.

Define token lifecycle states explicitly

Write down valid, expired, revoked and already-used states. Decide what each state means for the user interface and which operations are permitted.

For the invitation example, an expired link could offer a safe way to request a replacement. An already accepted invitation could guide the authorized user to the workspace. A revoked invitation should not be revived simply because the browser reloads an old page.

Keep the transition atomic where the application requires one-time use. Two near-simultaneous acceptance attempts should not create inconsistent membership or consume unrelated state.

Do not rely on recognizing every scanner

User-agent strings, timing patterns and network addresses can be useful diagnostic clues, but they are not a dependable authorization boundary. A list of known scanners will be incomplete and may change.

Prefer a workflow that remains correct when an unknown client retrieves the initial page. Then use diagnostics to understand unusual traffic rather than to decide whether a sensitive action is safe.

This also makes tests clearer. You can simulate repeated passive requests and verify that they do not change the action state without needing to impersonate every recipient security product.

Treat tracking and action completion separately

A recorded click can represent a request observed by a tracking system. It should not automatically mean that the recipient accepted an invitation, reviewed a document or completed an account change.

Record the actual business completion on the server that performs the operation. Use that event to stop reminders or update workflow status. Keep click analytics as a separate signal with its limitations.

If link rewriting or tracking is involved, inspect the complete route to the action page. Do not place sensitive action state in a fragile chain that depends on a tracking request being interpreted as a human confirmation.

Preserve the requirements of special-purpose endpoints

Some email protocols intentionally define machine-triggered behavior. One-click unsubscribe is a distinct case with its own specified request flow; it should not be broken by adding an arbitrary login or confirmation step to the protocol endpoint.

Separate that endpoint from ordinary account-action links and implement its documented requirements. The one-click unsubscribe specification describes the relevant mechanism. The general lesson is to understand the action contract, not apply one universal interaction pattern to every URL in an email.

Test the complete recipient journey

Test a passive visit, repeated visits, deliberate completion, a second completion attempt, expiry and revocation. Include the case where a recipient opens the email after the task was already completed elsewhere. Check that refreshing the completion page does not replay the mutation and that a second device sees the same authoritative state.

The message and destination should explain the current state without blaming the user for an “invalid link” when the actual problem is that it was consumed prematurely. SendDart delivers the action URL your application supplies; the application owns its lifecycle, authorization and recovery behavior. Make that boundary explicit before sending the first production message.

Related reading: Email Verification APIs: Compare Results, Uncertainty and Integration.