Skip to main content

A chainable Django email sender utility.

Project description

๐Ÿ“ง Django Email Sender Version 2

Django Email Sender is a lightweight and highly customisable utility for sending emails in Django using rich templates and a clean, chainable API.


๐Ÿš€ What's New in v2.0

Version 2.0 brings powerful new features that make your email workflows smarter and more robust:

  • โœ… Email Delivery Tracking
    Get immediate feedback on whether an email was sent successfully or failed, with access to error messages if something goes wrong.

  • ๐Ÿ” Field Management (Preserve/Clear)
    Fine-grained control over email fieldsโ€”clear or preserve specific fields even after setting them.

  • ๐Ÿ“ Logging and Database Integration
    Easily hook into your logging system to record email activityโ€”log to file, console, or even a database.

  • ๐Ÿ”ง New method introduced to Chainable API
    Enhanced method chaining to improve developer ergonomics and reduce boilerplate.


โœจ Features

  • Rich HTML and plain text templates
  • Chainable email building methods
  • Custom function to format your logging
  • Additional methods for fine grain control (v2.0+)
  • Logging and database integration (v2.0+)
  • i18n support (v2.0+)

๐Ÿงพ Changelog

v2.0

  • Added support for logging (file/database)
  • Added delivery status tracking with error feedback
  • Introduced methods to clear or preserve fields dynamically
  • Improved documentation and method chaining flexibility
  • Ability to get metadata for a sent email
  • See you payload before or after your email has been set
  • and so much more

It provides a clean, reusable, and chainable utility class for sending emails in Django, supporting both HTML and plain text templates, dynamic context injection, and flexible usage โ€” whether used directly, via subclassing, or abstracted into functions.

Table of Contents

Why Use This?

While Django already provides a way to send emails, it can become verbose and repetitive. EmailSender abstracts the boilerplate and lets you send templated emails fluently.

๐Ÿ” Back to top

๐Ÿ”„ Upgrading from Version 1

Version 2 introduces powerful new features โ€” with zero breaking changes. Your current integration will continue to work as expected!

๐Ÿš€ Whatโ€™s New in v2:

  • โœ… Integrated Logging & Database Support
    Log email activity to a file or database with customisable logging levels (debug, info, warning, error).

  • ๐Ÿงผ Advanced Field Management

    • Auto-reset fields after sending
    • Clear specific fields or all fields
    • Preserve chosen fields across sends
  • ๐Ÿ“‹ Selective Logging with Inclusion/Exclusion Rules
    Choose exactly which fields to log (log only) and which to exclude (log exclude) โ€” giving you full control over what gets logged.

  • ๐Ÿ“Š Custom Log Range
    Define a log range to specify precisely what data is captured in logs, improving privacy and debugging clarity.

  • ๐Ÿง  Smarter Error Handling
    Now with field-level and error messages for easier debugging and localisation.


โœ… No breaking changes โ€” Drop-in upgrade, full backwards compatibility.

๐Ÿ“ฆ Whatโ€™s New in Version 2?

Version 2 of Django Email Sender brings major upgrades while maintaining full backwards compatibility. Itโ€™s smarter, more flexible, and much more powerful.

  • ๐Ÿ“ Custom Logger Integration

    • Plug in your own logger for full control over log output and formatting.
    • Easily inherit from the base abstract model to create a custom log model.
    • Save logs to the database or file via the EmailLogger integration.
  • ๐Ÿ”„ Auto-Reset After Sending
    Automatically reset all email fields after sending with auto_reset=True to prevent accidental resends.

  • ๐Ÿ“Œ Preserve Specific Fields
    Use preserve_fields to retain selected fields even after an auto-reset.

  • ๐Ÿงผ Individual Field Clearing
    Clear only what you need with new methods like clear_subject(), clear_context(), clear_to(), and more.

  • โš™๏ธ Inclusion/Exclusion Logging
    Choose exactly which fields to log (log_only) and which to exclude (log_exclude) for fine-grained control.

  • ๐Ÿ“Š Custom Log Range
    Define a logging range to specify precisely what data is logged, improving privacy and audit clarity.

  • ๐Ÿ—‚ EmailSenderLogger Class
    A dedicated logger class to manage logging behaviour, output types, and integration points.

  • ๐Ÿงฐ More Utility Methods
    Version 2 introduces several new helper methods to simplify template handling, payload inspection, metadata access, and more.
    Check the full documentation for all available methods and usage examples.

โœจ Features

  • ๐Ÿ› ๏ธ Custom logger integration
    Plug in your own logger for full control over log formatting and output. Easily log email activities to files or databases.

  • ๐Ÿ—‚๏ธ Database logging support
    Integrate email logs directly into your database via the EmailLogger, with easy-to-configure log models.

  • ๐Ÿ”— Chainable API
    Fluent, easy-to-use API for configuring email attributes โ€” e.g., .to(), .from_address(), .subject(), .context(), etc.

  • ๐Ÿ”„ Auto-reset
    Automatically reset all fields after sending an email, keeping your instance clean for reuse.

  • ๐Ÿงผ Field clearing and preservation
    Clear individual fields (e.g., subject, recipients, templates) or preserve them between email sends.

  • ๐Ÿงผ logging including inclusion/exclusion
    Chose what fields you would like to log (e.g., subject, recipients, templates)

  • ๐Ÿ“จ HTML and plain text templates
    Send both HTML and plain-text emails with rich template support for flexibility.

  • ๐Ÿงฉ Lightweight & easy integration
    Simple to integrate into any Django project without unnecessary complexity.

  • ๐Ÿงฑ Clean architecture & folder structure
    Encourages good code practices with reusable components and clear folder organization.

  • ๐Ÿงฌ Subclassing & functional abstractions
    Extensible through subclassing or functional abstractions for maximum flexibility.

  • ๐Ÿงช Testable and extendable
    Designed with testability in mind, making it easy to write unit and integration tests.

๐Ÿ” Back to top


Sample Code Comparison: Version 1 vs Version 2

Version 1 (Classic Usage)

# Without auto-reset and custom logger (classic usage)
from django_email_sender.email_sender import EmailSender
  (
   EmailSender.create() 
    .from_address("no-reply@example.com") 
    .to(["test@example.com"]) 
    .with_subject("Welcome Email") 
    .with_context({"username": "John"}) 
    .with_html_template("welcome.html", folder_name="welcome") 
    .with_text_template("welcome.txt", folder_name="welcome") 
    .send()
  )

๐Ÿš€ Version 2 โ€” Demo: Custom Logger & Database Logging

## 

