CheckTick includes a comprehensive email notification system with customizable templates, granular user preferences, and two-level theming support.
Overview
The email system provides:
- Welcome emails on user signup
- Password change notifications for security
- Survey creation/deletion confirmations (optional)
- Team and survey invitations (future)
- Error and critical alerts (future, for logging integration)
- Markdown-based templates with platform and survey-level branding
- Granular user preferences controllable via profile page
Quick Start
1. Configure Email Backend
Email settings are configured via environment variables. The email backend is automatically selected based on your DEBUG setting:
DEBUG=True(development): Emails are printed to the consoleDEBUG=False(production): Emails are sent via SMTP (Mailgun)
You can override this behavior by explicitly setting EMAIL_BACKEND in your .env file.
Development setup (.env):
# DEBUG=True automatically uses console backend
DEBUG=True
[email protected]
Production setup (.env):
# DEBUG=False automatically uses SMTP backend
DEBUG=False
[email protected]
[email protected]
# SMTP settings (required for production)
EMAIL_HOST=smtp.mailgun.org
EMAIL_PORT=587
EMAIL_USE_TLS=True
[email protected]
EMAIL_HOST_PASSWORD=your-mailgun-smtp-password
EMAIL_TIMEOUT=10
Override email backend (optional):
If you want to force a specific backend regardless of DEBUG, add to .env:
# Force console output even in production (for testing)
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
# Force SMTP even in development
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
# Use file-based backend (emails saved to files)
EMAIL_BACKEND=django.core.mail.backends.filebased.EmailBackend
EMAIL_FILE_PATH=/tmp/app-emails
2. Set Up SMTP Provider (Production)
CheckTick supports any SMTP email service. Below are configuration examples for popular providers.
Mailgun (Recommended)
- Create a free Mailgun account
- Verify your sending domain (or use Mailgun's sandbox domain for testing)
- Get SMTP credentials from Mailgun dashboard:
- Navigate to Sending โ Domain Settings โ SMTP Credentials
- Use
smtp.mailgun.orgas host, port587 - Username format:
[email protected] - Add credentials to your
.envfile:
EMAIL_HOST=smtp.mailgun.org
EMAIL_PORT=587
EMAIL_USE_TLS=True
[email protected]
EMAIL_HOST_PASSWORD=your-mailgun-smtp-password
Gmail
For Gmail, you need to use an App Password:
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
[email protected]
EMAIL_HOST_PASSWORD=your-app-specific-password
Note: Regular Gmail passwords won't work. You must generate an App Password in your Google Account settings.
SendGrid
- Get your SendGrid API key
- Configure
.env:
EMAIL_HOST=smtp.sendgrid.net
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=apikey
EMAIL_HOST_PASSWORD=your-sendgrid-api-key
Note: The username is literally apikey, not your SendGrid username.
Amazon SES
- Set up Amazon SES SMTP credentials
- Configure
.env(replace region as needed):
EMAIL_HOST=email-smtp.us-east-1.amazonaws.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-ses-smtp-username
EMAIL_HOST_PASSWORD=your-ses-smtp-password
Other SMTP Providers
Any SMTP service will work. You just need:
EMAIL_HOST- SMTP server hostnameEMAIL_PORT- Usually587(TLS),465(SSL), or25(plain)EMAIL_USE_TLS- Set toTruefor port 587EMAIL_HOST_USER- Your SMTP usernameEMAIL_HOST_PASSWORD- Your SMTP password
3. Run Migration
The email preferences model needs to be migrated to your database:
# With Docker
docker compose exec web python manage.py migrate
# Without Docker
python manage.py migrate
4. Test Email Sending
Start your development server and create a new user account. With the console backend, you'll see the welcome email printed in the terminal:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Welcome to CheckTick!
From: [email protected]
To: [email protected]
Date: Sat, 04 Oct 2025 10:30:00 -0000
Message-ID: <...>
Welcome to CheckTick!
Hi [email protected],
Thank you for signing up for CheckTick! We're excited to have you on board.
...
User Email Preferences
Users have granular control over which emails they receive. Preferences are managed via the profile page at /profile.
Available Preferences
| Preference | Description | Default |
|---|---|---|
| Welcome emails | Sent when a new account is created | โ Enabled |
| Password change notifications | Security alerts when password is changed | โ Enabled |
| Survey created confirmations | Notification when you create a survey | โ Disabled |
| Survey deleted confirmations | Notification when you delete a survey | โ Disabled |
| Survey published notifications | Alert when your survey is published | โ Disabled |
| Team invitations | Invites to join organizations | โ Enabled |
| Survey invitations | Invites to collaborate on surveys | โ Enabled |
| Error notifications | System errors affecting you (future) | โ Enabled |
| Critical alerts | Critical system issues (future) | โ Enabled |
Managing Preferences
- Log in to CheckTick
- Navigate to your Profile page (click your avatar โ Profile)
- Scroll to Email Notification Preferences section
- Toggle checkboxes for desired notifications
- Click Save Email Preferences
Changes take effect immediately.
Email Templates
All email content is stored as Markdown templates in checktick_app/templates/emails/. This makes them easy to customize and maintain.
Template Structure
Each email has two components:
- Markdown content (
.mdfile) - The email body with variables - HTML wrapper (
base_email.html) - Responsive email template with branding
Available Templates
| Template | Purpose | Variables |
|---|---|---|
welcome.md |
Welcome new users | user.username, brand_title, site_url |
password_changed.md |
Security notification | user.username, user.email, brand_title |
survey_created.md |
Survey creation confirmation | user.username, survey.title, survey.slug, survey.state |
survey_deleted.md |
Survey deletion confirmation | user.username, survey_name, survey_slug |
Customizing Templates
To customize an email template:
- Open the
.mdfile inchecktick_app/templates/emails/ - Edit the content using Markdown syntax
- Use template variables (e.g.,
{{ user.username }}) for personalization - Restart your web server to apply changes
Example (welcome.md):
## Welcome to {{ brand_title }}!
Hi **{{ user.username }}**,
Thank you for signing up! Here's how to get started:
### Getting Started
- Create your first survey
- Invite team members
- Explore our documentation at {{ site_url }}/docs/
Need help? Contact us anytime.
Welcome aboard!
The {{ brand_title }} Team
Email Styling
The base HTML template (base_email.html) applies:
- Responsive design - Works on desktop and mobile
- Platform branding - Uses colors and fonts from SiteBranding model
- Email client compatibility - Inline CSS for broad support
- Professional styling - Buttons, code blocks, lists, blockquotes
Two-Level Theming
CheckTick supports theming at both platform and survey levels.
Platform-Level Theming
Used for account-related emails (welcome, password change, survey deleted):
- Brand title from
SiteBranding.titleor Djangosettings.SITE_NAME - Primary color from
SiteBranding.primary_coloror settings default - Fonts from
SiteBranding.font_headingandSiteBranding.font_body - Logo from
SiteBranding.icon_url(optional)
Configure via /profile (superusers only) under "Project theme and brand".
Survey-Level Theming
Used for survey-specific emails (survey created):
- Inherits platform defaults as baseline
- Overrides from
Survey.stylefield if customized - Allows per-survey branding for white-label use cases
Example: A research organization can create multiple surveys with different branding for different departments.
Developer Guide
Sending Emails Programmatically
The email utility module provides functions for all email types.
Import the functions
from checktick_app.core.email_utils import (
send_welcome_email,
send_password_change_email,
send_survey_created_email,
send_survey_deleted_email,
)
Send welcome email
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='[email protected]')
send_welcome_email(user)
Send password change notification
send_password_change_email(user)
Send survey created email
from checktick_app.surveys.models import Survey
survey = Survey.objects.get(slug='my-survey')
send_survey_created_email(user, survey)
Send survey deleted email
survey_name = survey.title
survey_slug = survey.slug
survey.delete()
send_survey_deleted_email(user, survey_name, survey_slug)
Respecting User Preferences
All email functions automatically check UserEmailPreferences before sending:
def send_welcome_email(user):
"""Send a welcome email to a new user."""
from checktick_app.core.models import UserEmailPreferences
prefs = UserEmailPreferences.get_or_create_for_user(user)
if not prefs.send_welcome_email:
logger.info(f"Welcome email skipped for {user.username} (user preference)")
return
# Continue sending email...
You don't need to check preferences manually - just call the send function.
Email Function Signatures
def send_branded_email(
to_email: str,
subject: str,
markdown_template: str,
context: dict,
from_email: str | None = None,
brand_override: dict | None = None,
) -> bool:
"""
Send a branded email using a markdown template.
Returns True if email sent successfully, False otherwise.
"""
def send_welcome_email(user) -> bool:
"""Send welcome email with platform branding."""
def send_password_change_email(user) -> bool:
"""Send password change notification with platform branding."""
def send_survey_created_email(user, survey) -> bool:
"""Send survey created confirmation with survey branding."""
def send_survey_deleted_email(user, survey_name: str, survey_slug: str) -> bool:
"""Send survey deleted confirmation with platform branding."""
Creating New Email Templates
- Create Markdown template:
touch checktick_app/templates/emails/my_new_email.md
- Write content with variables:
## {{ subject_line }}
Hi **{{ user.username }}**,
Your custom message here with {{ variables }}.
Best regards,
The {{ brand_title }} Team
- Create send function in
email_utils.py:
def send_my_custom_email(user, custom_data):
"""Send a custom email."""
from checktick_app.core.models import UserEmailPreferences
# Check user preferences (create new preference field if needed)
prefs = UserEmailPreferences.get_or_create_for_user(user)
if not prefs.send_my_custom_email:
logger.info(f"Custom email skipped for {user.username} (user preference)")
return False
# Get branding
brand = get_platform_branding()
# Build context
context = {
"user": user,
"custom_data": custom_data,
"brand_title": brand["title"],
"site_url": settings.SITE_URL,
}
# Send email
return send_branded_email(
to_email=user.email,
subject=f"Custom Subject - {brand['title']}",
markdown_template="emails/my_new_email.md",
context=context,
brand_override=brand,
)
- Add preference to model (if needed):
If you want user control, add a field to UserEmailPreferences:
class UserEmailPreferences(models.Model):
# ... existing fields ...
send_my_custom_email = models.BooleanField(
default=True,
help_text="Receive custom notification emails",
)
Then create and run a migration.
Integration Points
Signup Flow
Welcome emails are sent when a user signs up. To integrate:
In checktick_app/core/views.py (or your signup view):
from .email_utils import send_welcome_email
def signup(request):
if request.method == "POST":
form = SignupForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
# Send welcome email
send_welcome_email(user)
return redirect("dashboard")
# ... rest of view
Password Change
For password changes, use Django signals or override the password change view:
Option 1: Using signals (recommended)
Create checktick_app/core/signals.py:
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver
from .email_utils import send_password_change_email
# Note: Django doesn't have a built-in password_changed signal
# You may need to create a custom signal or override the view
Option 2: Override password change view
from django.contrib.auth.views import PasswordChangeView
from .email_utils import send_password_change_email
class CustomPasswordChangeView(PasswordChangeView):
def form_valid(self, form):
response = super().form_valid(form)
send_password_change_email(self.request.user)
return response
Survey CRUD Operations
Survey creation (in checktick_app/surveys/views.py):
from checktick_app.core.email_utils import send_survey_created_email
def create_survey(request):
# ... survey creation logic ...
survey = form.save()
send_survey_created_email(request.user, survey)
return redirect("survey_detail", slug=survey.slug)
Survey deletion:
from checktick_app.core.email_utils import send_survey_deleted_email
def delete_survey(request, slug):
survey = get_object_or_404(Survey, slug=slug)
# Capture details before deletion
survey_name = survey.title
survey_slug = survey.slug
survey.delete()
# Send notification
send_survey_deleted_email(request.user, survey_name, survey_slug)
return redirect("dashboard")
Troubleshooting
Emails not sending
Check backend configuration:
# In Django shell
python manage.py shell
>>> from django.conf import settings
>>> print(settings.EMAIL_BACKEND)
django.core.mail.backends.console.EmailBackend # Should print your backend
Test email sending:
from django.core.mail import send_mail
send_mail(
"Test Subject",
"Test message body.",
"[email protected]",
["[email protected]"],
fail_silently=False,
)
Console backend shows nothing
Make sure you're watching the correct terminal where runserver or docker compose logs -f web is running.
SMTP authentication errors
- Verify
EMAIL_HOST_USERandEMAIL_HOST_PASSWORDare correct - Check Mailgun dashboard for valid SMTP credentials
- Ensure domain is verified in Mailgun
- Try sandbox domain for testing:
sandbox123.mailgun.org
User not receiving emails
- Check user's email preferences in profile
- Verify user's email address is valid
- Check spam/junk folder
- Review Django logs for sending errors
- Verify Mailgun sending quota (free tier has limits)
Template variables not rendering
- Ensure variable names match those passed in context
- Check for typos:
{{ user.username }}not{{ user.name }} - Verify context is passed to
send_branded_email()
Branding not applied
- Check
SiteBrandingmodel exists:python manage.py shellโfrom checktick_app.core.models import SiteBranding - Verify branding configured in profile (superuser only)
- Check
settings.SITE_NAMEandsettings.PRIMARY_COLORfallbacks
Future Enhancements
The email system is designed to support future features:
Logging Integration
The UserEmailPreferences model includes fields for future logging notifications:
notify_on_error- Receive error notificationsnotify_on_critical- Receive critical alerts
When the logging system is implemented, these will control error/alert emails.
Batch Email Capabilities
Future updates may include:
- Bulk email sending for survey invitations
- Email scheduling for reminders
- Digest emails (daily/weekly summaries)
Advanced Templates
Planned template improvements:
- HTML email editor in admin
- Template versioning
- A/B testing for email content
- Personalization based on user activity
Related Documentation
- Getting Started - Initial setup guide
- User Management - Managing users and permissions
- Branding and Theme Settings - Customizing appearance
- Authentication and Permissions - Security concepts
Support
For issues or questions:
- Check troubleshooting section above
- Review GitHub issues
- Consult Mailgun documentation
- Open a new issue with email logs and configuration (redact credentials!)