# Email Verification Deep Links > Solve email verification for mobile apps using deferred deep links. Redirectly preserves verification context when users tap links before installing your app. - Canonical: https://redirectly.app/use-cases/email-verification - Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native Verify users seamlessly with deferred deep links. Preserve verification context even when users tap verification emails before installing your app. ## The email verification problem Email verification links break a critical user flow for uninstalled apps. Here's the typical scenario: ### What happens without deep links: 1. User signs up via web or email. They don't have your app installed yet. 2. Verification email arrives. User taps the link on mobile. 3. Browser opens the verification URL. App isn't installed, so verification page is incomplete. 4. User is prompted to install app. Even after install, they lose context—don't know what to verify. 5. User abandons flow. Sign-up conversion drops. ### Without deferred deep links - Verification token lost when navigating to app - User sees generic app home screen, not verification context - Manual re-verification needed (friction) - Higher app abandonment in onboarding ### With deferred deep links - Verification token preserved across app install - App opens directly to verification completion screen - One-tap confirmation in app after install - Seamless onboarding experience ## How deferred deep links solve email verification Deferred deep links preserve your verification token and context even when the app isn't installed at the time the user taps the link. ### Step 1: User receives email Your backend sends a verification email with a Redirectly deep link containing the verification token: `https://myapp.redirectly.app/verify?token=abc123&email=user@example.com` ### Step 2: User taps link on mobile If the app is **already installed**, the deep link opens directly to your verification screen with token intact. If the app is **not installed**, Redirectly captures the full URL (token included) and stores it. ### Step 3: App store redirect User is sent to App Store or Play Store to install your app. The verification token is **preserved** in Redirectly's servers. ### Step 4: SDK retrieves deferred link When your app launches for the first time, the Redirectly SDK queries the servers: "What deep link was intended for this device?" Redirectly responds with the full verification URL including the token. ### Step 5: App navigates to verification screen Your app receives the deep link with the token and navigates to the verification completion screen, ready for one-tap confirmation. Token is intact. User is delighted. ## Implementation with Redirectly ### Backend: Generate verification links When a user signs up, create a Redirectly deep link with the verification token as a query parameter: ```python # Example backend code (Python) verification_token = generate_token() deep_link = ( f"https://myapp.redirectly.app/verify" f"?token={verification_token}" f"&email={user_email}" ) # Send email with deep_link send_verification_email(user_email, deep_link) ``` ### Mobile: Setup and listen for deep links Initialize Redirectly in your Flutter app and listen for deep link events: ```dart import 'package:redirectly/redirectly.dart'; import 'package:go_router/go_router.dart'; void main() async { // Initialize Redirectly with your API key await Redirectly.initialize( apiKey: 'your_api_key_here', ); // Listen for deferred deep links Redirectly.onDeepLink.listen((link) { print('Received deferred link: ${link.fullUrl}'); }); runApp(const MyApp()); } ``` ### Navigation: Handle verification deep links When the app receives a verification deep link, navigate to the verification screen: ```dart // In your main.dart or routing setup Redirectly.onDeepLink.listen((link) { // Parse the deep link if (link.path == 'verify') { final token = link.queryParameters['token']; final email = link.queryParameters['email']; // Navigate to verification screen with token context.go('/verify?token=$token&email=$email'); } }); // Or with go_router route guards final goRouter = GoRouter( routes: [ GoRoute( path: '/verify', builder: (context, state) { final token = state.queryParameters['token']; final email = state.queryParameters['email']; return EmailVerificationScreen( token: token, email: email, ); }, ), ], ); ``` ## Complete Dart/Flutter example ```dart import 'package:flutter/material.dart'; import 'package:redirectly/redirectly.dart'; import 'package:go_router/go_router.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Redirectly.initialize( apiKey: 'your_api_key_here', ); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); // Listen for deep links (both foreground and deferred) Redirectly.onDeepLink.listen((link) { _handleDeepLink(link); }); } void _handleDeepLink(RedirectlyLink link) { if (link.path == 'verify') { final token = link.queryParameters['token']; final email = link.queryParameters['email']; // Navigate to verification screen context.go('/verify?token=$token&email=$email'); } } @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _buildRouter(context), ); } GoRouter _buildRouter(BuildContext context) { return GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/verify', builder: (context, state) { final token = state.queryParameters['token']; final email = state.queryParameters['email']; return EmailVerificationScreen( token: token ?? '', email: email ?? '', ); }, ), ], ); } } class EmailVerificationScreen extends StatefulWidget { final String token; final String email; const EmailVerificationScreen({ required this.token, required this.email, Key? key, }) : super(key: key); @override State createState() => _EmailVerificationScreenState(); } class _EmailVerificationScreenState extends State { bool _isVerifying = false; Future _verifyEmail() async { setState(() => _isVerifying = true); try { // Send token to your backend to verify final response = await http.post( Uri.parse('https://api.example.com/verify-email'), body: { 'token': widget.token, 'email': widget.email, }, ); if (response.statusCode == 200) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Email verified! Welcome!')), ); context.go('/home'); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: $e')), ); } } finally { setState(() => _isVerifying = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Verify Email')), body: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.mail_outline, size: 64), const SizedBox(height: 16), Text( 'Verify your email', style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 8), Text( 'Confirm your email: ${widget.email}', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 32), ElevatedButton( onPressed: _isVerifying ? null : _verifyEmail, child: _isVerifying ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, ), ) : const Text('Verify Email'), ), ], ), ), ), ); } } ``` This example shows the complete flow: - **Initialize Redirectly** in main.dart with your API key - **Listen for deep links** from Redirectly (works even if app was just installed) - **Parse the token** from the deep link query parameters - **Navigate to verification screen** with token intact - **Verify with your backend** using the token ## Next steps Learn more about email verification and deferred deep linking: - [Read: Email Verification Deep Linking Guide](https://redirectly.app/blog/email-verification-deep-linking.md) - [Learn: What is Deferred Deep Linking?](https://redirectly.app/deferred-deep-linking.md) - [Guide: Flutter Deferred Deep Linking](https://redirectly.app/flutter-deferred-deep-linking.md) ## Frequently asked questions ### Does this work if the app is already installed? Yes. If your app is installed when the user taps the verification link, the deep link opens immediately with the token intact. If the app isn't installed, Redirectly stores the token and delivers it after install. ### What if the user never installs the app? The verification link still works in the browser. The user can verify via web, then install the app later. Deferred deep links are an enhancement for better UX, not a replacement for web verification. ### Can I use this with React Native? Yes. Redirectly works with Flutter and React Native. The implementation is similar—initialize the SDK, listen for deep links, and parse the token from the URL. ### How long are tokens stored? By default, Redirectly stores deferred deep link data for 30 days. You can configure this in your dashboard. After that period, the link expires. ### Is this secure? Can someone intercept the token? Deep links travel in URLs, which can be logged. For maximum security, use short-lived tokens that expire quickly (5-15 minutes), and require user verification of their email address before activation. --- Full site index for AI agents: https://redirectly.app/llms.txt