```python
import logging

from django_email_sender.email_sender import EmailSender
from django_email_sender.email_logger import EmailSenderLogger
from django_email_sender.email_sender_constants import LoggerType, EmailSenderConstants

# For database logging, import your custom model inheriting from EmailBaseLog:
from <your_app>.models import YourCustomEmailLog


# ---------------------------
# Example: models.py
# ---------------------------
from django.db import models
from django_email_sender.models import EmailBaseLog

class YourCustomEmailLog(EmailBaseLog):
    # You can add any custom fields or methods if needed
    pass


# ---------------------------
# Example: views.py
# ---------------------------

# Optional: custom formatter for logger messages
def my_custom_formatter(exception: Exception, trace: str) -> str:
    return f"[CUSTOM ERROR] {str(exception)} | Trace: {trace}"


# Set up your own logger
logger = logging.getLogger("email_sender")

# Start the logging session
email_sender = (
    EmailSenderLogger.create()
    .start_logging_session()
    .enable_verbose()                                    # Enables verbose output for detailed logging
    .config_logger(logger, LoggerType.DEBUG)             # Plug in your custom logger
    .add_email_sender_instance(EmailSender.create())     # Attach the EmailSender instance
    .add_log_model(YourCustomEmailLog)                   # Use your custom DB model for logs
    .enable_email_meta_data_save()                       # Enable to save to db
    .from_address("no-reply@example.com")
    .to("jackie@example.com")
    .add_new_recipient("mj@example.com")
    .add_new_recipient("ba@example.com")
    .add_new_recipient("eu@gmail.com")
    .add_new_recipient("scott_mccall@example.com")
    .with_context({"promo_code": "12345"})
    .with_subject("Promo code for our loyal customers")
    .with_html_template("test_email.html", "sender")
    .with_text_template("test_email.txt", "sender")
    .send()
)

# Access the payload (for debugging or logging)
payload = email_sender.payload

# Access email meta-data (headers, timestamps, etc.)
meta_data = email_sender.email_meta_data

โœ… Notes:

Make sure your logger is configured in settings.py.

Your custom model must inherit from EmailBaseLog.

You can override or extend the base model with your own fields.

๐Ÿ’ก Tip:

This is just a basic example โ€” additional configuration options like preserve_fields, auto_reset, clear_*() methods, etc. See the documentation for details

๐Ÿ“ Minimal Example โ€” Custom Logger Only (No DB Integration)

A minimal example that shows off just the custom logging integration without the database part. This is great for users who want to plug in a logger quickly without setting up a database model.

import logging

from django_email_sender.email_sender import EmailSender
from django_email_sender.email_logger import EmailSenderLogger
from django_email_sender.email_sender_constants import LoggerType

# Optional: custom formatter for your log output
def my_custom_formatter(exception: Exception, trace: str) -> str:
   return f"[CUSTOM LOG] {str(exception)} | Traceback: {trace}"

# Set up your logger (must be configured in Django settings)
logger = logging.getLogger("email_sender")

# Start logging session
email_sender = (
   EmailSenderLogger.create()
   .start_logging_session()
   .enable_verbose()                  # Verbose logging for detailed trace
   .config_logger(logger, LoggerType.INFO)
   .set_custom_formatter(my_custom_formatter)
   .add_email_sender_instance(EmailSender.create())
   .from_address("noreply@example.com")
   .to("user@example.com")
   .with_subject("Welcome!")
   .with_html_template("emails/welcome.html", "sender")
   .with_text_template("emails/welcome.txt", "sender")
   .send()
)

# Optionally access logging details
print(email_sender.payload)
print(email_sender.email_meta_data)

โšก Highlights

No custom model setup required.

Plug-and-play logging with any Python logger.

Add your own formatter to control log message output.

Great for development or production environments where full DB logging isnโ€™t needed.

What is EmailSender and what is EmailSenderLogger, and why are they needed?

EmailSender is a module that allows you to send customisable emails with rich templates. It abstracts the multiple steps needed to send an email and enables you to do so in a quick, easy, and chainable manner.

While EmailSender does support sending emails, it lacks several functionalities:

  • No tracking of the sending process, making debugging difficult
  • No logging capabilities
  • No visibility of payloads (pre-send or post-send)
  • No access to metadata
  • No database interaction
  • No delivery tracking

Why is EmailSenderLogger needed?

Although EmailSender has recently been upgraded with new featuresโ€”such as clearing specific fields, resetting values, and preserving data after sendingโ€”it still focuses solely on sending emails. As a result, it doesnโ€™t provide methods to track or log email operations.

This is where EmailSenderLogger comes in.

Itโ€™s a lightweight wrapper that extends EmailSender with powerful features:

  • Integrated logging support
  • Optional database logging
  • Email report summaries (e.g., sent vs not sent)
  • Real-time access to the email payload during construction
  • Full metadata retrieval after the email is sent
  • Preview support for both HTML and plain text templates
  • And much more

โš ๏ธ Note: EmailSenderLogger does not include a logger or database by default. You must inject these via the provided public methods.

If you choose not to supply a logger or database, thatโ€™s fineโ€”EmailSenderLogger will still work, inheriting all functionality from EmailSender.

To use it, simply power EmailSenderLogger with an instance of EmailSender:

email_sender = (
    EmailSenderLogger.create()
        .add_email_sender_instance(EmailSender.create()) # must be powered by the instance of EmailSender
        .from_address("noreply@example.com")
        .to("user@example.com")
        .with_subject("Welcome!")

        # rest of the chain here
        # ...
        .send()
)

# do something with `email_sender` if you want

๐Ÿ” Back to top

What if Iโ€™m not using the advanced features of EmailSenderLogger?

Thatโ€™s completely fine. If you donโ€™t require logging or database integration, you can continue using EmailSender directly.

However, EmailSenderLogger is a subclass of EmailSender, meaning it fully supports all core email-sending features. If you prefer to use EmailSenderLogger (e.g., for future extensibility), simply initialise it with an instance of EmailSender.

EmailSenderLogger.create().add_email_sender_instance(EmailSender.create() or EmailSender())

Unless you explicitly opt-in to the enhanced features (e.g., by starting a logging session), EmailSenderLogger behaves just like EmailSender.


๐Ÿ” Back to top

Available Methods

Method Description

EmailSender methods

 - create()                                                                                    
 - from_address(email)                                                                        
 - to(recipients)                                                                            
 - with_subject(subject)                                                                     
 - with_context(context)                                                                     
 - with_text_template(folder_name="folder-name-here", template_name="template-name-here.txt")  
 - with_html_template(folder_name="folder-name-here", template_name="template-name-here.html") 
 - with_headers(headers) 
 - clear_from_email()       #  Added in version 2 
 - clear_to_email()         #  Added in version 2 
 - clear_subject()          #  Added in version 2 
 - clear_context()          #  Added in version 2 
 - clear_to_email()         #  Added in version 2 
 - clear_html_template()    #  Added in version 2     
 - clear_text_template()    #  Added in Version 2     
 - clear_from_email()       #  Added in Version 2 
 - clear_all_fields()       #  Added in Version 2                                   
 - send()

๐Ÿ” Back to top

EmailSender Class API Reference

๐Ÿ”จ create()

Factory method โ€” Instantiates and returns an EmailSender object.

๐Ÿ“ค from_address(email)

Sets the sender's email address.
email: A string representing the sender's email (e.g. noreply@yourdomain.com).

๐Ÿ“ฅ to(recipients)

Sets the recipient(s) of the email.
recipients: A string or list of strings with one or more email addresses.

New in version 2, no longer accepts a list. Use .add_new_recipient() to add multiple recipients

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)  
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_subject() # Clears the subject field from the chain

   # or it can be cleared directly from the instant method
   email_sender.clear_subject()

Note
The .to(...) method accepts either a single email string or a list of email addresses.
However, the list format is supported only for backwards compatibility.
If you pass a list like ["first_email@example.com", "second_email@example.com"], only the first email ("first_email@example.com") will be used.

โš ๏ธ Important: Passing a list only works when using EmailSender.
If you're using EmailSenderLogger, passing a list will raise an error. In that case, always pass a single string email address to .to(...).

To add multiple recipients, use the .add_new_recipient() method instead.

โš ๏ธ Do not use .to(...) to add multiple emails โ€” it will overwrite the to_email field rather than append to it.

๐Ÿ“ with_subject(subject)

Sets the subject line of the email.
subject: A string for the email's subject.

๐Ÿ”ง with_context(context)

Provides the context dictionary for rendering templates.
context: Optional. A dictionary containing variables that can be used in both HTML and text templates. This method is only necessary if your templates require dynamic content (variables) to be rendered.

๐Ÿ“„ with_text_template(folder_name="folder-name-here", template_name="template-name-here.txt")

Specifies the plain text template.
If folder_name is omitted, defaults to emails_templates/.

๐ŸŒ with_html_template(folder_name="folder-name-here", template_name="template-name-here.html")

Specifies the HTML version of the email template.
If folder_name is omitted, defaults to emails_templates/.

๐Ÿงพ with_headers(headers)

Optional method to add custom email headers.
headers: A dictionary of headers (e.g. {"X-Custom-Header": "value"}).

โœ‚๏ธ clear_subject()

Clears the subject field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the subject field without affecting other fields in the chain.

๐Ÿงณ clear_context()

New in version 2.

Clears the context field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the context field without affecting other fields in the chain

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_context()  # Clears the context field from the chain

   # or it can be cleared directly from the instant method
   email_sender.clear_context()  # Clears the context field from the chain

๐Ÿ“จ clear_to_email()

New in version 2.

Clears the recipient field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the recipient field without affecting other fields in the chain.

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_to_email()  # Clears the recipient field from the chain


   # or it can be cleared directly from the instant method
  email_sender.clear_to_email()

๐Ÿ“จ clear_from_email()

New in version 2.

Clears the sender email field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the sender email field without affecting other fields in the chain

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_from_email()  # Clears the sender email field from the chain


   # or it can be cleared directly from the instant method
  email_sender.clear_from_email()

๐Ÿง‘โ€๐Ÿ’ป clear_html_template()

New in version 2.

Clears the HTML template field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the HTML template field without affecting other fields in the chain.

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_html_template()  # Clears the HTML template field from the chain


   # or it can be cleared directly from the instant method
  email_sender.clear_html_template()  

๐Ÿ“ clear_text_template()

New in version 2.

Clears the text template field to its default empty value. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears the text template field without affecting other fields in the chain.

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = EmailSender.create()

    email_sender.from_address(from_email)
                .to(user.email)
                    .with_subject(subject)
                    .with_context({"username": 'John'})
                    .with_text_template(text_registration_path, folder_name="emails")
                    .with_html_template(html_registration_path, folder_name="emails")
                    .send()
                    .clear_text_template()  # Clears the text template field from the chain


   # or it can be cleared directly from the instant method
  email_sender.clear_text_template() 

๐Ÿ”„ clear_all_fields()

New in version 2.

Clears all fields to their default empty values. This method is optional and can be called as part of a method chain. It's only relevant if the object has been instantiated and used as a chain. Calling this method clears all fields without affecting the rest of the method chain or the logger if added.

    from django_email_sender.email_sender import EmailSender

    # instantialize the Email sender
    email_sender = (
            
        EmailSender.create()
        email_sender.from_address(from_email)
                    .to(user.email)
                        .with_subject(subject)
                        .with_context({"username": 'John'})
                        .with_text_template(text_registration_path, folder_name="emails")
                        .with_html_template(html_registration_path, folder_name="emails")
                        .send()
                        .clear_all_fields()  # Clears all fields in the chain without but leaves fields like logger, etc intact
    )

   # or it can be cleared directly from the instant method
  email_sender.clear_all_fields()   

๐Ÿ“ฌ send(auto_reset=False)

Sends the email using the provided configuration and templates.

Optional parameters New in version 2.

auto_reset If set to True, all fields will be cleared after the email is sent. Default is False.`


๐Ÿ” Back to top

EmailSenderLogger Class API Reference

๐Ÿ”จ create()

Factory method โ€” Instantiates and returns an EmailSenderLogger object.


๐Ÿ“ค add_email_sender_instance(email_sender_instance)

Sets the core EmailSender instance that will be used to send emails.


๐Ÿ“ฅ to(recipients)

The ability to add multiple recipients via a list has been removed and replaced with add_new_recipient.
This method now only accepts a string. To add multiple recipients use add_new_recipient method


๐Ÿ“ฅ add_new_recipient(recipient)

New in version 2.
Accepts a string and adds it to the set of recipients. To add multiple recipients, call this method repeatedly. The method uses a set to ensure that recipient names are unique.


๐Ÿ“ with_subject(subject)

Sets the subject line of the email.


๐Ÿ”ง with_context(context)

Sets the context dictionary for dynamic rendering of email templates.


๐Ÿ“„ with_text_template(folder_name, template_name)

Specifies the plain text template to use.
If folder_name is omitted, defaults to emails_templates/.


๐ŸŒ with_html_template(folder_name, template_name)

Specifies the HTML version of the email template.
If folder_name is omitted, defaults to emails_templates/.


๐Ÿ“‘ with_headers(headers)

Optional method to add custom email headers (as a dictionary).


๐Ÿงฉ set_custom_formatter(custom_formatter)

Adds a custom error formatter to customise how exceptions and traces are logged.


๐Ÿ›  config_logger(logger: Logger, log_level: LoggerType)

Integrates an external Python logger and sets its log level.


Logger Configuration

๐Ÿงพ To customise how and at what level email sending is logged, use the config_logger(). The levels can be added manually or used with LoggerType constants provided.

  from django_email_sender.email_sender_constants import LoggerType

  email_logger.config_logger(my_logger, LoggerType.INFO)
Constant Description
LoggerType.INFO Standard informational messages
LoggerType.WARNING Non-critical issues worth noting
LoggerType.ERROR Errors encountered during sending
LoggerType.DEBUG Verbose output for development/debugging

๐Ÿ—ƒ add_log_model(log_model: EmailBaseLog)

Attaches a custom model for database email logging.
The model provided must inherit from the abstract base model EmailBaseLog The module path : from django_email_sender.models import EmailBaseLog


๐Ÿงช to_debug(message)

Logs a message at the DEBUG level.


โ„น๏ธ to_info(message)

Logs a message at the INFO level.


