Skip to content

Markdown Email Templates

This document explains how to use the new Markdown-based email template system in PyLadiesCon Portal.

Overview

PyLadiesCon Portal now exclusively uses Markdown email templates for all email communications. This system:

  • โœ… Simplifies email development - Write once in Markdown, get both HTML and text versions
  • โœ… Improves maintainability - Single source of truth for email content
  • โœ… Provides better formatting - Rich Markdown features for better-looking emails
  • โœ… Enforces consistency - All emails use the same Markdown-based system
  • โœ… Ensures security - Automatic HTML sanitization prevents XSS attacks

Important: HTML and text templates are no longer supported. All new and existing email templates must use the Markdown format.

Quick Start

Creating a Markdown Email Template

  1. Create a .md file in the templates/emails/ directory
  2. Use Django template syntax combined with Markdown formatting
  3. Call the email function with the markdown_template parameter

Example template (templates/emails/welcome.md):

{% extends "emails/base_email.md" %}
{% load i18n %}
{% block content %}

# Welcome to PyLadiesCon, {{ user.first_name }}!

Thank you for joining our community. We're excited to have you on board.


**Welcome aboard!**

{% endblock content %}

Sending the Email

from common.send_emails import send_email

send_email(
    subject="Welcome to PyLadiesCon!",
    recipient_list=["user@example.com"],
    markdown_template="emails/welcome.md",
    context={"user": user_instance},
)

Markdown Features Supported

Headers

# H1 Header
## H2 Header  
### H3 Header

Text Formatting

