Skip to main content

Django Email Verification

Introduction

Email verification is an essential security feature for web applications. It ensures that users provide valid email addresses during registration and proves ownership of those emails. This verification step helps prevent spam accounts and provides an additional layer of security for your Django application.

In this tutorial, you'll learn how to implement email verification in Django from scratch. We'll create a system where new users receive a verification email with a unique token after registration. Users will need to click on the verification link to activate their accounts before they can log in.

Why Email Verification Matters

Before diving into the implementation, let's understand why email verification is important:

  1. Confirms identity: Ensures users provide real email addresses they own
  2. Reduces spam: Prevents automated bots from creating numerous fake accounts
  3. Enhances security: Adds an extra authentication factor
  4. Improves user data quality: Ensures valid contact information in your database

Prerequisites

To follow along with this tutorial, you should have:

  • Basic knowledge of Django
  • A Django project set up with the authentication system
  • Python 3.6 or newer
  • Understanding of how Django models and views work

Setting Up Email Backend

First, let's configure Django to send emails. For development purposes, we'll use the console backend, which prints emails to the console instead of actually sending them.

Add these settings to your settings.py file:

python
# Email settings
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # For development only
EMAIL_HOST = 'smtp.gmail.com' # For production with Gmail
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[email protected]' # Your email address
EMAIL_HOST_PASSWORD = 'your-email-password' # Your email password/app password
caution

For production, you should:

  1. Use a real email service (Gmail, SendGrid, AWS SES, etc.)
  2. Store sensitive credentials using environment variables
  3. Never commit email credentials to version control

Creating a User Profile Model with Verification

We'll need to extend the user model to include verification fields. Create a UserProfile model in your models.py:

python
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import uuid

class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
email_verified = models.BooleanField(default=False)
verification_token = models.UUIDField(default=uuid.uuid4)
token_created_at = models.DateTimeField(default=timezone.now)

def __str__(self):
return f"{self.user.username}'s Profile"

def token_is_valid(self):
# Token is valid for 24 hours after creation
return timezone.now() < self.token_created_at + timezone.timedelta(hours=24)

After creating this model, run migrations:

bash
python manage.py makemigrations
python manage.py migrate

Registration and Email Sending

Now, let's create a registration view that will create a user, generate a verification token, and send an email. First, create a form in forms.py:

python
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User

class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)

class Meta:
model = User
fields = ("username", "email", "password1", "password2")

def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user

Next, let's create the registration view in views.py:

python
from django.shortcuts import render, redirect
from django.contrib import messages
from django.core.mail import send_mail
from django.conf import settings
from django.template.loader import render_to_string
from .forms import UserRegistrationForm
from .models import UserProfile