โš ๏ธ to_warning(message)

Logs a message at the WARNING level.


โŒ to_error(message)

Logs a message at the ERROR level.


๐Ÿ”Š enable_verbose()

Enables verbose logging (e.g., shows additional trace and context information).


๐Ÿ”‡ disable_verbose()

Disables verbose mode and limits logs to essential information.


Logging Control Methods

Use the following methods to manage logging dynamically during the email sending flow.

start_logging_session() ๐Ÿ”“

Starts the logging session and returns self for chaining.
Useful if you want to enable logging partway through your workflow.

Note: If a logger has been added, you must call this method โ€” otherwise, no information will be logged, and youโ€™ll see the following output in your logs:

[2025-05-10 17:41:37,136] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-10 17:41:37,136] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-10 17:41:37,136] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-10 17:41:37,136] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]

stop_logging_session() ๐Ÿ”’

Completely disables further logging and ends the session.


pause_logging() โธ๏ธ

Temporarily pauses logging without clearing state.


resume_logging() โ–ถ๏ธ

Resumes logging after a pause_logging() call.

๐Ÿ“ฌ is_email_sent (Property)

Returns True if the last email was successfully sent, otherwise False.


๐Ÿ”ข email_delivery_count (Property)

Returns the number of successfully delivered emails in the current session.


๐Ÿ“ฆ payload (Property)

Returns the email payload dictionary. Useful for auditing and testing.


๐Ÿงพ email_meta_data (Property)

Returns meta information such as timestamps, recipients, and status.


๐Ÿงฎ return_successful_payload() (Not chainable)

Returns a copy of the _field_changes dictionary, which logs what fields were changed and when.
This ensures the original audit trail remains unmodified.


๐Ÿง  set_traceback(show_traceback: bool, method_tracing: bool = False )

method_tracing: show exactly which methods were called during your email building process, set_traceback() is your friend. It provides a step-by-step breakdown of the chained method calls that lead up to sending an email. Note, this must be set to True, default is False show_traceback: shows you a traceback error including the point where the error originated.


๐Ÿ›‘ log_only_fields(*fields)

Restricts logging to specific fields (e.g., only subject or to).
Useful for minimising log verbosity.


๐Ÿšซ exclude_fields_from_logging(*fields)

Excludes specific fields from being logged, even if logging is enabled.


๐Ÿ”„ reset_field_logging_filters()

Clears all field-based filters (log_only_fields and exclude_fields_from_logging) and resets to default logging behaviour.


๐Ÿง  enable_email_meta_data_save(save_to_db=True)

Enables metadata saving for each email sent. If save_to_db=True, the metadata will also be saved to the database via the configured model.

๐Ÿง  return_successful_payload()

Returns the email payload for logging, but only if the email was successfully processed.

๐Ÿ” Back to top

Additional API Notes

๐Ÿ” Shared Methods Between EmailSenderLogger and EmailSender

๐Ÿ“Ž Since EmailSenderLogger is a wrapper around EmailSender, it inherits many of the same methods. For more detailed explanations, refer to the EmailSender API section.

Using Model Constants to Minimise Errors

๐Ÿ“จ To reduce the risk of typos and improve code clarity, both EmailSender and EmailSenderLogger support the use of constants via the EmailSenderConstants enum.

Instead of hardcoding strings like "from_email" or "subject" when specifying fields, use the enums for safer, auto-completable references.

โš ๏ธ Note: These enums must be used with .value, as they are instances of Enum. This can then be passed in methods like exclude_fields_from_logging or log_until_field


EmailSenderConstants Fields ๐Ÿ” 

Constant Field Name Description
EmailSenderConstants.Fields.FROM_EMAIL "from_email" The sender's email address
EmailSenderConstants.Fields.TO_EMAIL "to_email" The recipient's email address
EmailSenderConstants.Fields.SUBJECT "subject" The subject line of the email
EmailSenderConstants.Fields.HTML_TEMPLATE "html_template" The HTML template used to render the email body
EmailSenderConstants.Fields.TEXT_TEMPLATE "text_template" The plain text version of the email body
EmailSenderConstants.Fields.CONTEXT "context" A dictionary of variables used in template rendering
EmailSenderConstants.Fields.HEADERS "headers" Optional custom email headers
EmailSenderConstants.Fields.LIST_OF_RECIPIENT "list_of_recipients" A list of recipient email addresses
EmailSenderConstants.Fields.EMAIL_ID "email_id" Unique identifier for the email instance

๐Ÿ“Š Logging-specific use (with EmailSenderLogger)

from django_email_sender.email_sender_constants import EmailSenderConstants

email_sender_with_logging = EmailSenderLogger.create().start_logging_session()
email_sender_with_logging.exclude_fields_from_logging(
    EmailSenderConstants.Fields.CONTEXT.value,
    EmailSenderConstants.Fields.HEADERS.value,
)

This approach ensures consistency across your codebase and provides a single source of truth for all field and method references related to EmailSender and EmailSenderLogger.= "add_new_recipient"

๐Ÿ” Back to top

EmailSender and EmailSenderLogger Methods

Core Functions

Name Type Chainable Description Defined In
create() Method โœ… Factory method to instantiate the class Both
to(recipients) Method โœ… Set recipient(s) Both
with_subject(subject) Method โœ… Set the subject of the email Both
with_context(context) Method โœ… Set template context Both
with_text_template(...) Method โœ… Attach plain text template Both
with_html_template(...) Method โœ… Attach HTML template Both
with_headers(headers) Method โœ… Add custom headers Both
send(auto_reset=False, ...) Method โŒ Sends the email Both
clear_subject() Method โœ… Clears the subject field Both
clear_context() Method โœ… Clears the context dictionary Both
clear_to_email() Method โœ… Clears the recipient(s) Both
clear_from_email() Method โœ… Clears the from address Both
clear_html_template() Method โœ… Clears the HTML template Both
clear_text_template() Method โœ… Clears the text template Both
clear_all_fields() Method โœ… Clears all email-related fields Both

Database Access

Name Type Chainable Description Defined In
add_email_sender_instance() Method โœ… Inject an EmailSender into the logger wrapper EmailSenderLogger
add_log_model() Method โœ… Attach a model to persist email logs EmailSenderLogger
enable_email_meta_data_save() Method โœ… Enables saving of email meta to the database EmailSenderLogger

Logging and Verbose Functions

Name Type Chainable Description Defined In
log_only_fields() Method โœ… Only log specific fields EmailSenderLogger
exclude_fields_from_logging() Method โœ… Exclude fields from logging EmailSenderLogger
start_logging_session() Method โœ… Enable logging mode EmailSenderLogger
stop_logging_session() Method โœ… Stop logging and finalise log object EmailSenderLogger
pause_logging() Method โœ… Temporarily stop logging changes EmailSenderLogger
resume_logging() Method โœ… Resume logging after a pause EmailSenderLogger
enable_verbose() Method โœ… Enable verbose logging EmailSenderLogger
disable_verbose() Method โœ… Disable verbose logging EmailSenderLogger
set_custom_formatter() Method โœ… Set a formatter for custom log formatting EmailSenderLogger
set_traceback() Method โœ… Enable stack trace logging on errors and the order methods were called EmailSenderLogger
config_logger() Method โœ… Configure logger object and the log level EmailSenderLogger

Logging Level Methods

Name Type Chainable Description Defined In
to_info() Method โœ… Changes the level to info EmailSenderLogger
to_debug() Method โœ… Changes the level to debug EmailSenderLogger
to_warning() Method โœ… Changes the level to warning EmailSenderLogger
to_error() Method โœ… Changes the level to error EmailSenderLogger

Properties and Metadata

Name Type Chainable Description Defined In
is_email_sent Property โŒ Property that returns if the email was sent EmailSenderLogger
email_delivery_count Property โŒ Property that returns number of deliveries EmailSenderLogger
email_meta_data Property โŒ Returns metadata of sent email EmailSenderLogger
payload Property โŒ Property returning the full internal state EmailSenderLogger
return_successful_payload() Method โŒ Returns a copy of the payload (audit fields) EmailSenderLogger

Explanation of Categories:

  • Core Functions: The main email sending functionality, like setting recipients, subject, context, templates, headers, and clearing fields.

  • Database Access: Methods that deal with interacting with a database, logging email details, saving email meta data, and configuring the logger.

  • Logging and Verbose Functions: Methods that manage the logging session, verbosity, and custom log formatting.

  • Properties and Metadata: Methods that return properties related to the email state, including whether it was sent, the delivery count, and email metadata.


๐Ÿ” Back to top

Code Style Tips

๐Ÿ”„ Formatting long method chains

When chaining multiple methods, breaking the chain onto separate lines can cause syntax errors unless you use an escape character (\). However, this approach can be difficult to read. A cleaner solution is to wrap the chain in parentheses.

๐Ÿ”น Using backslashes (\)

This works but can become harder to read as the chain grows:

# Assume that you passed in a user class

EmailSender.create()\
    .from_address(from_email)\
    .to([user.email])\
    .with_subject(subject)\
    .with_context({"username": user.username})\
    .with_text_template(text_registration_path, folder_name="emails")\
    .with_html_template(html_registration_path, folder_name="emails")\
    .send()

๐Ÿ”น Using parentheses (recommended)

This method is cleaner, more readable, and less error-prone:

   
   # Assume that you passed in a user class

    EmailSender.create()
    .from_address(from_email)
    .to([user.email])
    .with_subject(subject)
    .with_context({"username": user.username})
    .with_text_template(text_registration_path, folder_name="emails")
    .with_html_template(html_registration_path, folder_name="emails")
    .send()

๐Ÿ” Back to top


Installation via Pypi

django-email-sender is a Django package that allows you to send emails using customizable templates, with easy-to-use methods for setting the sender, recipients, subject, and context.

Installation

To install the package: pip install django-email-sender

For more details, visit the PyPI page.

๐Ÿ” Back to top

Requirements

Why Python 3.10?

One of the key features used in this project is the match-case syntax, which offers a more readable and expressive way to handle complex conditional logic.

Example Usage:

def log_level_handler(level: str) -> str:
    match level.lower():
        case "debug":
            return "Logging in debug mode"
        case "info":
            return "Standard info logging"
        case "warning":
            return "Warning: Check your configuration"
        case "error":
            return "Error occurred during processing"
        case _:
            return "Unknown logging level"

