Creating Beautiful HTML Email Templates for Django
# Creating Beautiful HTML Email Templates for Django
Email is crucial for e-commerce. Here's how we create professional email templates in DjangoZen that render perfectly everywhere.
## Email Client Challenges
Email HTML is stuck in 2005. You must:
- Use tables for layout
- Inline all CSS
- Avoid modern CSS features
- Test across many clients
## Basic Email Structure
```html
<!-- emails/base_email.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{% block title %}DjangoZen{% endblock %}</title>
<!--[if mso]>
<style type="text/css">
table {border-collapse: collapse;}
.fallback-font {font-family: Arial, sans-serif;}
</style>
<![endif]-->
</head>
<body style="margin: 0; padding: 0; background-color: #f4f4f4; font-family: Arial, Helvetica, sans-serif;">
<!-- Wrapper table -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f4f4f4;">
<tr>
<td align="center" style="padding: 40px 20px;">
<!-- Content table -->
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width: 600px; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
{% block content %}{% endblock %}
</table>
<!-- Footer -->
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width: 600px;">
<tr>
<td style="padding: 30px 20px; text-align: center;">
<p style="margin: 0 0 10px; font-size: 12px; color: #999999;">
© {{ current_year }} DjangoZen. All rights reserved.
</p>
<p style="margin: 0; font-size: 12px; color: #999999;">
<a href="{{ site_url }}/unsubscribe/" style="color: #999999;">Unsubscribe</a> |
<a href="{{ site_url }}/privacy/" style="color: #999999;">Privacy Policy</a>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
```
## Order Confirmation Email
```html
<!-- emails/order_confirmation.html -->
{% extends 'emails/base_email.html' %}
{% load currency_tags %}
{% block title %}Order Confirmation #{{ order.order_number }}{% endblock %}
{% block content %}
<!-- Header -->
<tr>
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 30px; text-align: center;">
<img src="{{ site_url }}/static/img/logo-white.png" alt="DjangoZen" width="150" style="max-width: 150px;">
<h1 style="margin: 20px 0 0; color: #ffffff; font-size: 28px; font-weight: bold;">
Thank You for Your Order!
</h1>
</td>
</tr>
<!-- Order info -->
<tr>
<td style="padding: 40px 30px;">
<p style="margin: 0 0 20px; font-size: 16px; color: #333333; line-height: 1.6;">
Hi {{ order.user.first_name|default:order.user.username }},
</p>
<p style="margin: 0 0 30px; font-size: 16px; color: #333333; line-height: 1.6;">
Your order <strong>#{{ order.order_number }}</strong> has been confirmed and is ready for download!
</p>
<!-- Order summary box -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f8f9fa; border-radius: 8px; margin-bottom: 30px;">
<tr>
<td style="padding: 20px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="padding: 5px 0;">
<strong style="color: #333;">Order Number:</strong>
</td>
<td style="padding: 5px 0; text-align: right; color: #666;">
{{ order.order_number }}
</td>
</tr>
<tr>
<td style="padding: 5px 0;">
<strong style="color: #333;">Order Date:</strong>
</td>
<td style="padding: 5px 0; text-align: right; color: #666;">
{{ order.created_at|date:"F j, Y" }}
</td>
</tr>
<tr>
<td style="padding: 5px 0;">
<strong style="color: #333;">Payment Method:</strong>
</td>
<td style="padding: 5px 0; text-align: right; color: #666;">
{{ order.payment_method|title }}
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- Order items -->
<h2 style="margin: 0 0 20px; font-size: 20px; color: #333333;">Order Details</h2>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden;">
<tr style="background-color: #f8f9fa;">
<th style="padding: 15px; text-align: left; font-size: 14px; color: #666;">Product</th>
<th style="padding: 15px; text-align: center; font-size: 14px; color: #666;">Qty</th>
<th style="padding: 15px; text-align: right; font-size: 14px; color: #666;">Price</th>
</tr>
{% for item in order.items.all %}
<tr>
<td style="padding: 15px; border-top: 1px solid #e9ecef;">
<strong style="color: #333;">{{ item.product.name }}</strong>
{% if item.license_type %}
<br><span style="font-size: 12px; color: #666;">{{ item.license_type }} License</span>
{% endif %}
</td>
<td style="padding: 15px; border-top: 1px solid #e9ecef; text-align: center; color: #666;">
{{ item.quantity }}
</td>
<td style="padding: 15px; border-top: 1px solid #e9ecef; text-align: right; color: #333;">
{{ item.subtotal|currency }}
</td>
</tr>
{% endfor %}
<tr style="background-color: #f8f9fa;">
<td colspan="2" style="padding: 15px; text-align: right; font-weight: bold; color: #333;">
Total:
</td>
<td style="padding: 15px; text-align: right; font-weight: bold; color: #667eea; font-size: 18px;">
{{ order.total|currency }}
</td>
</tr>
</table>
</td>
</tr>
<!-- CTA Button -->
<tr>
<td style="padding: 0 30px 40px; text-align: center;">
<table role="presentation" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
<tr>
<td style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px;">
<a href="{{ site_url }}/dashboard/downloads/"
style="display: inline-block; padding: 16px 32px; color: #ffffff; text-decoration: none; font-weight: bold; font-size: 16px;">
Download Your Products
</a>
</td>
</tr>
</table>
</td>
</tr>
<!-- Help section -->
<tr>
<td style="padding: 30px; background-color: #f8f9fa; border-top: 1px solid #e9ecef;">
<p style="margin: 0 0 10px; font-size: 14px; color: #666; text-align: center;">
Need help? Contact our support team
</p>
<p style="margin: 0; font-size: 14px; text-align: center;">
<a href="mailto:support@djangozen.com" style="color: #667eea;">support@djangozen.com</a>
</p>
</td>
</tr>
{% endblock %}
```
## Sending Emails from Django
```python
# utils/email.py
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.conf import settings
from django.utils import timezone
def send_templated_email(to_email, template_name, context, subject):
"""Send an HTML email with text fallback"""
# Add common context
context.update({
'site_url': settings.SITE_URL,
'site_name': 'DjangoZen',
'current_year': timezone.now().year,
})
# Render templates
html_content = render_to_string(f'emails/{template_name}.html', context)
text_content = render_to_string(f'emails/{template_name}.txt', context)
# Create email
email = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[to_email] if isinstance(to_email, str) else to_email,
reply_to=[settings.SUPPORT_EMAIL],
)
email.attach_alternative(html_content, 'text/html')
return email.send()
# Usage
send_templated_email(
to_email=order.user.email,
template_name='order_confirmation',
context={'order': order},
subject=f'Order Confirmation #{order.order_number}'
)
```
## Responsive Email Design
```html
<style>
@media only screen and (max-width: 600px) {
.wrapper {
width: 100% !important;
}
.content {
padding: 20px !important;
}
.button {
width: 100% !important;
display: block !important;
}
.hide-mobile {
display: none !important;
}
.stack-column {
display: block !important;
width: 100% !important;
}
}
</style>
```
## Testing Emails
1. **Litmus/Email on Acid**: Test across 90+ clients
2. **Mail-tester.com**: Check spam score
3. **Local testing**: Use MailHog or Django's console backend
```python
# settings.py (development)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
```
## Best Practices
1. **Always provide plain text version**
2. **Keep width under 600px**
3. **Use web-safe fonts**
4. **Inline all CSS**
5. **Test extensively**
6. **Include unsubscribe links**
Beautiful emails build trust and drive engagement!