QA Testing

How to Test Transactional Email Deep Links on Android

How do you test transactional email deep links on Android? Use disposable inboxes, then verify Intent filters, routing order, screen, parameters, fallback.

Pankaj KumawatPankaj KumawatPublished
Illustrated cover for How to Test Transactional Email Deep Links on Android: an envelope with code brackets representing email testing.
Illustration: Tempmail editorial teamQA Testing
Share
Skip to article ↓
On this page6 sections

Generating Disposable Addresses for QA

A clean test starts with an inbox you can discard after the run. For transactional flows — password resets, order confirmations, verification codes — the address is part of the test fixture: it must be unique per run, easy to recognize in a list of test messages, and disposable when the run ends.

The Tempmail API at tempmail.codaipro.com supports both ends of that workflow. Two authentication modes are documented: keyless anonymous access for instant browser-based test mailboxes, and bearer token access for automated CI pipelines with elevated rate quotas. In practice, a human tester can spin up an address from a browser with no setup, while a pipeline that regenerates addresses on every build should authenticate with a token.

Address structure gives you organization for free. The API generates addresses in the form {namespace}.{tag}@inbox.testmail.app, where the tag is customizable. Treat the two segments as a small naming scheme:

  • namespace — the product or environment, e.g. checkout or staging.
  • tag — the specific transactional flow and run, e.g. reset-2024-06-11-a1.

So checkout.reset-2024-06-11-a1@... tells you at a glance which flow and which run a message belongs to, without opening it. This matters once several flows are in flight and you need to know which inbox received which link. The Tempmail documentation covers generating and monitoring these disposable inboxes in more detail.

One constraint worth stating plainly: a disposable address is a test identity, not an account identity. Do not register a real user account, attach it to real order history, or use it anywhere you would need to recover access later. If your test scenario requires returning to the same mailbox hours or days later, confirm first how long the inbox lives and whether saving a tag preserves access — that is addressed in the FAQ below. For flows that outlive the mailbox, a persistent test alias is the safer fixture.

With the address generated, trigger the transactional flow under test: request a password reset, place a test order, or otherwise cause the system to send mail. Then monitor the generated inbox for the message.

While you wait, it helps to know what the message link is actually doing on Android. Deep linking on Android runs on the Intents system: an app declares an Intent filter in its manifest that matches the link’s URI, and Android routes the tapped link to apps interested in that URI (Android developer documentation). So the tap itself is not magic — it is a URI match against declared filters.

The routing decision, per the same documentation, follows a fixed order when a user taps a deep link:

  1. Open the user’s default app for that URI, if one is set.
  2. Otherwise open the app if it is the only available app for the URI.
  3. Otherwise show a disambiguation dialog letting the user choose.

This order is the single most useful thing to keep in mind while tapping, because it predicts what you should see. On a device with only your app installed and a matching filter, the link should open the app silently. On a device with a second app claiming the same URI pattern, you should get a chooser. If you get neither, the filter match failed somewhere.

Practical tapping checklist per message:

  • Tap the link once from the inbox view and record what happens: app opens, browser opens, chooser appears, or nothing.
  • Note whether your device has a default app set for that link type, since rule 1 above changes the outcome.
  • Note how many apps on the device could plausibly claim the URI.

Authoring side: if you are building the emails yourself, note that some ESPs expose deep links as a first-class editing feature. Adobe Journey Optimizer, for example, lets you insert a deep link through the Email Designer (insert link → Deeplink) or via a personalization editor HTML snippet using data-nl-type="DEEPLINK" (Adobe documentation). That is marketing-email tooling, but the same authored URI ends up in a message body regardless — the QA step is verifying where that URI lands on Android.

Verifying App Navigation and Fallback

Opening the app is only half the test. The other half is confirming the app landed on the exact screen the URI specified.

For each link, verify three things in order:

  1. Launch target — the app opened at all, rather than the browser.
  2. Screen identity — the screen matches the deep link’s path and parameters (reset token screen, not the generic home screen; the specific order, not an order list).
  3. Parameter handling — tokens, IDs, and error codes from the URI survived into the destination screen.

Then test the fallback path, where most real-world failures surface. When the app is not installed, tapping a deep link on Android falls back to the browser or surfaces the disambiguation dialog described earlier (Android routing rules). Uninstall the app, or use a device that never had it, and tap the same link. Record what the user sees. A link that opens a browser to a blank page, a 404, or an app-store dead end is a real user-facing defect even though “app not installed” is a legitimate state.

Link expiry deserves its own pass. Transactional links — especially password resets — typically carry short time limits, so run a timing test: tap a link immediately, then trigger a fresh email and tap it after the expiry window has elapsed. Record the behavior: does the app open and show an error? Does the browser show an error page? Is the message clear enough for a real user to act on?

