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?
- ๐ Upgrading from Version 1
- ๐ What's New in Version 2?
- โจ Features
- ๐ What is
EmailSenderand what isEmailSenderLogger, and why are they needed? - ๐ค What if I am not interested in using the advanced features provided by
EmailSenderLogger? - ๐ง
EmailSenderClass API Reference - ๐
EmailSenderLoggerClass API Reference - ๐งผ Available Methods
- ๐ Additional API Notes
- ๐งฉ
EmailSenderandEmailSenderLoggerMethods - ๐งผ Code Style Tips
- ๐ Installation via PyPI
- ๐งฉ Requirements
- ๐ Logger and Database Integration
- ๐ How to configure the Logger
- ๐ Enabling the Logger
- ๐ ๏ธ Example Setting up a simple Logger with EmailSenderLogger
- ๐ Reuse the Logger and Formatter Across Multiple Emails
- ๐ฃ๏ธ Turning on Verbose Mode
- ๐งญ Tracing Method Chains and logging errors with
set_traceback - โ๏ธ Setting up an advanced logger
- ๐ Advanced Tip: Rotate Log Files Automatically
- ๐ Advanced Logger Usage
- ๐ Sample Logging Report
- ๐๏ธ Database Integration
- ๐ ๏ธ Setting up an advanced logger
- ๐ Reuse the Logger and Formatter Across Multiple Emails
- ๐ Resetting or Reusing the Instance Cleanly
- ๐ง HTML Email Template Example
- ๐ Plain Text & Multi-part Email Support
- ๐งฑ Subclassing
- ๐ ๏ธ Function-Based Abstractions
- ๐ Templates
- ๐ Configuring the Template Directory
- ๐ Multilingual Error Messages
- ๐งฉ Putting it all together Example
- ๐ฎ Playing Around with Features Without Sending Emails
- ๐ Best Practices
- โ Worst Practices
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.
๐ 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
EmailLoggerintegration.
-
๐ Auto-Reset After Sending
Automatically reset all email fields after sending withauto_reset=Trueto prevent accidental resends. -
๐ Preserve Specific Fields
Usepreserve_fieldsto retain selected fields even after an auto-reset. -
๐งผ Individual Field Clearing
Clear only what you need with new methods likeclear_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 theEmailLogger, 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.
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
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.
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()
EmailSender Class API Reference
๐จ create()
Factory method โ Instantiates and returns an
EmailSenderobject.
๐ค from_address(email)
Sets the sender's email address.
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 usingEmailSenderLogger, 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 theto_emailfield 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.
Iffolder_nameis omitted, defaults toemails_templates/.
๐ with_html_template(folder_name="folder-name-here", template_name="template-name-here.html")
Specifies the HTML version of the email template.
Iffolder_nameis omitted, defaults toemails_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.`
EmailSenderLogger Class API Reference
๐จ create()
Factory method โ Instantiates and returns an
EmailSenderLoggerobject.
๐ค add_email_sender_instance(email_sender_instance)
Sets the core
EmailSenderinstance 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 useadd_new_recipientmethod
๐ฅ 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.
Iffolder_nameis omitted, defaults toemails_templates/.
๐ with_html_template(folder_name, template_name)
Specifies the HTML version of the email template.
Iffolder_nameis omitted, defaults toemails_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 theabstractbase modelEmailBaseLogThe module path :from django_email_sender.models import EmailBaseLog
๐งช to_debug(message)
Logs a message at the
DEBUGlevel.
โน๏ธ to_info(message)
Logs a message at the
INFOlevel.
โ ๏ธ to_warning(message)
Logs a message at the
WARNINGlevel.
โ to_error(message)
Logs a message at the
ERRORlevel.
๐ 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
selffor 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
Trueif the last email was successfully sent, otherwiseFalse.
๐ข 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_changesdictionary, 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 toTrue, default isFalseshow_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
subjectorto).
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_fieldsandexclude_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.
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"
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.
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()
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.
Requirements
- Python 3.10+
This library uses Structural Pattern Matching, introduced in Python 3.10, via thematch-casesyntax.
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 withpip 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.
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.
EmailSenderadapts 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,
EmailSenderLoggerwill 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.
- Advanced users may customise logging heavily using the
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 callingresume_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 thedatabase.
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.
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:
- Use the
auto_resetflag: Set it toTrueto clear all fields before sending the next email. - 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_loggermethod 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.pywhich can be overriden when usingEmailSenderLogger
โ
Easy for beginners
โ
Powerful for advanced users
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_tracingparameter in theset_tracebackmethod toTrue- Call
enable_verbose()- set the level to
Debugeither by theconfig_loggeror 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:
- Method Chain Tracking
The method chain begins from the initial call (e.g.,create()) and continues through all chained methods liketo(),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.
-
Verbose Debugging
Ifshow_traceback=Trueis 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 aTemplateNotFoundError. - 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=Trueensures 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()
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
debugmode, you'll see logs from all levels:debug,info,warning, anderror. - In
infomode, you'll seeinfo,warning, anderrorlogs. - In
warningmode, you'll seewarninganderrorlogs. - In
errormode, you'll only seeerrorlogs.
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()
)
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_handlerand 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
LOGGINGconfiguration if you manually build loggers everywhere.
- You can't easily change logging behaviour (e.g., send email errors, write to file, silence logs in production) through Djangoโs
-
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.
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.
stop_logging_session()โ permanently stops logging for the current flow.pause_logging()โ temporarily halts logging.resume_logging()โ resumes logging after a pause.
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
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 : [________________________________________________________________________
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()
)
๐ 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 senderto_emailโ List or string of recipient addressessubjectโ Subject line of the emailbody_htmlโ HTML version of the messagebody_textโ Plain-text version of the messagecontextโ Context dictionary used to render the templateheadersโ 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
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 toemail_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.
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.
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
EmailSendereach time, reducing complexity.
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.
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.
Default Behaviour
By default, EmailSender will look for templates in the following directory:
{BASE_DIR}/templates/emails_templates/
Where:
BASE_DIRis the root directory of your Django project (wheremanage.pyis located).templatesis the default directory where EmailSender expects to find your templates.emails_templatesis 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:
- Open your
settings.pyfile. - Define the
MYAPP_TEMPLATES_DIRsetting 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/.
How It Works
MYAPP_TEMPLATES_DIR: If defined, EmailSender uses this setting to locate the main template folder.- Fallback: If
MYAPP_TEMPLATES_DIRis 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
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.
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.
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 :
- See HTML Email Template Example and Plain Text & Multi-part Email Support for how to create the files
- Replace the folder
emailswithverification - Do the same with the file names
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
EmailSenderfor 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:
-
Set up a sample view in your Django app.
-
Add this to your
settings.pyto log emails to the console:EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-
Add
<a-html-template>.htmland<a-test-template>.txttemplates to yourtemplatesdirectory to avoid template errors.html template: Your basic HTML structure.text template: Your basic text structure.
-
Now, all emails sent will be logged in the console only, without actually sending to real email addresses.
-
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.
-
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.
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 (liketo(),from_address(), etc.) are set before calling.send().
3. Logging Customization
- For more advanced logging, customize the format by providing a
custom_formatterfunction. This can help format error messages or traceback details according to your needs. - The
custom_formatterfunction must accept two arguments: anExceptionand atracebackstring. This ensures security and prevents injection vulnerabilities.
4. Set Traceback Visibility During Development
- Set the
show_traceback=Trueflag 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_namefor 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
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 liketo(...),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_formatterfunction must accept exactly two arguments: anExceptionand atracebackstring. 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_nameFor 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.
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc05c2c1c995c798963b5a484e37bd3f4f7f4e3e63f6ed7d99d7c3c8a96c42bd
|
|
| MD5 |
2e2c4fbd09d9a23f25a7c8dba44dc55c
|
|
| BLAKE2b-256 |
34b80b8c4a06dd1d68204cca548cd998060835a86c6f16be60ed8598789e7ffc
|
File details
Details for the file django_email_sender-2.0.5-py3-none-any.whl.
File metadata
- Download URL: django_email_sender-2.0.5-py3-none-any.whl
- Upload date:
- Size: 73.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16a85da0e3a9670351cc06db5a19283d747175d1666453769744c237c79d58fe
|
|
| MD5 |
089d2a427312c7ad393dc9c70d806414
|
|
| BLAKE2b-256 |
6b582225a62fe9b019b79a6915d572769794872b142ed5a93df4e86868225784
|