This syntax is not available in versions prior to Python 3.10, so attempting to run the library on an earlier version will raise a SyntaxError.

  • Dependencies:
    List of required dependencies (install with pip install -r requirements.txt)

Compatibility

This package has been tested against Django 5.2 (the latest version at the time of release) and is known to work with versions 3.2 and above.

โš ๏ธ Compatibility with Django 6.x and beyond is not yet guaranteed. If you're using a future version, proceed with caution and consider opening an issue if anything breaks.

๐Ÿ” Back to top


Logger and Database Integration

๐Ÿงฉ EmailSendervia EmailSenderLogger, supports optional integration with both logging and database persistence for email sending events.

Logging Integration

๐Ÿชต EmailSenderLogger does not provide a built-in logger. You must configure and pass your own logger (e.g., using Pythonโ€™s built-in logging module). If no logger is provided, EmailSenderLogger will still function as expected โ€” silently and without log output.

Key Points:

If you pass a logger, it will be used as-is.

EmailSenderLogger does not modify logger levels, handlers, or formatters.

If you do not set a logger level (e.g., INFO, DEBUG), the logger will be ignored.

If no logger is configured, email sending proceeds normally, without logging. If you do not configure your formatting then default spacing will be used which may or may not align depending if you are using different levels

config_logger

The config_logger method is entirely optional. You only need to call it if you want to enable logging for EmailSenderLogger. If not called, EmailSenderLogger will skip all logging operations and focus solely on sending the email โ€” no setup required, no extra overhead.

To enable logging, you must also call:

  • start_logging_session()
    This ensures that logging starts. Without calling this, the logger will not log anything.

This setup gives you full flexibility:

  • Enable logging when you need visibility into the email-sending process.
  • Skip logging for quicker or simpler email sends.
  • EmailSender adapts to your needs โ€” whether you prefer a fully monitored pipeline or a fast, lightweight send.

How to configure the Logger

๐Ÿ”— Once the user has configured a logger, it can be passed using the config_logger chain method.
Use LoggerType to minimise errors and ensure consistency across your implementation.
See Logger Configuration for a full list of supported types and usage examples.

The parameters available for config_logger are:

Parameter Type Description
logger Logger instance (required) The logger instance provided by the user.
log_level LoggerType str (required) The log level to listen to (e.g., "info", "error").

๐Ÿ“ Notes

  • If no logger is set, EmailSenderLogger will not log anything

    • Advanced users may customise logging heavily using the custom_formatter.
    • Casual users can simply provide a logger and rely on default behaviour.
    • Users not interested in logging can simply skip the set logger setup and proceed with sending emails.

Example Setting up a simple Logger with EmailSenderLogger

In this section, we'll configure a Python simple logger and demonstrate how to integrate it with EmailSenderLogger.

import logging
from django_email_sender.email_sender import EmailSender
from django_email_sender.email_logger import EmailSenderLogger
from django_email_sender.email_sender_constants import LoggerType

# Step 1: Set up a basic logger
logger = logging.getLogger("email_sender_logger")

# Optional: Add a console handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
logger.addHandler(console_handler)

# Step 2: (Optional) Define a custom formatter
def my_custom_formatter(exception: Exception, trace: str) -> str:
    return f"Error occurred: {str(exception)} | Traceback: {trace}"

# Step 3: Use the logger with EmailSender

  email_sender = (
      
      EmailSenderLogger.create().start_logging_session()
    
         .enable_verbose()
         .config_logger(logger, LoggerType.DEBUG) # logger added
         .add_email_sender_instance(EmailSender.create())
         .set_custom_formatter(my_custom_formatter) # only use this option when you are passing in an optional custom formatter
         .from_address("no-reply@example.com")
         .to("no-reply@example.com")
         .with_subject("test subject")
         .with_html_template("test_email.html")
         .with_text_template("test_email.txt")
         .send()
    
)

๐Ÿ“‹ Whats Exactly Is Happening Here?

Step What
1 We create a simple logger using Pythonโ€™s built-in logging module.
2 (Optional) We define a custom_formatter for formatting error messages. If defined set it using set_custom_formatter
3 We chain set_logger() with other EmailSender methods like to(...), with_subject(...), with_context(...), and finally send().

Enabling the Logger

๐Ÿ”’ By default, even if you've configured your logger using methods such as config_logger() or **logging will not begin until you explicitly call**start_logging_session()`. This ensures that there is no accidental logging unless you want to log.

If you forget to call start_logging_session(), the system will inform you through repeated debug messages like the following:

[2025-05-11 22:20:27,875] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-11 22:20:27,875] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-11 22:20:27,875] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-11 22:20:27,875] DEBUG    email_sender : [Logger Not Enabled: Logger is not enabled. Skipping logging. | category=LOGGER | status=NOT_ENABLED]
[2025-05-11 22:20:27,875] INFO     email_sender : [Setting language safely...]
[2025-05-11 22:20:27,875] INFO     email_sender : [Django is ready, activating language.]

To fix this, ensure you call the following after your logger setup:

.start_logging_session() 

โœ… Tip: You can stop the session anytime with stop_logging_session() or temporarily pause it with pause_logging() and resume_logging().


๐Ÿ› ๏ธ Troubleshooting Tip

If your logs are not being recorded or saved even after calling config_logger() and other setup methods, check that:

  • start_logging_session() has been called.
  • The logging level (e.g., .to_info(), .to_debug()) matches your applicationโ€™s verbosity.
  • You have not paused logging with pause_logging() without calling resume_logging() afterward.
  • You are using a valid log model (if required), and add_log_model() is set up properly if you want to log to the database .

if you are still not seeing any logs? Look for [Logger Not Enabled: Logger is not enabled. Skipping logging.] messages โ€” it means logging was configured but never started.

๐Ÿ” Back to top

Reuse the Logger and Formatter Across Multiple Emails

If you have set up a custom logger or formatter setup and plan to send multiple emails, you don't need to set them up every single time.

Instead, you can set them once when you create an instance of EmailSenderLogger, then reuse the same instance to send all your emails.

This way:

  • Your logger and formatter stay attached to the instance.
  • You avoid repetitive setup code.
  • Sending emails becomes much cleaner and faster.

Resetting or Reusing the Instance Cleanly

When reusing the same EmailSender or EmailSenderLogger instance to send multiple emails, certain fields (like recipients, subject, or context) may retain values from previous emails, especially if you're not overriding them. Since you're using a single instance of EmailSender or EmailSenderLogger if you are interested in logging or storing the data in a database then no errors will occur when calling the send method again, as these fields were set to required values earlier.

However, this can lead to issues where you might unintentionally send emails with the wrong template, subject, or even to the wrong recipient.

To avoid this, you have two options:

  1. Use the auto_reset flag: Set it to True to clear all fields before sending the next email.
  2. Manually clear specific fields: Call the appropriate clear_<field_name> method (e.g., clear_subject(), clear_context(), clear_all_fields(), etc.) to reset only the fields you need.

๐Ÿš€ Example Usage

The example focuses on using the auto_reset flag to clear the fields but this can be done using the corresponding clear_<field_name> method

# Step 1: Create an instance and set the logger/formatter once
email_sender = EmailSenderLogger.create().config_logger(
    logger=logger,
    log_level=LoggerType.ERROR,
)

# Step 2: Send multiple emails using the same instance

# Example 1 - Send email 1
(
    email_sender.from_address("no-reply@example.com")
        .to("test@example.com")
        .with_subject("Verify Your Email")
        .with_context({"username": "John", "verification_code": "123456"})
        .with_html_template("verification.html", folder_name="verification")
        .with_text_template("verification.txt", folder_name="verification")
        .send(auto_reset=True)  # ensure a clean state
)

# Example 2 - Send email 2
# auto_reset is not set to true, default of False is used
(
    email_sender.from_address("no-reply@example.com")
        .to("test@example.com")
        .with_subject("Welcome Email")
        .with_html_template("welcome.html", folder_name="welcome")
        .with_text_template("welcome.txt", folder_name="welcome")
        .send()
)

# Example 3 - Send email 3
# Since `auto_reset` wasn't set in Example 2, and the user is not overriding the fields, the `welcome.html` and `welcome.txt` templates 
# will be used for the third email, even though the subject ("Import Account Information") doesn't match the templates.
# This mismatch will result in the recipient receiving an email with irrelevant content.
# To avoid this issue, you should either manually clear the fields or use `auto_reset=True` to ensure a clean state.
(
    email_sender.from_address("no-reply@example.com")
        .to("test@example.com")
        .with_subject("Meetup Information time and location")
        .send(auto_reset=True)  # ensure a clean state
)

โœ… All emails are sent using the same instance, with the logger and formatter already set up.

Tip

If you are sending dozens of emails with the same logger and configuration, reusing .clear_all_fields() might save you tiny performance overhead. But if you're only sending 1โ€“2 emails each time, it's easier to just create a fresh instance!


โœจ Why This Matters

  • Performance: Avoids re-initialising the logger every time.
  • Cleaner Code: Reduces duplication and clutter.
  • Consistency: Ensures all emails follow the same logging/formatting rules.

โœจ Key Points:

  • By default : EmailSenderLogger doesn't log events to the console or file unless an error occurs
  • Custom logger: Use the config_logger method if you need more control over logging, like logging to a file or an external service, See the flow of the email process, sending, delivery, errors, etc
  • Logging configuration: You can configure your logger with handlers, formats, and levels as needed via the settings.py which can be overriden when using EmailSenderLogger

โœ… Easy for beginners
โœ… Powerful for advanced users


๐Ÿ” Back to top

Tracing Method Chains and logging errors with set_traceback

๐Ÿงญ If you ever wanted to know exactly which methods were called during your email building process, set_traceback() is your friend. It provides a step-by-step breakdown of the chained method calls that lead up to sending an email.

This is especially useful for debugging complex chains, understanding the flow, or simply verifying that everything is working in the expected order.

Why Use Method Tracing?

When working with complex chains of method calls or deeply nested logic, it can be difficult to understand the exact flow of execution. Method tracing provides a powerful way to gain visibility into what's happening under the hood.

  • Debug complex chains with greater ease
  • Understand execution flow step-by-step
  • Verify correct method ordering and dependencies

Enabling chain tracing

To enable method tracing, you must:

  • Set method_tracing parameter in the set_traceback method to True
  • Call enable_verbose()
  • set the level to Debug either by the config_logger or using the .to_debug() method

Chain tracing is only shown at the debug level and when enable_verbose mode is enabled to avoid overwhelming the user with too much information.