One honest limitation: the Android documentation, the Tempmail docs, and the ESP configuration guides all describe routing and configuration, not expiry semantics. None of them specifies how an expired link should behave. Expiry criteria have to come from your own product spec — decide what the correct post-expiry experience is first, then test against it. Do not assume the platform handles it for you.

Failures cluster into a few recognizable buckets. Work through them in this order; each step eliminates a class of cause.

# Symptom Likely cause Check
1 Nothing happens on tap URI does not match any declared filter Manifest Intent filters vs. the exact URI in the email (scheme, host, path)
2 Browser opens despite app being installed Filter mismatch or the link is a plain web link Compare the email’s URI against the filter’s declared host/path patterns
3 Disambiguation dialog appears unexpectedly More than one app claims the URI Decide whether a chooser is acceptable, or whether the URI is too broad
4 App opens to the wrong screen Filter matches but the handler ignores path/parameters Instrument or log the incoming Intent’s data URI
5 Works on device, fails in CI or on another domain Association file not served correctly assetlinks.json served over HTTPS at the expected path
6 Link worked yesterday, fails today Expiry Confirm the link’s validity window from your own spec

The two structural causes are worth expanding:

Manifest filters. Deep linking works only when apps declare matching Intent filters in their manifest files (Android documentation). A single mismatched host, a missing scheme, or an over-specific path pattern silently breaks routing — and the symptom is “nothing happens,” which looks like a device problem but is a configuration problem.

Association files over HTTPS. For app links, Iterable’s configuration guide states that assetlinks.json files must be served over HTTPS for Android deep links to function (Iterable support article). Serve the file on a dev host over plain HTTP and verification fails. Note also that Iterable hosts an apple-app-site-association file per tracking domain, and that association files are configured per platform — one does not substitute for the other.

When a fix lands, re-run the whole matrix rather than the single failing row. Filter changes affect fallback behavior too, and a link that now opens the app may have changed what the chooser shows or what an uninstalled device renders.

Test Matrix

Run this per transactional flow (reset, order confirmation, verification) per platform. It converts the sections above into a repeatable pass.

Case Preconditions Action Expected record
Fresh link, app installed App installed, no default set Tap link in inbox App opens to the exact screen; parameters intact
Fresh link, app uninstalled App removed Tap link Browser fallback or disambiguation dialog; no dead page
Competing app present Second app claims the URI Tap link Default app, only app, or chooser — per Android’s routing order
Wrong/missing screen — Tap link Destination matches the URI path, not a generic screen
Expired link Wait past your spec’s window Tap link Behavior matches the documented-in-spec post-expiry experience
CI-generated address Bearer token auth Trigger flow from pipeline Mail arrives at {namespace}.{tag}@inbox.testmail.app and is monitored programmatically
Association file Staging domain Fetch assetlinks.json Served over HTTPS at the expected path

FAQ

Can I use a disposable address for password resets or order recovery? For receiving a test password reset or order confirmation — yes, that is the intended use. The caution is against treating the disposable address as a durable account identity. If you need to return to that mailbox later to complete a recovery flow, confirm the inbox’s lifetime first and use a persistent alias for scenarios that outlive it.

What happens if the app is not installed when I tap the link? On Android, the link falls back to the browser or presents a disambiguation dialog, according to the routing order in the Android developer documentation. What the user actually sees depends on whether your product has a meaningful web fallback at that URI — which is why the uninstalled-device case belongs in every test run.

Does saving a TempMail inbox tag preserve access after expiry? The fetched documentation covers address generation, the {namespace}.{tag}@inbox.testmail.app format, and the two authentication workflows, but it does not state what tag-saving does after an inbox expires. Verify this behavior directly against the Tempmail documentation or API reference before designing a test that depends on returning to an old mailbox days later.

Pankaj Kumawat

About the author

Pankaj Kumawat

Software Engineer & Backend Lead at Codaipro

Software Engineer and Backend Lead at Codaipro specializing in Python, distributed architecture, and email testing infrastructure. Researches E2E QA automation and ephemeral systems.

Tempmail is the product this team builds. Our editorial policy covers sourcing, testing, corrections, and how we disclose that relationship.

Sources & further reading

  1. 01Authentication | Playwright (opens in a new tab)
  2. 02Use and configure deep links in email and SMS messages | Adobe Journey Optimizer (opens in a new tab)
  3. 03Documentation — Setup, Deploy & Configure Tempmail (opens in a new tab)
  4. 04API Reference — Free Developer Temp Mail REST & SSE Endpoints (opens in a new tab)
  5. 05Create deep links  |  App architecture  |  Android Developers (opens in a new tab)
  6. 06Configuring Deep Links for Email or SMS (opens in a new tab)
Share

Try it without signing up

The address on the home page is ready in a second. Copy it, use it anywhere, and read the reply here — no account, no ads.

Keep reading

All guides