DEV Community

Ajit Kumar
Ajit Kumar

Posted on

Mastering Emails in Django: From Hardcoded Strings to Branded Templates

Sending emails is a core requirement for almost every web application—whether it's for password resets, OTP verification, or invitations. Django provides a powerful, flexible email wrapper that makes this process seamless.

In this guide, we’ll move from "just getting it to work" to professional, template-based email delivery.


1. The Foundation: Configuring Your Settings

Before you write a single line of Python, Django needs to know how to send the mail. This is handled in settings.py.

For Development (The Console Backend)

You don't want to spam your actual inbox while testing. Use the console backend to print emails directly to your terminal.

# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Enter fullscreen mode Exit fullscreen mode

For Production (SMTP)

When you're ready to go live, you'll likely use an SMTP provider (like Gmail, SendGrid, or Mailgun).

# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-app-password' # Use an App Password, not your login password!
DEFAULT_FROM_EMAIL = 'Your App Name <noreply@yourdomain.com>'

Enter fullscreen mode Exit fullscreen mode

2. Level 1: Hardcoded Content (The Quick Way)

If you are sending a very simple notification, you can keep the logic inside your views.py. We use EmailMultiAlternatives to ensure we send both a plain-text version (for safety) and an HTML version.

from django.core.mail import EmailMultiAlternatives
from django.conf import settings

def send_simple_otp(user, otp_code):
    subject = "Your Verification Code"
    from_email = settings.DEFAULT_FROM_EMAIL
    to = [user.email]

    # Plain text fallback
    text_content = f"Your code is {otp_code}. It expires in 10 minutes."

    # HTML version string
    html_content = f"""
        <html>
            <body>
                <h2 style="color: #2c3e50;">Verification Code</h2>
                <p>Your code is: <strong>{otp_code}</strong></p>
            </body>
        </html>
    """

    msg = EmailMultiAlternatives(subject, text_content, from_email, to)
    msg.attach_alternative(html_content, "text/html")
    msg.send()

Enter fullscreen mode Exit fullscreen mode

3. Level 2: Decoupling with Templates (The Professional Way)

Hardcoding HTML inside Python is messy. As your emails grow (adding headers, footers, and branding), you should use Django’s template engine.

Folder Structure

Create a dedicated folder for email templates within your app:
your_app/templates/emails/otp_email.html

The Template (otp_email.html)

<!DOCTYPE html>
<html>
<head>
    <style>
        .code { font-size: 20px; color: blue; }
    </style>
</head>
<body>
    <p>Hi {{ first_name }},</p>
    <p>Your requested code is: <span class="code">{{ otp_code }}</span></p>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

The View Logic

We use render_to_string to turn that HTML file into a string for our email.

from django.template.loader import render_to_string
from django.utils.html import strip_tags

def send_template_email(user, otp_code):
    context = {
        'first_name': user.first_name,
        'otp_code': otp_code,
    }

    # Render the HTML template
    html_content = render_to_string('emails/otp_email.html', context)

    # Automatically generate the plain-text version from the HTML
    text_content = strip_tags(html_content) 

    msg = EmailMultiAlternatives("Your Code", text_content, settings.DEFAULT_FROM_EMAIL, [user.email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

Enter fullscreen mode Exit fullscreen mode

4. Advanced: Static Content (Logos & CSS)

This is where most beginners get stuck. Emails are not web pages. They are viewed in external clients (Gmail, Outlook) that cannot access your local static files.

🖼️ Handling Images (Logos)

Recipients cannot see <img src="/static/logo.png">. You have two choices:

  1. Absolute URLs: Host the image on a public server (S3, Cloudinary) and use the full https:// path.
  2. CID Attachment: Embedding the image as an attachment (more complex).

Best Practice: Host your logo on a public CDN and hardcode the absolute URL in your template:

<img src="https://your-cdn.com/logo.png" width="150" alt="MyBrand">

Enter fullscreen mode Exit fullscreen mode

🎨 Handling CSS

Email clients often strip out <style> tags or <link> tags.

  • Always use Inline CSS. * Instead of .btn { color: red; }, use <a style="color: red;">.

🛡️ Best Practices Checklist

  1. Fail Silently? Never set fail_silently=True in development. You want to see the errors (like authentication failure) so you can fix them.
  2. Background Tasks: Sending an email is slow (1-3 seconds). Use Celery or Django-Q to send emails in the background so the user doesn't have to wait for the page to reload.
  3. Plain Text Fallback: Always provide a text_content version. It improves your spam score.
  4. Security: Never store your EMAIL_HOST_PASSWORD directly in settings.py. Use environment variables (.env files).

Conclusion

Start with the Console Backend to test your logic, move to templates for better organization, and always remember that inline styles are your best friend for email design.

Happy Coding! 🚀

Top comments (0)