Here's how it works:

  1. Method Chain Tracking
    The method chain begins from the initial call (e.g., create()) and continues through all chained methods like to(), with_subject(), and so on. Each method gets logged in real-time.

โš™๏ธ Behind the Scenes (Optional Detail for Power Users)

Internally, show_traceback() appends each method name and its arguments to a trace log as the chain is built. This is particularly helpful in:

  • Debugging method order issues
  • Catching repeated or conflicting method calls
  • Understanding flow when extending or contributing to the library

๐Ÿงช Example Output from set_traceback(method_tracing=True)

When set_traceback(method_tracing=True) is enabled, youโ€™ll see something like this in your logs or console (depending on your logger configuration):


\[TRACE] Method Chain Started:
โ†’ create()
โ†’ start\_logging\_session()
โ†’ config\_logger()
โ†’ add\_email\_sender\_instance()
โ†’ from\_address()
โ†’ to()
โ†’ with\_subject()
โ†’ with\_text\_template()
โ†’ send()

Each arrow (โ†’) represents a method call in your builder chain. This gives you full visibility into whatโ€™s been run before .send() is triggered.


  1. Verbose Debugging
    If show_traceback=True is enabled, stack trace information is included in the logs when an error happens. This includes:

    • The sequence of method calls leading to the error.
    • Detailed information about the error (e.g., the specific line number, method name, and error message).

    This allows you to track back to the root cause of an issue quickly and effectively.

Example of an error traceback:

Consider an email sending process like this:

# Assume the neccessary modules have been imported

email = EmailSenderLogger.create() \
       .start_logging_session()\
        .enable_verbose()\
        .add_email_sender_instance(EmailSender())\
        .config_logger(logger, LoggerType.DEBUG)\
        .set_traceback(method_tracing=True, show_traceback=True)\
        .to("recipient@example.com") \
        .with_subject("Test Email") \
        .with_html_template("invalid_test_template.html") \

    .... other methods here
    .send()

Now, letโ€™s assume that thereโ€™s an error while sending due to an invalid template, and you've set show_traceback=True. The logged traceback might look like this:

[DEBUG][METHOD_TRACE] Method Chain Started: EmailSenderLogger._get_environment() โ†’ EmailSenderLogger._create_meta_data() โ†’ EmailSenderLogger._validate_template() โ†’ EmailSenderLogger._send()
--- Error Traceback ---
File "email_sender.py", line 145, in _send
    raise TemplateNotFoundError("HTML template not found")
TemplateNotFoundError: HTML template not found

Breakdown of what happens:

  • The log shows the exact method calls that were made in the process.
  • The error is flagged, and the traceback reveals where the failure occurredโ€” in the _send() method, specifically due to a TemplateNotFoundError.
  • You can now see exactly which method caused the problem and what the error was, making it easier to debug.

Why is this useful?

  • Debugging Complex Chains: If you're chaining many methods together, itโ€™s often hard to track down where an error happens. show_traceback=True ensures that you know exactly where to look.

  • Detailed Logging: You get more than just a "method failed" message; you get a full traceback that includes method names, file locations, and specific error messages, which can greatly speed up debugging.

You can also do something like this to ensure that you always have a traceback

# Assume the necessary modules have been imported
import os

email_sender = (
    EmailSenderLogger.create()
        .start_logging_session()
        .config_logger(logger)
        .add_email_sender_instance(EmailSender)
        .from_address("dev@example.com")
        .to("debug@example.com")
        .with_subject("Dev Mode Email")
        .with_text_template("dev_template.txt", "emails")
)

# Automatically enable tracing in dev
if os.getenv("ENV") == "development":
    email_sender.set_traceback(True, True)
    email_sender.enable_verbose()

email_sender.send()

๐Ÿ” Back to top

Turning on Verbose Mode

When using EmailSenderLogger, depending on the log level youโ€™ve chosen (info, warning, error, or debug), you'll see information corresponding to those levels. By default, the logger doesn't show detailed step-by-step information to avoid overwhelming you. Instead, it focuses on providing the most relevant details for debugging.

However, if you want to view more detailed information, you can enable verbose logging with the enable_verbose() method. When enabled, youโ€™ll see extra details that would normally be hidden. Note that enable_verbose will show the additional information based on the log level youโ€™ve selected. For example:

  • In debug mode, you'll see logs from all levels: debug, info, warning, and error.
  • In info mode, you'll see info, warning, and error logs.
  • In warning mode, you'll see warning and error logs.
  • In error mode, you'll only see error logs.

To use verbose logging, simply chain the enable_verbose() method. To disable verbose mode, use the disable_verbose() method.

Example usage:

# assume that neccessary modules have been imported

email_sender = (
    EmailSenderLogger.create()
    .start_logging_session()
    .config_logger(logger, LoggerType.INFO)
    .enable_verbose()  # Enable verbose mode
    .add_email_sender_instance(EmailSender)
    .from_address("test@example.com")
    .to("recipient@example.com")
    .send()
)

๐Ÿ” Back to top

Setting up an advanced logger

In the above example we set up a very limited logger that doesn't involve the user configure the settings.py file, however that logger has several limited.

โš ๏ธ Limitations:

  • Logger might duplicate messages:

    • Django automatically configures logging early during startup, which may cause the logger to propagate messages up to Djangoโ€™s root logger.
    • This could result in messages being printed twice: once by your console_handler and once by Djangoโ€™s default logger.
  • No control through Django settings:

    • You can't easily change logging behaviour (e.g., send email errors, write to file, silence logs in production) through Djangoโ€™s LOGGING configuration if you manually build loggers everywhere.
  • Handler added multiple times:

    • If the code runs multiple times (e.g., Django imports modules multiple times), you could end up attaching multiple handlers, which causes duplicate log messages.

To configure your email_sender_logger properly through Django's LOGGING settings

EmailSender via EmailSenderLogger supports flexible logging integration. To enable logging, configure a logger in your Django settings.py (or your project settings) like this:

# -------------------------------------------------------------------
# settings.py
#--------------------------------------------------------------------

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,  # required so Django's default logging still works
    "formatters": {
        'right_indented': {
            '()': 'your-app-path-to-this-file.RightIndentedFormatter',  # Update with the correct path to your custom formatter
            'format': '[%(asctime)s] %(levelname)-8s %(name)-13s: [%(message)s]',
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "right_indented",  # Use 'right_indented' formatter here
        },
        "file": {
            "class": "logging.FileHandler",
            "filename": "emails.log",  # Specify your log file name here
            "formatter": "right_indented",  # Use 'right_indented' formatter here
        },
    },
    "loggers": {
        "email_sender": {  # Logger name used by EmailSender or whatever name you chose
            "handlers": ["console", "file"],  # Both handlers for console and file
            "level": "DEBUG",  # Set log level to DEBUG or as needed
            "propagate": False,  # This prevents propagation to parent loggers
        },
    },
}

Now create a utils.py file (or choose a name that suits your project) inside your app folder, and define your custom formatter there. This ensures proper indentation and structure for logging output, as shown below.

[2025-05-08 06:49:56,087] INFO     email_sender : Some information
[2025-05-08 06:49:56,087] DEBUG    email_sender : Some Debug message
[2025-05-08 06:49:56,195] WARNING  email_sender : Some Waringin
[2025-05-08 06:49:56,195] Error    email_sender : Some Waringin
# -------------------------------------------------------------------
# utils.py  
#--------------------------------------------------------------------
import logging

class RightIndentedFormatter(logging.Formatter):
    def __init__(self, fmt=None, datefmt=None, style='%'):
        super().__init__(fmt, datefmt, style)

    def format(self, record):
        
        # Add a custom indent to the beginning of the log message
        original_message = super().format(record)
        return f"    {original_message}"  # 4 spaces or any amout of space you want

Note: Make sure to link formatter file in the LOGGER with the settings.py and now whenever you send an email it will nicely be displayed in a formatted setting.

Advanced Tip: Rotate Log Files Automatically

For larger applications, it's recommended to rotate your log files to avoid growing indefinitely. You can modify the file handler to automatically create a new log file each day:

from logging.handlers import TimedRotatingFileHandler

"handlers": {
    "file": {
        "class": "logging.handlers.TimedRotatingFileHandler",
        "filename": "django.log",
        "when": "midnight",  # Create a new file every midnight
        "backupCount": 7,    # Keep last 7 days of logs
        "formatter": "standard",
    },
},

This will keep your logs clean without manually deleting old files.

๐Ÿ” Back to top

Advanced Logger Usage

EmailSenderLogger allows you to monitor and capture detailed information while using the EmailSender. You can configure it in various ways depending on how much control or verbosity you need.

The most straightforward usage is to configure the logger with a specific log level โ€” such as ERROR, WARNING, INFO, or DEBUG โ€” and let it automatically log events to the console or a file.


๐Ÿ”น First Approach

Set up the Logger with a Log Level

You can initialise a logger and attach it to EmailSenderLogger to record activity at a chosen level. This is useful for capturing general usage, errors, or debugging info.

```python

EmailSenderLogger.create().config_logger(
    logger=logger, 
    log_level=LoggerType.ERROR
)

Second Approach

.log_only_fields(*fields)

Use the .log_only_fields() method to log only specific fields you're interested in.

This gives you fine-grained control and avoids cluttering your logs with unnecessary data. To use this method, import EmailSenderConstants and pass in the fields you'd like to track. See the full list of fields under EmailSenderConstants.

from django_email_sender.constants import EmailSenderConstants

EmailSenderLogger.create()
    .start_logging_session()
    .config_logger(logger, LoggerType.DEBUG) # start of debug
    .log_only_fields(
        EmailSenderConstants.Fields.SUBJECT.value,
        EmailSenderConstants.Fields.TO_EMAIL.value
    )

๐Ÿ”น Third Approach

.exclude_fields_from_logging(*fields)

Sometimes, you might want to log everything except certain fields.
The .exclude_fields_from_logging() method allows you to omit specific fields from being recorded in your logs.

This is especially useful when logging sensitive data such as emails or templates, or just to reduce noise.

from django_email_sender.constants import EmailSenderConstants

EmailSenderLogger.create()
    .start_logging_session()
    .config_logger(logger, LoggerType.DEBUG) # start of debug
    .exclude_fields_from_logging(
        EmailSenderConstants.Fields.HTML_TEMPLATE.value,
        EmailSenderConstants.Fields.CONTEXT.value
    )

โš ๏ธ Note: Do not use .log_only_fields() and .exclude_fields_from_logging() together. These methods are mutually exclusiveโ€”use one or the other depending on your logging preference.

๐Ÿ”„ Change Levels Dynamically During Logging

During the logging process, you may wish to change levels to capture different aspects of whatโ€™s happening. For instance, you might start in DEBUG mode and then switch to INFO, WARNING, or ERROR depending on what data you want to capture.

You can do this using .to_debug(), .to_info(), .to_warning(), and .to_error() โ€” all without restarting or reconfiguring the logger.

#### ๐Ÿงช Example 1 โ€“ Switching Levels While Chaining
```python

