Deep Linking Auth Emails
Make password-reset and verification emails open your app directly — with a web fallback for users who don't have it installed.
Do you need this?
Probably not on day one. If you leave the URL templates blank in Email Branding, Koolbase serves its own hosted https reset and verification pages. They work in every mail client, need no configuration, and complete the flow correctly. That is the recommended default and it is production-safe.
You want this page when you'd rather the link be yours — branded with your domain, and opening your app on the right screen instead of a web page. That's a Universal Link (iOS) or App Link (Android), and it takes about thirty minutes of setup across your domain, your app, and your dashboard.
Custom URL schemes don't work in email
A scheme like myapp://reset?token=... is tempting — no domain setup, and it works when you test it with adb or by pasting into a notes app.
But most mail clients, Gmail included, strip or ignore non-https link targets. The button renders and does nothing when tapped. Nothing errors, so it reads as a delivery failure and you'll debug the wrong layer. Use custom schemes for local testing; use https links in email.
How it works
One https URL does double duty, and your setup has to handle three cases:
That means four pieces: two verification files on your domain, declarations inside your app, web fallback pages, and the Koolbase templates pointing at your domain. None of it is difficult. All of it fails silently when wrong, which is why each step below ends with something you can verify.
1. Host the verification files
Both platforms confirm you own the domain by fetching a file from it. This is what stops any app from claiming it handles links for someone else's site.
iOS
Serve at https://yourdomain.com/.well-known/apple-app-site-association — JSON content, with no file extension.
{
"applinks": {
"apps": [],
"details": [{
"appID": "TEAMID.com.example.app",
"appIDs": ["TEAMID.com.example.app"],
"paths": ["/reset*", "/verify*"],
"components": [
{ "/": "/reset*" },
{ "/": "/verify*" }
]
}]
}
}TEAMID is your Apple Developer Team ID, followed by your bundle identifier. Scope paths to the routes your app actually handles rather than claiming the whole domain.
Android
Serve at https://yourdomain.com/.well-known/assetlinks.json.
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"PLAY_APP_SIGNING_SHA256",
"UPLOAD_KEY_SHA256"
]
}
}]List both fingerprints if you use Play App Signing
With Play App Signing enabled, the certificate that signs the build users install is Google's app signing key — not your upload keystore. A fingerprint taken locally with keytool describes your upload cert, so store installs will silently fail verification if that's all you list.
Get the app signing key's SHA-256 from Play Console (Test and release → App signing), and list both: the Play key covers store installs, the upload key covers release builds you install directly.
2. Check how they're served
Uploading isn't enough — the response matters. Verify with curl before touching your app, because a bad response here surfaces later as a mysterious app-side bug.
curl -sI https://yourdomain.com/.well-known/apple-app-site-associationTwo requirements:
- A bare 200 — no redirects. iOS does not follow redirects when fetching this file. If your apex redirects to
www, verification fails with no error reported anywhere. content-type: application/json. The file has no extension, so most servers won't infer the type.
On Apache, a small .htaccess inside .well-known/ fixes the content type:
<Files "apple-app-site-association">
ForceType application/json
</Files>3. Declare the domain in your app
iOS
In Xcode, select your app target → Signing & Capabilities → + Capability → Associated Domains, then add one entry:
applinks:yourdomain.comAndroid
Add an intent filter to your main activity — separate from any existing custom-scheme filter. Mixing schemes in one filter breaks verification.
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https" android:host="yourdomain.com"/>
<data android:pathPrefix="/reset"/>
<data android:pathPrefix="/verify"/>
</intent-filter>autoVerify="true" makes Android fetch your assetlinks.json at install time, so links open your app without a disambiguation dialog.
Your router needs to handle the https shape too
myapp://reset?token=..., the universal link now arrives as https://yourdomain.com/reset?token=... — same screen, different URL structure. If the app opens but lands somewhere unexpected, this mapping is why.4. Add web fallback pages
This step separates a demo from production. Once your emails point at https://yourdomain.com/reset?token=..., that URL will be opened in browsers — by desktop users, by people without the app, and by anyone whose OS hasn't verified your domain yet. If nothing is served at that path, your reset email leads to a 404 for a significant share of real users.
The simplest correct fallback is a static page that reads the token and forwards to the Koolbase hosted page — the one you'd have used with the templates left blank. App users never see it, because the OS intercepts before any HTTP request happens.
<script>
var token = new URLSearchParams(location.search).get('token');
var dest = 'https://api.koolbase.com/v1/sdk/auth/reset'
+ '?token=' + encodeURIComponent(token || '')
+ '&project=YOUR_PROJECT_ID';
if (token) location.replace(dest);
</script>Do the same at /verify with /v1/sdk/auth/verify. Keep the forward client-side rather than a server redirect rule — server rewrites near these paths are exactly what tends to catch /.well-known/ requests by accident, and iOS will not follow a redirect there.
5. Point Koolbase at your domain
In the dashboard, open Branding → Email Link URLs and set both templates. {token} is substituted at send time.
Verification URL: https://yourdomain.com/verify?token={token}
Password Reset URL: https://yourdomain.com/reset?token={token}Double-check which value goes in which field — the two are easy to transpose, and a swapped pair sends reset emails to your verification screen with no error to warn you.
6. Test it
The build already on your device cannot pass this test — entitlements and manifest declarations ship inside the binary, so install a fresh build first. Then:
- Tap the link from a mail client, not Safari. iOS deliberately does not hand typed or pasted URLs to apps — Universal Links only trigger from taps in other apps. Pasting the URL into Safari proves nothing and will convince you it's broken.
- Expect Apple's CDN to lag. A newly added domain can take time to propagate, so a correct setup may still fail right after install. For testing,
applinks:yourdomain.com?mode=developerforces a direct fetch and skips the cache — remove it before store builds. - On Android, ask the OS directly.
adb shell pm get-app-links com.example.appverified means the assetlinks fetch succeeded. Anything else tells you which layer to look at — usually the fingerprint list or the file's response headers.
Verify the browser path too
/reset → /reset/) is normal and harmless — use curl -L when checking from the terminal so you follow it.