# Email Verification with Deep Linking: Complete Implementation Guide > Learn how to implement secure email verification flows using deep links, with a step-by-step guide and code examples for mobile and web apps. - Canonical: https://redirectly.app/blog/email-verification-deep-linking - Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native - Published: 2024-01-15 - Author: Dmytro Vyrych - Category: Authentication · 8 min read Learn how to implement secure email verification using temporary deep links that work seamlessly across web and mobile platforms. This comprehensive guide covers everything from backend setup to Flutter integration. ## Table of Contents - 1. Overview & Benefits - 2. System Architecture - 3. Backend Implementation - 4. Flutter Integration - 5. Security Best Practices - 6. Testing & Validation ## Overview & Benefits > **What is Email Verification with Deep Linking?** Email verification with deep linking allows users to verify their email addresses by clicking a link that opens directly in your app, providing a seamless experience across web and mobile platforms. Instead of traditional web-only verification, users get a native app experience with proper navigation and state management. ### Key Benefits - Seamless cross-platform experience - Higher verification completion rates - Better user engagement and retention - Secure token-based verification - Automatic expiration for security - Detailed analytics and tracking ### Use Cases - User account registration - Email address changes - Two-factor authentication setup - Newsletter subscriptions - Account recovery processes - Marketing campaign opt-ins ## System Architecture ### How It Works 1. **User Registration** — User signs up with email address in your Flutter app 2. **Verification Link Creation** — Backend creates temporary verification link using Redirectly API 3. **Email Delivery** — Verification link sent to user's email address 4. **Link Click & Verification** — User clicks link, app opens, email gets verified ## Backend Implementation ### 1. User Registration Endpoint First, create an endpoint that handles user registration and triggers email verification: ```javascript // Node.js/Express example const axios = require('axios'); const crypto = require('crypto'); // Initialize Redirectly client const redirectly = axios.create({ baseURL: 'https://redirectly.app/api', headers: { 'Authorization': `Bearer ${process.env.REDIRECTLY_API_KEY}`, 'Content-Type': 'application/json' } }); app.post('/api/register', async (req, res) => { try { const { email, password, username } = req.body; // 1. Create user account (unverified) const user = await createUser({ email, password: await hashPassword(password), username, email_verified: false }); // 2. Generate verification token const verificationToken = crypto.randomBytes(32).toString('hex'); await storeVerificationToken(user.id, verificationToken); // 3. Create temporary verification link const verificationLink = await createVerificationLink(user.id, verificationToken); // 4. Send verification email await sendVerificationEmail(email, verificationLink); res.json({ success: true, message: 'Registration successful. Please check your email to verify your account.', user: { id: user.id, email, username } }); } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'Registration failed' }); } }); ``` ### 2. Verification Link Creation Create a function that generates temporary verification links using Redirectly: ```javascript async function createVerificationLink(userId, token) { try { // Create temporary link that expires in 24 hours const response = await redirectly.post('/v1/temp-links', { target: `https://redirectly.app/verify-email?token=${token}&user=${userId}`, ttlSeconds: 86400 // 24 hours }); return response.data.url; } catch (error) { console.error('Failed to create verification link:', error); throw new Error('Failed to create verification link'); } } async function storeVerificationToken(userId, token) { // Store token in database with expiration await db.query( 'INSERT INTO email_verification_tokens (user_id, token, expires_at) VALUES (?, ?, ?)', [userId, token, new Date(Date.now() + 24 * 60 * 60 * 1000)] ); } ``` ### 3. Email Verification Handler Create an endpoint that handles the verification when the link is clicked: ```javascript app.get('/api/verify-email', async (req, res) => { try { const { token, user } = req.query; // 1. Validate token const tokenRecord = await db.query( 'SELECT * FROM email_verification_tokens WHERE token = ? AND user_id = ? AND expires_at > NOW()', [token, user] ); if (!tokenRecord.length) { return res.status(400).json({ error: 'Invalid or expired verification token' }); } // 2. Mark email as verified await db.query( 'UPDATE users SET email_verified = true WHERE id = ?', [user] ); // 3. Delete used token await db.query( 'DELETE FROM email_verification_tokens WHERE token = ?', [token] ); // 4. Return success response res.json({ success: true, message: 'Email verified successfully' }); } catch (error) { console.error('Email verification error:', error); res.status(500).json({ error: 'Verification failed' }); } }); ``` ## Flutter Integration ### 1. Setup Deep Link Handling Configure your Flutter app to handle incoming verification links: ```dart import 'package:flutter_redirectly/flutter_redirectly.dart'; class EmailVerificationService { static final FlutterRedirectly _redirectly = FlutterRedirectly(); static Future initialize() async { await _redirectly.initialize(RedirectlyConfig( apiKey: 'YOUR_API_KEY', baseUrl: 'https://redirectly.app', enableDebugLogging: true, )); // Listen for incoming links _redirectly.onLinkClick.listen(_handleIncomingLink); // Check for initial link (app opened from link) final initialLink = await _redirectly.getInitialLink(); if (initialLink != null) { _handleIncomingLink(initialLink); } } static void _handleIncomingLink(LinkClickEvent event) { if (event.error != null) { print('Link error: ${event.error}'); return; } final uri = Uri.parse(event.originalUrl); // Check if this is an email verification link if (uri.path.contains('/verify-email')) { final token = uri.queryParameters['token']; final userId = uri.queryParameters['user']; if (token != null && userId != null) { _processEmailVerification(token, userId); } } } static Future _processEmailVerification(String token, String userId) async { try { // Call your backend to verify the email final response = await http.get( Uri.parse('${ApiConfig.baseUrl}/api/verify-email?token=$token&user=$userId'), headers: {'Content-Type': 'application/json'}, ); if (response.statusCode == 200) { // Show success message _showVerificationSuccess(); // Update app state await AuthService.updateEmailVerificationStatus(true); // Navigate to main app Get.offAllNamed('/home'); } else { _showVerificationError('Verification failed. Please try again.'); } } catch (e) { _showVerificationError('Network error. Please check your connection.'); } } static void _showVerificationSuccess() { Get.snackbar( 'Success', 'Your email has been verified successfully!', backgroundColor: Colors.green, colorText: Colors.white, duration: Duration(seconds: 3), ); } static void _showVerificationError(String message) { Get.snackbar( 'Error', message, backgroundColor: Colors.red, colorText: Colors.white, duration: Duration(seconds: 5), ); } } ``` ### 2. Initialize in main.dart Initialize the email verification service when your app starts: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize email verification service await EmailVerificationService.initialize(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( title: 'My App', theme: ThemeData( primarySwatch: Colors.blue, ), home: AuthService.isLoggedIn() ? HomeScreen() : LoginScreen(), routes: { '/home': (context) => HomeScreen(), '/login': (context) => LoginScreen(), '/verify-email': (context) => EmailVerificationScreen(), }, ); } } ``` ## Security Best Practices **Critical Security Measures:** - Use cryptographically secure random tokens (32+ bytes) - Set appropriate expiration times (24 hours max for email verification) - Implement rate limiting on verification endpoints - Use HTTPS for all verification links - Validate tokens server-side before processing - Delete used tokens immediately after verification ### Token Security - Generate tokens using crypto.randomBytes() - Store tokens with expiration timestamps - Use one-time tokens (delete after use) - Implement token rotation for resend requests ### Rate Limiting - Limit verification attempts per user - Implement exponential backoff - Monitor for suspicious activity - Block IPs with excessive attempts ## Testing & Validation ### Testing Checklist **Functional Testing** - Test link generation and expiration - Verify email delivery - Test app opening from email - Validate verification completion - Test expired link handling - Verify error handling **Security Testing** - Test with invalid tokens - Verify rate limiting works - Test token reuse prevention - Validate HTTPS enforcement - Test with expired tokens - Verify user isolation ### Sample Test Cases ```javascript // Jest test example describe('Email Verification', () => { test('should create verification link successfully', async () => { const mockUser = { id: '123', email: 'test@example.com' }; const mockToken = 'valid-token-123'; const link = await createVerificationLink(mockUser.id, mockToken); expect(link).toContain('redirectly.app'); expect(link).toContain(mockToken); }); test('should reject expired tokens', async () => { const expiredToken = 'expired-token'; const userId = '123'; const response = await request(app) .get(`/api/verify-email?token=${expiredToken}&user=${userId}`); expect(response.status).toBe(400); expect(response.body.error).toContain('expired'); }); test('should prevent token reuse', async () => { const token = 'used-token'; const userId = '123'; // First verification should succeed const firstResponse = await request(app) .get(`/api/verify-email?token=${token}&user=${userId}`); expect(firstResponse.status).toBe(200); // Second verification should fail const secondResponse = await request(app) .get(`/api/verify-email?token=${token}&user=${userId}`); expect(secondResponse.status).toBe(400); }); }); ``` ## You're Ready to Implement Email Verification! You now have everything you need to implement secure email verification with deep linking in your Flutter app. This approach provides a seamless user experience while maintaining high security standards. ## Related Articles & Resources - [Flutter Deferred Deep Linking](https://redirectly.app/flutter-deferred-deep-linking.md) — Complete guide to implementing deferred deep linking in your Flutter apps. - [React Native Deferred Deep Linking](https://redirectly.app/react-native-deferred-deep-linking.md) — Learn how to implement deferred deep linking in your React Native applications. --- Full site index for AI agents: https://redirectly.app/llms.txt