# Assume Logger and EmailSender have been imported and linked

email_sender = (
    EmailSenderLogger.create().start_logging_session()
        .config_logger(logger, LoggerType.DEBUG)       # Start at DEBUG
        .add_email_sender_instance(EmailSender)
        .from_address("test@example.com")
        .to_info()                                     # Switch to INFO
        .to("jackie@example.com")
        .to_warning()                                  # Switch to WARNING
        .with_subject("test subject")
        .with_html_template("test_email.html", "sender")
        .with_text_template("test_email.txt", "sender")
        .to_error()                                    # Final switch to ERROR
        .send()
)

๐Ÿงช Example 2 โ€“ Changing Levels with config_logger

You can also switch levels inline by re-calling .config_logger().

from django_email_sender.email_sender import EmailSender, LoggerType
import logging

logger = logging.getLogger("email_sender")

(
    EmailSenderLogger.create()
        .config_logger(logger, LoggerType.DEBUG)
        .from_address("no-reply@example.com")
        .to("jtest@example.com")
        .config_logger(logger, LoggerType.INFO)
        .with_subject("test")
        .config_logger(logger, LoggerType.WARNING)
        .with_context({"username": "John", "code": "123456"})
        .with_headers({"X_HEADER": "some header"})
        .config_logger(logger, LoggerType.ERROR)
        .with_html_template("test_email.html", folder_name="sender")
        .with_text_template("test_email.txt", folder_name="sender")
        .send()
)

Cancel or Pause Logging Mid-Flow

๐Ÿ›‘ If you only want to log certain parts of your flow, you can stop or pause the logger at any point.

Use the following methods:

start_logging_session() โ€” starts the logging session.

Example: Stopping the Logging Session

# Assume Logger and EmailSender have been imported and linked

email_sender = (
    EmailSenderLogger.create().start_logging_session()
        .config_logger(logger, LoggerType.DEBUG)
        .add_email_sender_instance(EmailSender)
        .from_address("test@example.com")
        .to("jackie@example.com")
        .stop_logging_session()  # Logging ends here
        .with_subject("test subject")
        .with_html_template("test_email.html", "sender")
        .with_text_template("test_email.txt", "sender")
        .send()
)

Anything after the .to() field will not be captured in the logs.

Example: Pausing and Resuming Logging

email_sender = (
    EmailSenderLogger.create().start_logging_session()
        .config_logger(logger, LoggerType.DEBUG)
        .add_email_sender_instance(EmailSender)
        .from_address("test@example.com")
        .to("jackie@example.com")
        .pause_logging()                     # Temporarily pause
        .with_subject("test subject")        # Not logged
        .resume_logging()                    # Logging resumes
        .with_html_template("test_email.html", "sender")
        .with_text_template("test_email.txt", "sender")
        .send()
)

Key points

- Provides advanced way to log or monitor or your data

- Allows you to log or exclude fields 

- Allows you to an `end point` where it can then run up to

- Does not force you to use these advanced featues, you can just as easily set up with `config_logger` and let run or use no logger at all

- It is easier for beginners to use and allows `advanced` users more control over their logging

๐Ÿ” Back to top

Sample Logging Report

๐Ÿ“„ Below is a sample snippet from the logging output generated when an email is processed using EmailSenderLogger with logging enabled.

This report captures a comprehensive summary of the email sending processโ€”including details like recipients, templates used, status, time taken, and previews of the message content. It's particularly useful for debugging, auditing, or tracking email delivery during development or production.

