Back to Blog

Creating Beautiful HTML Email Templates for Django

admin
November 24, 2025 5 min read
145 views
Design professional, responsive email templates that look great in every email client using inline CSS and Django templating.

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

<!-- 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

<!-- 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

# 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

<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
# 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!

Comments (0)

Please login to leave a comment.

No comments yet. Be the first to comment!