**Bold text**
*Italic text*
~~Strikethrough~~
`Code text`
[Link text](https://example.com)
[Link with title](https://example.com "Link title")

Lists

## Unordered List
- Item 1
- Item 2
- Item 3

## Ordered List
1. First item
2. Second item
3. Third item

Code Blocks

```python
def hello_world():
    print("Hello, PyLadiesCon!")
### Blockquotes

```markdown
> This is a blockquote
> with multiple lines

Horizontal Rules

---

Tables (GitHub Flavored Markdown)

| Feature | Status |
|---------|--------|
| Markdown | โœ… Working |
| HTML | โœ… Working |
| Text | โœ… Working |

Django Template Integration

Markdown templates work seamlessly with Django template features:

Template Inheritance

{% extends "emails/base_email.md" %}
{% load i18n %}
{% block content %}
# Your content here
{% endblock content %}

Template Tags and Filters

# Welcome {{ user.first_name|title }}!

{% if user.is_staff %}
As a staff member, you have special privileges.
{% endif %}

Your registration date: {{ user.date_joined|date:"F j, Y" }}

## Available Teams
{% for team in teams %}
- **{{ team.name }}** - {{ team.description }}
{% endfor %}

Internationalization

{% load i18n %}
# {% trans "Welcome to PyLadiesCon" %}

{% blocktrans with name=user.first_name %}
Hello {{ name }}, thank you for joining us!
{% endblocktrans %}

Template Structure

Base Template

All email templates should extend the base template for consistency:

{% extends "emails/base_email.md" %}
{% load i18n %}
{% block content %}
# Your email content here
{% endblock content %}

Template Locations

  • Base template: templates/emails/base_email.md
  • Volunteer emails: templates/emails/volunteer/*.md
  • Sponsorship emails: templates/emails/sponsorship/*.md
  • Custom emails: templates/emails/your-app/*.md

API Reference

send_email() Function

The send_email() function now exclusively supports Markdown templates:

def send_email(
    subject: str,
    recipient_list: list,
    *,
    markdown_template: str,                   # Required Markdown template
    context: Optional[Dict[str, Any]] = None,
) -> None

Parameters:

  • subject: Email subject line
  • recipient_list: List of recipient email addresses
  • markdown_template: Path to Markdown template (required)
  • context: Dictionary of template variables

Examples:

# New Markdown approach (recommended)
send_email(
    subject="Welcome!",
    recipient_list=["user@example.com"],
    markdown_template="emails/welcome.md",
    context={"user": user},
)

send_markdown_email() Function

For direct Markdown email sending:

from common.markdown_emails import send_markdown_email

send_markdown_email(
    subject="Test Email",
    recipient_list=["test@example.com"],
    markdown_template="emails/test.md",
    context={"name": "John"},
)

Migration Complete

All email templates have been converted to Markdown format. The system no longer supports HTML or text templates.

Markdown Conversion Reference

For understanding how the conversion was done:

  1. HTML syntax converted to Markdown:
  2. <h1>Title</h1> โ†’ # Title
  3. <strong>Bold</strong> โ†’ **Bold**
  4. <a href="url">Link</a> โ†’ [Link](url)
  5. <ul><li>Item</li></ul> โ†’ - Item
  6. All email sending code updated to use markdown_template parameter
  7. Old HTML/text templates removed from the system

Before (HTML template):

{% extends "emails/base_email.html" %}
{% block content %}
<h1>Welcome {{ user.first_name }}!</h1>
<p>Thank you for joining <strong>PyLadiesCon</strong>.</p>
<ul>
    <li>Join our <a href="https://discord.com/invite/example">Discord</a></li>
    <li>Read the <a href="https://conference.pyladies.com/docs/">docs</a></li>
</ul>
{% endblock content %}

After (Markdown template):

{% extends "emails/base_email.md" %}
{% block content %}
# Welcome {{ user.first_name }}!

Thank you for joining **PyLadiesCon**.

- Join our [Discord](https://discord.com/invite/example)
- Read the [docs](https://conference.pyladies.com/docs/)
{% endblock content %}

Best Practices

๐Ÿ“ Writing Guidelines

  • Use semantic headings (#, ##, ###) for structure
  • Bold important information with **text**
  • Create clear action items with bullet points
  • Include descriptive link text instead of raw URLs
  • Use horizontal rules (---) to separate sections

๐ŸŽจ Design Considerations

  • Keep line length reasonable (under 80 characters when possible)
  • Use whitespace effectively for readability
  • Group related information under headings
  • Make calls-to-action prominent with bold text or buttons

๐Ÿ”’ Security

  • HTML is automatically sanitized to prevent XSS attacks
  • Only safe HTML tags are allowed in the output
  • Link URLs are validated and dangerous protocols are blocked
  • Template context is escaped following Django security practices

๐Ÿงช Testing

# Test Markdown email in development
from django.test import TestCase
from common.markdown_emails import send_markdown_email
from django.core import mail

class EmailTest(TestCase):
    def test_welcome_email(self):
        mail.outbox = []

        send_markdown_email(
            subject="Welcome Test",
            recipient_list=["test@example.com"],
            markdown_template="emails/welcome.md",
            context={"user": {"first_name": "Test"}},
        )

        self.assertEqual(len(mail.outbox), 1)
        email = mail.outbox[0]
        self.assertIn("Welcome Test!", email.body)
        self.assertIn("<h1>Welcome Test!</h1>", email.alternatives[0][0])

Troubleshooting

Common Issues

Template not found error:

TemplateDoesNotExist: emails/my_template.md
- Check the template path is correct - Ensure the file exists in templates/emails/ - Verify template inheritance paths

Markdown not rendering:

# Make sure you're using markdown_template, not html_template
send_email(
    subject="Test",
    recipient_list=["test@example.com"],
    markdown_template="emails/test.md",  # โ† Correct parameter
    context=context,
)

Missing dependencies:

ImportError: No module named 'markdown'
- Ensure markdown and bleach are installed - Check requirements-app.txt includes the dependencies

Debug Mode

Enable email debugging to see both HTML and text output:

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

Examples

Volunteer Onboarding Email

{% extends "emails/base_email.md" %}
{% load i18n %}
{% block content %}

# Welcome to the PyLadiesCon team, {{ profile.user.first_name }}!

Thank you for applying to volunteer with PyLadiesCon. We're excited to share that your application has been **approved**.

## ๐ŸŽ‰ Team Assignment

You have been assigned to the following team(s):

{% for team in profile.teams.all %}
- **{{ team.short_name }}** - Team Lead(s): {% for lead in team.team_leads.all %}{{ lead.user.get_full_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endfor %}

## ๐Ÿ“‹ Action Items

**Please complete these steps within 7 days:**

- **Join our [Discord server](https://discord.com/invite/example)** *(Required)*
- **Read the [Volunteer Guide](https://conference.pyladies.com/docs/)**
- **Complete your [profile setup](https://{{ current_site.domain }}{% url 'volunteer:index' %})**

---

**Questions?** Reach out to your team lead or contact us on Discord.

**Thank you for volunteering!** ๐Ÿ™

{% endblock content %}

Sponsorship Notification Email

{% extends "emails/base_email.md" %}
{% block content %}

# New Sponsorship Application ๐Ÿ“‹

Hello {{ recipient_name }}, a new sponsorship application has been submitted.

## Sponsor Details

- **Organization:** {{ profile.organization_name }}
- **Tier:** {{ profile.sponsorship_tier }}
- **Contact:** {{ profile.sponsor_contact_name }} ({{ profile.sponsors_contact_email }})
- **Amount:** ${{ profile.sponsorship_tier.amount }}

## Next Steps

Please review the application in the [admin dashboard](https://{{ current_site.domain }}/admin/).

{% endblock content %}

Contributing

When contributing new email templates:

  1. Use Markdown format for new templates (.md files)
  2. Follow the style guide in this documentation
  3. Include comprehensive tests for email functionality
  4. Update this documentation if adding new features
  5. Test in multiple email clients when possible

For technical questions about the Markdown email system, check the code in common/markdown_emails.py or reach out to the development team.