[2025-05-08 06:49:56,758] INFO     email_sender : [________________________________________________________________________
[2025-05-08 06:49:56,758] INFO     email_sender : [                                                                        
[2025-05-08 06:49:56,758] INFO     email_sender : [      '**Email Sent Process Summary Logs**']
[2025-05-08 06:49:56,758] INFO     email_sender : [________________________________________________________________________
[2025-05-08 06:49:56,758] INFO     email_sender : [Email ID                          : '8679bf20ada7055179feefc730305e28d8070b2844e74b6152b83a6aa3f3e205'
[2025-05-08 06:49:56,759] INFO     email_sender : [Timestamp                         : 2025-05-08 05:49:56.757213+00:00
[2025-05-08 06:49:56,759] INFO     email_sender : [Language code sent in             : en-us
[2025-05-08 06:49:56,760] INFO     email_sender : [Subject                           : apple
[2025-05-08 06:49:56,760] INFO     email_sender : [From                              : en@gmail.com
[2025-05-08 06:49:56,760] INFO     email_sender : [To                                : peter@gmail.com
[2025-05-08 06:49:56,760] INFO     email_sender : [Additional Recipients             : ['bin@example.com', 's@exmaple.com', 'jake@gmailcom', 'peter@gmail.com']
[2025-05-08 06:49:56,761] INFO     email_sender : [Total Recipients                  : 5
[2025-05-08 06:49:56,761] INFO     email_sender : [Template Used (HTML) full path    : C:\full\path\to\template\email_templates\emails_templates\sender\test_email.html
[2025-05-08 06:49:56,762] INFO     email_sender : [Template Used (Text) full path    : C:\full\path\to\template\email_templates\test_email.txt
[2025-05-08 06:49:56,763] INFO     email_sender : [HTML short file name              : test_email.html
[2025-05-08 06:49:56,763] INFO     email_sender : [Text short file name              : test_email.txt
[2025-05-08 06:49:56,763] INFO     email_sender : [Attachments Added                 : 'None'
[2025-05-08 06:49:56,763] INFO     email_sender : [Environment                       : 'Development'
[2025-05-08 06:49:56,765] INFO     email_sender : [Time Taken                        : 0.00 seconds
[2025-05-08 06:49:56,766] INFO     email_sender : [Status                            : Failed to send email
[2025-05-08 06:49:56,766] INFO     email_sender : [Emails delivered successfully     : 0
[2025-05-08 06:49:56,766] INFO     email_sender : [Text Preview                      : hi, { username}  please verify your email by clicking this link { code }..
[2025-05-08 06:49:56,767] INFO     email_sender : [HTML Preview                      : Verify Your Email Verify Your Email Address Hi { username }, Pleas...
[2025-05-08 06:49:56,768] INFO     email_sender : [Email format                      : multipart/alternative (HTML + plain text)
[2025-05-08 06:49:56,768] INFO     email_sender : [________________________________________________________________________

๐Ÿ” Back to top

Database Integration

EmailSenderLogger allows users to optionally persist email metadata to a database. This is useful for audit logs, diagnostics, and history tracking.

๐Ÿ”” Note: EmailSenderLogger does not create or manage any database tables. You must define your own log model, and explicitly opt in to database logging.

Requirements:

You must create your own model that inherits from EmailBaseLog.

The model must be passed as a class, not an instance, using .add_log_model().

You must explicitly enable database logging using .enable_email_meta_data_save().

If no valid model is added, no data will be saved

๐Ÿ“„ Example: Custom Email Log Model

# models.py

from django_email_sender.models import EmailBaseLog 

class CustomEmailLog(EmailBaseLog):
    # Optional: Add custom fields
    request_id = models.CharField(max_length=100, null=True, blank=True)
    environment = models.CharField(max_length=20, default='production')

    def __str__(self):
        return f"{self.recipient} | {self.subject} | {self.status}"

Run migrations

python manage.py makemigrations
python manage.py migrate

๐Ÿ› ๏ธ Usage in Code

from django_email_sender.email_sender import EmailSender
from django_email_sender.email_logger import EmailSenderLogger
from "your-app".models import CustomEmailLog


email_sender = ( EmailSenderLogger.create().start_logging_session()
                .add_log_model(CustomEmailLog)    # customEmailLog
                .enable_email_meta_data_save()    # enable saving to the 
                
                # Rest of the code here .
                # .....
                .send()
)

๐Ÿ” Back to top


๐Ÿ” Payload & Metadata Access

Payload Inspection

EmailSenderLogger provides structured access to the full email payload in json via the .payload property.

This enables developers to inspect, log, or persist detailed data related to any email being sent. The payload is dynamically constructed, allowing you to access and inspect it either after sending the email or as you build each field during the setup process.

โœ… What the Payload Includes:

  • from_email โ†’ Email address of the sender
  • to_email โ†’ List or string of recipient addresses
  • subject โ†’ Subject line of the email
  • body_html โ†’ HTML version of the message
  • body_text โ†’ Plain-text version of the message
  • context โ†’ Context dictionary used to render the template
  • headers โ†’ Any custom headers attached to the message

โš ๏ธ Why This Method Is Not Chainable

Unlike other methods in EmailSender, this one is not chainable. This is intentional.

The purpose of the method is to return a structured payload containing dynamic email data such as from_email, to_email, subject, body_html, etc. Since EmailSenderLogger assumes you may need to inspect, log, or persist this data, the method returns the payload directly instead of returning the instance (self).

This design ensures developers have immediate access to the email data when they need it โ€” whether during construction or after sending to either inspect or use as part of the application. Making it chainable would require additional steps to extract the data, which would reduce clarity and usability.

๐Ÿ“ฆ Example usage :

payload = email_sender.payload
print(payload)
# {
#   "from_email": "admin@example.com",
#   "to_email": ["user@example.com"],
#   "subject": "Welcome to Our Platform",
#   "body_html": "full/path/to/html/template/",
#   "body_text": "full/path/to/text/template/",
#   "context": {"username": "john_doe"},
#   "headers": {"X-Custom-Header": "value"}


or 

# use the data somewhere in your application

๐Ÿ” Back to top

HTML Email Template Example

django-email-sender supports sending beautiful HTML emails using Django templates.

This example shows a verification email template that you can use out of the box or modify to suit your needs.

๐Ÿ—‚๏ธ Save this as: templates/emails_templates/emails/verify_email.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Verify Your Email</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
        }
        .container {
            max-width: 600px;
            margin: 30px auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
        }
        .code {
            font-size: 32px;
            font-weight: bold;
            color: #333;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Verify Your Email Address</h1>
        <p>Hi {{ username }},</p>
        <p>Please verify your email address by entering the following code:</p>
        <div class="code">{{ verification_code }}</div>
        <p>If you didn't request this, you can safely ignore this email.</p>
    </div>
</body>
</html>

Plain Text & Multi-part Email Support

django-email-sender supports both plain text and multi-part (HTML + text) emails. This ensures emails are readable in all clients, including those that don't support HTML.


๐Ÿ“„ Plain Text Email Example

๐Ÿ—‚๏ธ Save this as: templates/emails_templates/emails/verify_email.txt

Hi {{ username }},

Please verify your email address by entering the following code:

{{ verification_code }}

If you didn't request this, you can safely ignore this email.

## Usage Example

๐Ÿ“จ Multi-part Email (HTML + Plain Text) usage

Use both .with_text_template() and .with_html_template() together to send a multi-part email:

from django_email_sender.email_sender import EmailSender

EmailSender.create()
    .from_address("noreply@example.com")
    .to(["user@example.com"])
    .with_subject("Please verify your email")
    .with_context({
        "username": user.username,
        "verification_code": "123456"
    })
    .with_html_template("verify_email.html", folder_name="emails")
    .with_text_template("verify_email.txt", folder_name="emails")
    .send()

โœจ This approach helps you keep your email logic clean and makes templates easy to design or preview.

Explanation:

  • .from_address("no-reply@example.com"): Specifies the sender's email address.
  • .to("recipient@example.com") : Specifies the recipient's email address.
  • .with_subject("Welcome!") : The subject of the email.
  • .with_context({"username": "John"}) : Context for the email templates, allowing dynamic insertion of values (e.g., the recipient's name).
  • .with_text_template("welcome.txt", folder_name="emails"): The path to the text-based email template. Here, we specify the folder name (emails) where the template is stored. If no folder name is provided, it defaults to email_templates/.
  • .with_html_template("welcome.html", folder_name="emails"): The path to the HTML-based email template. Similarly, you can specify the folder name (emails) for this template.
  • .send(): Sends the email.

๐Ÿ” Back to top

Subclassing

You can also subclass the EmailSender class to create more specific types of emails.

Example: Password Reset Email

class PasswordResetEmail(EmailSender):
    def __init__(self, user):
        super().__init__()
        self.user = user

    def build(self):
        return self\
            .from_address("no-reply@example.com")\
            .to([self.user.email])\
            .with_subject("Reset Your Password")\
            .with_context({"username": self.user.username, "reset_link": generate_reset_link(self.user)})\
            .with_text_template("reset_password.txt", folder_name="emails")\
            .with_html_template("reset_password.html", folder_name="emails")

Usage:

PasswordResetEmail(user).build().send()

Here, the PasswordResetEmail class uses reset_password.txt and reset_password.html templates from the emails folder.

๐Ÿ” Back to top


Function-Based Abstractions

๐Ÿ› ๏ธ For a functional approach, you can also wrap EmailSender in specific functions to handle common email use cases.

Example: Sending a Verification Email

def send_verification_email(user):
    html_verification_path = "verification/verification.html"
    text_verification_path = "verification/verification.txt"
    subject = "Verify Your Email"
    from_email = "no-reply@example.com"

    return EmailSender.create()\
        .from_address(from_email)\
        .to([user.email])\
        .with_subject(subject)\
        .with_context({
            "username": user.username,
            "verification_link": generate_verification_link(user)
        })\
        .with_text_template(text_verification_path, folder_name="emails")\
        .with_html_template(html_verification_path, folder_name="emails")\
        .send()

Example: Sending a Registration Email

def send_registration_email(user):
    html_registration_path = "registration/registration.html"
    text_registration_path = "registration/registration.txt"
    
    subject = "Welcome to the Platform!"
    from_email = "no-reply@example.com"

    return EmailSender.create()\
        .from_address(from_email)\
        .to([user.email])\
        .with_subject(subject)\
        .with_context({"username": user.username})\
        .with_text_template(text_registration_path, folder_name="emails")\
        .with_html_template(html_registration_path, folder_name="emails")\
        .send()

Advantages of this Approach:

  • Keeps your logic functional and simple: It's straightforward to use and easy to test.
  • Keeps your email templates modular and easy to override: Templates are organized in subfolders (e.g., registration, verification), making them easier to manage.
  • Clean and maintainable codebase: You donโ€™t have to subclass EmailSender each time, reducing complexity.

๐Ÿ” Back to top


Templates

๐Ÿ“ Templates must reside inside a dedicated email_templates/ directory, which should exist inside your Django template directory.

This folder can contain your own structure to help organise different types of emails. For example:

Example

project/
โ”œโ”€โ”€ templates/
โ”‚   โ””โ”€โ”€ email_templates/
โ”‚       โ””โ”€โ”€ registration/
โ”‚           โ”œโ”€โ”€ registration.html
โ”‚           โ””โ”€โ”€ registration.txt

When calling with_html_template() or with_text_template(), you can provide the subfolder and filename like so:

EmailSender.create()
    .with_html_template("registration.html", folder_name="registration")
    .with_text_template("registration.txt", folder_name="registration")

You must have both an .html and .txt version of the email template. These are required for rich content and email client compatibility.

๐Ÿ” Back to top


Configuring the Template Directory**

๐Ÿ“ EmailSender allows you to easily configure the location of template directories used by the app, including email templates. By default, EmailSender will look for templates in a templates folder inside the base directory of your project. However, if you'd like to customize the location, you can do so using the MYAPP_TEMPLATES_DIR setting in your Django project's settings.py.

๐Ÿ” Back to top


Default Behaviour

By default, EmailSender will look for templates in the following directory:

{BASE_DIR}/templates/emails_templates/

Where:

  • BASE_DIR is the root directory of your Django project (where manage.py is located).
  • templates is the default directory where EmailSender expects to find your templates.
  • emails_templates is the subdirectory where email-related templates should be stored.

Customizing the Template Directory Path

If you'd like to customize the template directory location, you can define the MYAPP_TEMPLATES_DIR setting in your settings.py file.

Steps to Override:

  1. Open your settings.py file.
  2. Define the MYAPP_TEMPLATES_DIR setting to point to your custom template folder.

Example:

# settings.py

BASE_DIR = Path(__file__).resolve().parent.parent

# Custom template directory location
MYAPP_TEMPLATES_DIR = BASE_DIR / "custom_templates"

In this example:

  • EmailSender will look for templates in {BASE_DIR}/custom_templates/emails_templates/.
  • If you do not define MYAPP_TEMPLATES_DIR, EmailSender will use the default location: {BASE_DIR}/templates/emails_templates/.

๐Ÿ” Back to top


How It Works

  • MYAPP_TEMPLATES_DIR: If defined, EmailSender uses this setting to locate the main template folder.
  • Fallback: If MYAPP_TEMPLATES_DIR is not defined, EmailSender falls back to the default location: {BASE_DIR}/templates.
  • Email Templates: EmailSender looks specifically in the emails_templates/ subdirectory for email-related templates.

Example File Structure:

Default Setup:

my_project/
โ”‚
โ”œโ”€โ”€ templates/
โ”‚   โ””โ”€โ”€ emails_templates/
โ”‚       โ”œโ”€โ”€ welcome_email.html
โ”‚       โ””โ”€โ”€ welcome_email.txt


Custom Setup (with MYAPP_TEMPLATES_DIR defined):

my_project/
โ”‚
โ”œโ”€โ”€ custom_templates/
โ”‚   โ””โ”€โ”€ emails_templates/
โ”‚       โ”œโ”€โ”€ welcome_email.html
โ”‚       โ””โ”€โ”€ welcome_email.txt

๐Ÿ” Back to top


Error Handling

If EmailSender cannot find the templates in the expected location, it will raise a error to let you know where the missing templates are expected.

If BASE_DIR is not defined in settings.py, an ImproperlyConfigured error will be raised to prompt you to define it.

๐Ÿ” Back to top


Fallback Logic

In case the MYAPP_TEMPLATES_DIR is not defined in settings.py, EmailSender will automatically fallback to the default template directory (templates) without requiring any extra configuration.

Conclusion

The MYAPP_TEMPLATES_DIR setting provides flexibility for users who prefer to store their templates in a custom location. By defining this setting in settings.py, users can control where the templates for EmailSender (including email templates) are stored, ensuring a smooth and configurable integration.

๐Ÿ” Back to top


Putting It All Together

This guide shows how to use django-email-sender in a Django project to send a verification email. This will not be using a logger. See the logger section See logger section on how to use EmailSender with a logger.


๐Ÿ›  Step 1: Virtual Environment

python -m venv venv
source venv/bin/activate.ps1
source venv/bin/activate      # On Mac or linux use: venv\Scripts\activate

๐Ÿ“ฆ Step 2: Install Dependencies

pip install django django-email-sender

โš™๏ธ Step 3: Create a Django Project

django-admin startproject config .
python manage.py startapp core

In config/settings.py, add 'core' to INSTALLED_APPS.


๐Ÿงฑ Step 4: Update Django Settings

Add the following settings to your settings.py file to configure the email backend and other email-related settings.

Email settings

EMAIL_BACKEND        = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST           = 'smtp.example.com'  # Replace with your email provider's SMTP server
EMAIL_PORT           = 587  # Typically 587 for TLS
EMAIL_USE_TLS        = True  # Enable TLS encryption
EMAIL_HOST_USER      = 'your-email@example.com'  # Your email address
EMAIL_HOST_PASSWORD  = 'your-email-password'  # Your email password (or app password if using 2FA)
DEFAULT_FROM_EMAIL   = EMAIL_HOST_USER  # Default email to send from

Note replace

  - smtp.example.com with your-email@example.com
  - your-email-password with your actual email service provider's SMTP details

If you are using gmail to send emails then the setup would look like

    # Email Backend
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

    # Email Settings for Gmail

    EMAIL_USE_TLS        = True  
    EMAIL_HOST           = 'smtp.gmail.com'  
    EMAIL_PORT           = 587  
    EMAIL_HOST_USER      = 'your-email@gmail.com'  # Your Gmail address
    EMAIL_HOST_PASSWORD  = 'your-app-password'     # Use the generated app password (if 2FA is enabled)
    DEFAULT_FROM_EMAIL   = EMAIL_HOST_USER         # Optional: Set default sender email (same as the one above)


Important Notes:

  • App Password: If you have two-factor authentication (2FA) enabled for your Gmail account, you'll need to create an App Password instead of using your regular Gmail password. You can generate it in your Google account settings.

  • TLS: Setting EMAIL_USE_TLS = True ensures that emails are sent securely over TLS encryption.

This configuration should allow you to send emails via Gmail's SMTP server.

๐Ÿงฑ Step 4: Create Email Templates

Create the folder structure :

Then add the templates path in config/settings.py:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # This is where you add the line
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

๐Ÿงช Step 5: Add a Test View

In core/views.py:

from django.http import HttpResponse
from django_email_sender.email_sender import EmailSender

def test_email_view(request):
    (
    EmailSender.create()
        .from_address("no-reply@example.com")
        .to(["test@example.com"])
        .with_subject("Verify Your Email")
        .with_context({ "username": "John", "verification_code": "123456"})
        .with_html_template("verification.html", folder_name="verification")
        .with_text_template("verification.txt", folder_name="verification")
        .send()
    )
    return HttpResponse("Verification email sent!")

๐Ÿ”— Step 6: Wire Up URLs

Create core/urls.py:

from django.urls import path
from .views import test_email_view

urlpatterns = [
    path("send-verification-email/", test_email_view),
]

Then include it in config/urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("core.urls")),
]

๐Ÿš€ Step 7: Run and Test

python manage.py runserver

Open http://localhost:8000/send-verification-email/ in your browser and check your inbox!


๐Ÿ’ก Tips

  • You can subclass EmailSender for different email types or simply wrap it in functions.
  • Organise your templates by email type (registration/, verification/, etc.)
  • Subject and context are fully customisable.

Playing Around with Features Without Sending Emails

๐Ÿงช You can quickly test various features of EmailSenderLogger by setting up a simple view. Here's how:

  1. Set up a sample view in your Django app.

  2. Add this to your settings.py to log emails to the console:

    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
    
  3. Add <a-html-template>.html and <a-test-template>.txt templates to your templates directory to avoid template errors.

    • html template: Your basic HTML structure.
    • text template: Your basic text structure.
  4. Now, all emails sent will be logged in the console only, without actually sending to real email addresses.

  5. You can safely test features like:

    • Logging: Track the email sending process and potential errors.
    • Field Filtering: Choose which fields to log or save, such as subject, from email, or the context.
    • Metadata Storage: Save email metadata (like recipients, subject, timestamp) to a database for auditing purposes.
  6. View the logging output in your console or test out these features.

What is django.core.mail.backends.console.EmailBackend?

It replicates the entire process of sending an email but logs it to the console instead of actually sending it to an address. This allows you to test the email-related features of EmailSenderLogger safely without sending real emails.

Example View

# views.py

import logging
from django.http import HttpResponse
from your_app.models import CustomEmailLog
from django_email_sender.email_sender import EmailSender
from django_email_sender.email_logger import EmailSenderLogger
from django_email_sender.email_sender_constants import LoggerType, EmailSenderConstants

# Assume logger is configured in settings.py
logger = logging.getLogger("email_sender")

def test_email(request):
    email_sender_logger = EmailSenderLogger.create()
    (
        email_sender_logger
        .start_logging_session()
        .enable_verbose()
        .add_email_sender_instance(EmailSender())
        .add_log_model(CustomEmailLog)
        .enable_email_meta_data_save()
        .config_logger(logger, LoggerType.DEBUG)
        .log_only_fields(
            EmailSenderConstants.Fields.CONTEXT.value,
            EmailSenderConstants.Fields.SUBJECT.value,
            EmailSenderConstants.Fields.FROM_EMAIL.value,
            EmailSenderConstants.Fields.TEXT_TEMPLATE.value,
        )
        .reset_field_logging_filters()
        .from_address("no-reply@gmail.com")
        .to("to-reply@gmail.com")
        .with_subject("Subject Line")
        .with_html_template("test_email.html", "sender")
        .with_text_template("test_email.txt", "sender")
        .send()
    )

    print(email_sender_logger.payload)
    print(email_sender_logger.email_meta_data)
    print(email_sender_logger.return_successful_payload())

    return HttpResponse("Email sent successfully!")

Key Points:

  • Test out features by logging email data to the console instead of sending actual emails.
  • This process allows you to safely check the logging, field filtering, metadata, payload, emails database storage features, etc without worrying about sending real emails to recipients.

๐Ÿ” Back to top

Best Practices

1. Configure a Logger in Production

  • Always configure a logger for production environments to track errors and activities.
  • Ensure that your logger is properly set up before calling .send(). This helps with debugging and provides valuable insights into any email issues.

2. Use .send() as the Final Call

  • The .send() method should be the last call in your chain. Ensure all other configurations (like to(), from_address(), etc.) are set before calling .send().

3. Logging Customization

  • For more advanced logging, customize the format by providing a custom_formatter function. This can help format error messages or traceback details according to your needs.
  • The custom_formatter function must accept two arguments: an Exception and a traceback string. This ensures security and prevents injection vulnerabilities.

4. Set Traceback Visibility During Development

  • Set the show_traceback=True flag during development to view detailed error traces. This is helpful for debugging, but should be turned off in production to avoid exposing sensitive information.

5. Related folder names

  • Use the same folder_name for related templates (text and HTML):
    This keeps your project organised, consistent, and easy to maintain.

    project/
    โ”œโ”€โ”€ templates/
    โ”‚   โ””โ”€โ”€ email_templates/
    โ”‚       โ””โ”€โ”€ authentication/
    โ”‚           โ”œโ”€โ”€ login/
    โ”‚           โ”‚   โ”œโ”€โ”€ login.html
    โ”‚           โ”‚   โ””โ”€โ”€ login.txt
    โ”‚           โ”œโ”€โ”€ passwords/
    โ”‚           โ”‚   โ”œโ”€โ”€ password_reset.html
    โ”‚           โ”‚   โ””โ”€โ”€ password_reset.txt
    โ”‚           โ””โ”€โ”€ register/
    โ”‚               โ”œโ”€โ”€ register.html
    โ”‚               โ””โ”€โ”€ register.txt
    

๐Ÿ” Back to top

Worst Practices

1. Skipping Logger Configuration

  • Avoid skipping logger configuration, especially in production. Not configuring a logger will result in missed error details, making it much harder to debug issues. Always pass a logger if you want insights into what's happening during the email sending process.

2. Calling .send() Before Finalizing Email Details

  • Donโ€™t call .send() before setting all required email details like to(...), from_address(...), with_subject(...), etc. Calling .send() will result in an error raised.

3. Using a Custom Formatter with Incorrect Parameters

  • Donโ€™t pass an invalid custom formatter. The custom_formatter function must accept exactly two arguments: an Exception and a traceback string. Passing in incorrect arguments will result in an error being raised.

4. Leaving Tracebacks Visible in Production

  • Never leave detailed tracebacks visible in production. Tracebacks should only be shown in a development environment. Exposing them in production can reveal sensitive information to users or attackers, potentially causing security issues.

5. Ignoring Error Handling for Email Failures

  • Donโ€™t ignore errors when email sending fails. Always ensure you have error handling in place, whether itโ€™s logging or retries, so that youโ€™re aware of any issues that occur during the email process.

6. Mixing Different Log Levels without Thought

  • Donโ€™t randomly mix log levels without a clear purpose. Choose a log level for each message carefully (INFO, WARNING, ERROR, etc.) to make your logs structured and easy to interpret.

7. Skipping the logger

  • Skipping the logger and then struggling to debug when an email fails.

5. Mixing unrelated templates together randomly:

  • Keeping related files in a different folder_name For example :
    This makes your project messy, error-prone, and hard to maintain.

    project/
    โ”œโ”€โ”€ templates/
    โ”‚   โ””โ”€โ”€ email_templates/
    โ”‚       โ”œโ”€โ”€ login.html
    โ”‚       โ”œโ”€โ”€ password_reset.txt
    โ”‚       โ”œโ”€โ”€ newsletter.html
    โ”‚       โ”œโ”€โ”€ random_folder/
    โ”‚           โ”œโ”€โ”€ register.txt
    โ”‚       โ”œโ”€โ”€ backup_templates/
    โ”‚           โ”œโ”€โ”€ login_backup.txt
    โ”‚           โ””โ”€โ”€ reset_password_backup.html
    โ”‚       โ””โ”€โ”€ marketing/
    โ”‚           โ”œโ”€โ”€ new_product.html
    โ”‚           โ””โ”€โ”€ welcome.txt
    
  • Key issues:

    • Templates from different features (login, passwords, marketing) are thrown together with no clear separation.
    • Some templates are floating randomly without folders.
    • Backup files are mixed with production files.
    • Inconsistent folder naming (random_folder, backup_templates, etc).
    • Future developers (even you!) will struggle to find the correct template.
    • Higher chance of using the wrong template by mistake.

๐Ÿ” Back to top

License

  • This package is licensed under the MIT License. See the LICENSE file for details.

Credits

-This library was created and maintained by Egbie Uku a.k.a EgbieAndersonUku1.

๐Ÿ” Back to top

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_email_sender-2.0.5.tar.gz (145.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_email_sender-2.0.5-py3-none-any.whl (73.7 kB view details)

Uploaded Python 3

File details

Details for the file django_email_sender-2.0.5.tar.gz.

File metadata

  • Download URL: django_email_sender-2.0.5.tar.gz
  • Upload date:
  • Size: 145.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.6

File hashes

Hashes for django_email_sender-2.0.5.tar.gz
Algorithm Hash digest
SHA256 fc05c2c1c995c798963b5a484e37bd3f4f7f4e3e63f6ed7d99d7c3c8a96c42bd
MD5 2e2c4fbd09d9a23f25a7c8dba44dc55c
BLAKE2b-256 34b80b8c4a06dd1d68204cca548cd998060835a86c6f16be60ed8598789e7ffc

See more details on using hashes here.

File details

Details for the file django_email_sender-2.0.5-py3-none-any.whl.

File metadata

File hashes

Hashes for django_email_sender-2.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 16a85da0e3a9670351cc06db5a19283d747175d1666453769744c237c79d58fe
MD5 089d2a427312c7ad393dc9c70d806414
BLAKE2b-256 6b582225a62fe9b019b79a6915d572769794872b142ed5a93df4e86868225784

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page