def register(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
user = form.save()

# Create user profile
profile = UserProfile.objects.create(user=user)

# Generate verification URL
token = profile.verification_token
verification_url = request.build_absolute_uri(
f'/verify-email/{token}/'
)

# Send verification email
subject = 'Verify your account'
message = f'Please click the link to verify your account: {verification_url}'
html_message = render_to_string('email_verification.html', {
'user': user,
'verification_url': verification_url,
})

send_mail(
subject=subject,
message=message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=[user.email],
html_message=html_message,
fail_silently=False,
)

messages.success(request, 'Account created! Please check your email to verify your account.')
return redirect('login')
else:
form = UserRegistrationForm()

return render(request, 'register.html', {'form': form})

Creating Email Templates

Create an HTML email template by making a new file templates/email_verification.html:

html
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h2>Verify Your Email Address</h2>
<p>Hi {{ user.username }},</p>
<p>Thank you for registering! Please click the button below to verify your email address:</p>
<p>
<a href="{{ verification_url }}" class="button">Verify Email</a>
</p>
<p>Or copy and paste this link in your browser:</p>
<p>{{ verification_url }}</p>
<p>This link will expire in 24 hours.</p>
<p>If you didn't register on our site, please ignore this email.</p>
</div>
</body>
</html>

Implementing Email Verification View

Now, let's create a view to handle the verification process when users click the link in their emails:

python
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from django.utils import timezone
from .models import UserProfile
import uuid

def verify_email(request, token):
try:
# Convert string token to UUID
verification_token = uuid.UUID(token)

# Find the profile with this token
profile = get_object_or_404(UserProfile, verification_token=verification_token)

# Check if token is expired
if not profile.token_is_valid():
return render(request, 'verification_failed.html', {
'message': 'Verification link has expired. Please request a new one.'
})

# Check if already verified
if profile.email_verified:
return render(request, 'verification_success.html', {
'message': 'Your email was already verified. You can log in.'
})

# Verify the email
profile.email_verified = True
profile.save()

return render(request, 'verification_success.html', {
'message': 'Your email has been verified successfully! You can now log in.'
})

except (ValueError, uuid.BadUUIDError):
return render(request, 'verification_failed.html', {
'message': 'Invalid verification link.'
})

Create templates for verification success and failure:

templates/verification_success.html:

html
{% extends "base.html" %}

{% block content %}
<div class="verification-container">
<h2>Email Verification</h2>
<div class="alert alert-success">
<p>{{ message }}</p>
</div>
<p><a href="{% url 'login' %}" class="btn btn-primary">Go to Login</a></p>
</div>
{% endblock %}

templates/verification_failed.html:

html
{% extends "base.html" %}

{% block content %}
<div class="verification-container">
<h2>Email Verification</h2>
<div class="alert alert-danger">
<p>{{ message }}</p>
</div>
<p><a href="{% url 'register' %}" class="btn btn-primary">Register Again</a></p>
</div>
{% endblock %}

Updating URLs

Add the URL patterns in your urls.py:

python
from django.urls import path
from . import views

urlpatterns = [
path('register/', views.register, name='register'),
path('verify-email/<str:token>/', views.verify_email, name='verify_email'),
# Add other auth URLs as needed
]

Enhancing Login to Check Verification Status

Finally, let's modify the login process to check if a user's email is verified:

python
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .models import UserProfile

def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')

user = authenticate(request, username=username, password=password)

if user is not None:
# Check if email is verified
try:
profile = UserProfile.objects.get(user=user)
if not profile.email_verified:
messages.error(request, 'Please verify your email before logging in.')
return redirect('login')

login(request, user)
return redirect('dashboard')
except UserProfile.DoesNotExist:
# If profile doesn't exist, create one and require verification
profile = UserProfile.objects.create(user=user)
messages.error(request, 'Please verify your email before logging in.')
return redirect('login')
else:
messages.error(request, 'Invalid username or password.')

return render(request, 'login.html')

Resending Verification Emails (Optional)

Let's create a utility function to resend verification emails if users didn't receive them:

python
from django.utils import timezone
import uuid

def resend_verification_email(request):
if request.method == 'POST':
email = request.POST.get('email')
try:
user = User.objects.get(email=email)
profile = UserProfile.objects.get(user=user)

# Update verification token and timestamp
profile.verification_token = uuid.uuid4()
profile.token_created_at = timezone.now()
profile.save()

# Generate new verification URL
verification_url = request.build_absolute_uri(
f'/verify-email/{profile.verification_token}/'
)

# Send new verification email
subject = 'Verify your account'
message = f'Please click the link to verify your account: {verification_url}'
html_message = render_to_string('email_verification.html', {
'user': user,
'verification_url': verification_url,
})

send_mail(
subject=subject,
message=message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=[user.email],
html_message=html_message,
fail_silently=False,
)

messages.success(request, 'Verification email resent. Please check your inbox.')

except (User.DoesNotExist, UserProfile.DoesNotExist):
messages.error(request, 'No account found with that email address.')

return render(request, 'resend_verification.html')

Don't forget to create a template for this and add it to your URLs.

Testing the Implementation

To test your implementation:

  1. Register a new user account
  2. Check your console output (or email inbox in production) for the verification email
  3. Click the verification link or copy-paste it into your browser
  4. Try to log in before verification (should be denied)
  5. Verify the email and try logging in again (should succeed)

Real-world Considerations

When implementing email verification in a production environment:

  1. Security: Use HTTPS for all verification links
  2. Expiry time: Set reasonable token expiration times (24-48 hours is common)
  3. Rate limiting: Limit verification email resends to prevent abuse
  4. Email deliverability: Use a reliable email service like SendGrid, Mailgun, or AWS SES
  5. Error handling: Provide clear error messages if verification fails
  6. Logging: Log verification attempts for debugging and security monitoring
  7. Accessibility: Ensure emails render well on different devices and email clients

Complete Example Project Structure

Here's how your final project structure might look:

my_project/
├── accounts/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── forms.py
│ ├── migrations/
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── my_project/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── templates/
│ ├── base.html
│ ├── email_verification.html
│ ├── login.html
│ ├── register.html
│ ├── resend_verification.html
│ ├── verification_failed.html
│ └── verification_success.html
└── manage.py

Summary

In this tutorial, you learned how to implement a complete email verification system for Django:

  1. Setting up the email backend configuration
  2. Creating a UserProfile model with verification fields
  3. Implementing the registration process that sends verification emails
  4. Building the verification view that validates tokens
  5. Enhancing the login process to check verification status
  6. Adding functionality to resend verification emails

Email verification is a crucial security feature for any modern web application. It ensures that users provide valid email addresses and helps protect your system against spam and automated registrations.

Additional Resources

Exercises

  1. Enhance the system to generate a new token when resending verification emails
  2. Add a page where users can request verification email resends
  3. Implement a system to notify admins when multiple verification attempts fail for the same user
  4. Create an admin interface to manually verify users
  5. Add email templates for different scenarios (welcome email, password reset, etc.)

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)