django-email-sender 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_email_sender-1.0.0/LICENCE +21 -0
- django_email_sender-1.0.0/MANIFEST.in +12 -0
- django_email_sender-1.0.0/PKG-INFO +248 -0
- django_email_sender-1.0.0/README.md +219 -0
- django_email_sender-1.0.0/django_email_sender/__init__.py +0 -0
- django_email_sender-1.0.0/django_email_sender/email_sender.py +301 -0
- django_email_sender-1.0.0/django_email_sender/exceptions.py +18 -0
- django_email_sender-1.0.0/django_email_sender.egg-info/PKG-INFO +248 -0
- django_email_sender-1.0.0/django_email_sender.egg-info/SOURCES.txt +13 -0
- django_email_sender-1.0.0/django_email_sender.egg-info/dependency_links.txt +1 -0
- django_email_sender-1.0.0/django_email_sender.egg-info/requires.txt +1 -0
- django_email_sender-1.0.0/django_email_sender.egg-info/top_level.txt +1 -0
- django_email_sender-1.0.0/setup.cfg +4 -0
- django_email_sender-1.0.0/setup.py +26 -0
- django_email_sender-1.0.0/tests/test_email_sender.py +23 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Egbie Uku
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Include the README files
|
|
2
|
+
include README.md
|
|
3
|
+
include usage.md
|
|
4
|
+
|
|
5
|
+
# Include the email templates folder
|
|
6
|
+
recursive-include email_templates *
|
|
7
|
+
|
|
8
|
+
# Include all Python files
|
|
9
|
+
recursive-include emailsender *.py
|
|
10
|
+
|
|
11
|
+
# Include any other necessary files (e.g., LICENSE, etc.)
|
|
12
|
+
include LICENSE
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-email-sender
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A chainable Django email sender utility.
|
|
5
|
+
Home-page: https://github.com/EgbieAndersonUku1/django-email-sender
|
|
6
|
+
Author: Egbie Uku
|
|
7
|
+
Author-email: egbieuku@hotmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 3.2
|
|
11
|
+
Classifier: Framework :: Django :: 4.0
|
|
12
|
+
Classifier: Framework :: Django :: 5.0
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENCE
|
|
18
|
+
Requires-Dist: Django<6.0,>=3.2
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
# π§ Django Email Sender Utility
|
|
31
|
+
|
|
32
|
+
A clean, reusable, lightweight and chainable utility class for sending emails in Django using templates. It supports both HTML and plain text templates, context injection, and flexible usage β either directly, via subclassing, or abstracted into functions.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
π§ Why Use This?
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## β¨ Features
|
|
41
|
+
|
|
42
|
+
- Chainable API (`.to()`, `.from_address()`, etc.)
|
|
43
|
+
- Supports HTML and plain text templates
|
|
44
|
+
- Uses Django's template system for dynamic content
|
|
45
|
+
- Easy to integrate and override
|
|
46
|
+
- Encourages clean code and reusability
|
|
47
|
+
- Supports subclassing or functional abstractions
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
π Available Methods
|
|
55
|
+
|
|
56
|
+
Method Description
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- create() # Class factory method to instantiate the EmailSender.
|
|
60
|
+
- from_address(email) # Sets the senderβs email address.
|
|
61
|
+
- to(recipients) # Accepts a string or list of recipient email addresses.
|
|
62
|
+
- with_subject(subject) # Sets the email subject.
|
|
63
|
+
- with_context(context) # Context dictionary used in the templates.
|
|
64
|
+
- with_text_template(path) # Path to the plain text email template.
|
|
65
|
+
- with_html_template(path) # Path to the HTML email template.
|
|
66
|
+
- with_headers(headers) # Optional custom headers as a dictionary.
|
|
67
|
+
- send() # Sends the email. Returns the number of successfully delivered messages.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
π¨ Error Handling
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
- Raises ValueError if required fields are missing.
|
|
75
|
+
- Raises TypeError if headers are not provided as a dictionary.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## π Installation
|
|
80
|
+
|
|
81
|
+
This is a standalone utility. Copy the file into your Django project, or install it as a package using PyPI.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## π§© Requirements
|
|
86
|
+
|
|
87
|
+
- Python 3.8+
|
|
88
|
+
- Django >= 3.2
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## π§ͺ Basic Usage
|
|
93
|
+
|
|
94
|
+
Here's a simple example of how to send an email using `EmailSender`:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from email_sender import EmailSender
|
|
98
|
+
|
|
99
|
+
EmailSender.create()\
|
|
100
|
+
.from_address("no-reply@example.com")\
|
|
101
|
+
.to(["recipient@example.com"])\
|
|
102
|
+
.with_subject("Welcome!")\
|
|
103
|
+
.with_context({"username": "John"})\
|
|
104
|
+
.with_text_template("welcome.txt", folder_name="emails")\
|
|
105
|
+
.with_html_template("welcome.html", folder_name="emails")\
|
|
106
|
+
.send()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Explanation:
|
|
110
|
+
|
|
111
|
+
- `.from_address("no-reply@example.com")`: Specifies the sender's email address.
|
|
112
|
+
- `.to(["recipient@example.com"])`: Specifies the recipient's email address.
|
|
113
|
+
- `.with_subject("Welcome!")`: The subject of the email.
|
|
114
|
+
- `.with_context({"username": "John"})`: Context for the email templates, allowing dynamic insertion of values (e.g., the recipient's name).
|
|
115
|
+
- `.with_text_template("welcome.txt", folder_name="emails")`: The path to the text-based email template. Here, we specify the folder name (`emails`) where the template is stored. If no folder name is provided, it defaults to `email_templates/`.
|
|
116
|
+
- `.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.
|
|
117
|
+
- `.send()`: Sends the email.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## π§± Subclassing
|
|
122
|
+
|
|
123
|
+
You can also subclass the `EmailSender` class to create more specific types of emails.
|
|
124
|
+
|
|
125
|
+
### Example: Password Reset Email
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
class PasswordResetEmail(EmailSender):
|
|
129
|
+
def __init__(self, user):
|
|
130
|
+
super().__init__()
|
|
131
|
+
self.user = user
|
|
132
|
+
|
|
133
|
+
def build(self):
|
|
134
|
+
return self\
|
|
135
|
+
.from_address("no-reply@example.com")\
|
|
136
|
+
.to([self.user.email])\
|
|
137
|
+
.with_subject("Reset Your Password")\
|
|
138
|
+
.with_context({"username": self.user.username, "reset_link": generate_reset_link(self.user)})\
|
|
139
|
+
.with_text_template("reset_password.txt", folder_name="emails")\
|
|
140
|
+
.with_html_template("reset_password.html", folder_name="emails")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Usage:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
PasswordResetEmail(user).build().send()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Here, the `PasswordResetEmail` class uses `reset_password.txt` and `reset_password.html` templates from the `emails` folder.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## π οΈ Function-Based Abstractions
|
|
154
|
+
|
|
155
|
+
For a functional approach, you can also wrap `EmailSender` in specific functions to handle common email use cases.
|
|
156
|
+
|
|
157
|
+
### Example: Sending a Verification Email
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
|
|
161
|
+
def send_verification_email(user):
|
|
162
|
+
html_verification_path = "verification/verification.html"
|
|
163
|
+
text_verification_path = "verification/verification.txt"
|
|
164
|
+
subject = "Verify Your Email"
|
|
165
|
+
from_email = "no-reply@example.com"
|
|
166
|
+
|
|
167
|
+
return EmailSender.create()\
|
|
168
|
+
.from_address(from_email)\
|
|
169
|
+
.to([user.email])\
|
|
170
|
+
.with_subject(subject)\
|
|
171
|
+
.with_context({
|
|
172
|
+
"username": user.username,
|
|
173
|
+
"verification_link": generate_verification_link(user)
|
|
174
|
+
})\
|
|
175
|
+
.with_text_template(text_verification_path, folder_name="emails")\
|
|
176
|
+
.with_html_template(html_verification_path, folder_name="emails")\
|
|
177
|
+
.send()
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Example: Sending a Registration Email
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
def send_registration_email(user):
|
|
184
|
+
html_registration_path = "registration/registration.html"
|
|
185
|
+
text_registration_path = "registration/registration.txt"
|
|
186
|
+
|
|
187
|
+
subject = "Welcome to the Platform!"
|
|
188
|
+
from_email = "no-reply@example.com"
|
|
189
|
+
|
|
190
|
+
return EmailSender.create()\
|
|
191
|
+
.from_address(from_email)\
|
|
192
|
+
.to([user.email])\
|
|
193
|
+
.with_subject(subject)\
|
|
194
|
+
.with_context({"username": user.username})\
|
|
195
|
+
.with_text_template(text_registration_path, folder_name="emails")\
|
|
196
|
+
.with_html_template(html_registration_path, folder_name="emails")\
|
|
197
|
+
.send()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Advantages of this Approach:
|
|
201
|
+
|
|
202
|
+
- **Keeps your logic functional and simple**: It's straightforward to use and easy to test.
|
|
203
|
+
- **Keeps your email templates modular and easy to override**: Templates are organized in subfolders (e.g., `registration`, `verification`), making them easier to manage.
|
|
204
|
+
- **Clean and maintainable codebase**: You donβt have to subclass `EmailSender` each time, reducing complexity.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## π Templates
|
|
212
|
+
|
|
213
|
+
Templates must reside inside a dedicated `email_templates/` directory, which should exist inside your Django template directory.
|
|
214
|
+
|
|
215
|
+
This folder can contain your own structure to help organise different types of emails. For example:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Example
|
|
219
|
+
|
|
220
|
+
project/
|
|
221
|
+
βββ templates/
|
|
222
|
+
β βββ email_templates/
|
|
223
|
+
β βββ registration/
|
|
224
|
+
β βββ registration.html
|
|
225
|
+
β βββ registration.txt
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
When calling `with_html_template()` or `with_text_template()`, you can provide the subfolder and filename like so:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
EmailSender.create()
|
|
232
|
+
.with_html_template("registration.html", folder_name="registration")
|
|
233
|
+
.with_text_template("registration.txt", folder_name="registration")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
You **must** have both an `.html` and `.txt` version of the email template. These are required for rich content and email client compatibility.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## π‘ Tips
|
|
242
|
+
|
|
243
|
+
- You can subclass `EmailSender` for different email types or simply wrap it in functions.
|
|
244
|
+
- Organise your templates by email type (`registration/`, `verification/`, etc.)
|
|
245
|
+
- Subject and context are fully customisable.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# π§ Django Email Sender Utility
|
|
2
|
+
|
|
3
|
+
A clean, reusable, lightweight and chainable utility class for sending emails in Django using templates. It supports both HTML and plain text templates, context injection, and flexible usage β either directly, via subclassing, or abstracted into functions.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
π§ Why Use This?
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## β¨ Features
|
|
12
|
+
|
|
13
|
+
- Chainable API (`.to()`, `.from_address()`, etc.)
|
|
14
|
+
- Supports HTML and plain text templates
|
|
15
|
+
- Uses Django's template system for dynamic content
|
|
16
|
+
- Easy to integrate and override
|
|
17
|
+
- Encourages clean code and reusability
|
|
18
|
+
- Supports subclassing or functional abstractions
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
π Available Methods
|
|
26
|
+
|
|
27
|
+
Method Description
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- create() # Class factory method to instantiate the EmailSender.
|
|
31
|
+
- from_address(email) # Sets the senderβs email address.
|
|
32
|
+
- to(recipients) # Accepts a string or list of recipient email addresses.
|
|
33
|
+
- with_subject(subject) # Sets the email subject.
|
|
34
|
+
- with_context(context) # Context dictionary used in the templates.
|
|
35
|
+
- with_text_template(path) # Path to the plain text email template.
|
|
36
|
+
- with_html_template(path) # Path to the HTML email template.
|
|
37
|
+
- with_headers(headers) # Optional custom headers as a dictionary.
|
|
38
|
+
- send() # Sends the email. Returns the number of successfully delivered messages.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
π¨ Error Handling
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
- Raises ValueError if required fields are missing.
|
|
46
|
+
- Raises TypeError if headers are not provided as a dictionary.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## π Installation
|
|
51
|
+
|
|
52
|
+
This is a standalone utility. Copy the file into your Django project, or install it as a package using PyPI.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## π§© Requirements
|
|
57
|
+
|
|
58
|
+
- Python 3.8+
|
|
59
|
+
- Django >= 3.2
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## π§ͺ Basic Usage
|
|
64
|
+
|
|
65
|
+
Here's a simple example of how to send an email using `EmailSender`:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from email_sender import EmailSender
|
|
69
|
+
|
|
70
|
+
EmailSender.create()\
|
|
71
|
+
.from_address("no-reply@example.com")\
|
|
72
|
+
.to(["recipient@example.com"])\
|
|
73
|
+
.with_subject("Welcome!")\
|
|
74
|
+
.with_context({"username": "John"})\
|
|
75
|
+
.with_text_template("welcome.txt", folder_name="emails")\
|
|
76
|
+
.with_html_template("welcome.html", folder_name="emails")\
|
|
77
|
+
.send()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Explanation:
|
|
81
|
+
|
|
82
|
+
- `.from_address("no-reply@example.com")`: Specifies the sender's email address.
|
|
83
|
+
- `.to(["recipient@example.com"])`: Specifies the recipient's email address.
|
|
84
|
+
- `.with_subject("Welcome!")`: The subject of the email.
|
|
85
|
+
- `.with_context({"username": "John"})`: Context for the email templates, allowing dynamic insertion of values (e.g., the recipient's name).
|
|
86
|
+
- `.with_text_template("welcome.txt", folder_name="emails")`: The path to the text-based email template. Here, we specify the folder name (`emails`) where the template is stored. If no folder name is provided, it defaults to `email_templates/`.
|
|
87
|
+
- `.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.
|
|
88
|
+
- `.send()`: Sends the email.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## π§± Subclassing
|
|
93
|
+
|
|
94
|
+
You can also subclass the `EmailSender` class to create more specific types of emails.
|
|
95
|
+
|
|
96
|
+
### Example: Password Reset Email
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
class PasswordResetEmail(EmailSender):
|
|
100
|
+
def __init__(self, user):
|
|
101
|
+
super().__init__()
|
|
102
|
+
self.user = user
|
|
103
|
+
|
|
104
|
+
def build(self):
|
|
105
|
+
return self\
|
|
106
|
+
.from_address("no-reply@example.com")\
|
|
107
|
+
.to([self.user.email])\
|
|
108
|
+
.with_subject("Reset Your Password")\
|
|
109
|
+
.with_context({"username": self.user.username, "reset_link": generate_reset_link(self.user)})\
|
|
110
|
+
.with_text_template("reset_password.txt", folder_name="emails")\
|
|
111
|
+
.with_html_template("reset_password.html", folder_name="emails")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Usage:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
PasswordResetEmail(user).build().send()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Here, the `PasswordResetEmail` class uses `reset_password.txt` and `reset_password.html` templates from the `emails` folder.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## π οΈ Function-Based Abstractions
|
|
125
|
+
|
|
126
|
+
For a functional approach, you can also wrap `EmailSender` in specific functions to handle common email use cases.
|
|
127
|
+
|
|
128
|
+
### Example: Sending a Verification Email
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
|
|
132
|
+
def send_verification_email(user):
|
|
133
|
+
html_verification_path = "verification/verification.html"
|
|
134
|
+
text_verification_path = "verification/verification.txt"
|
|
135
|
+
subject = "Verify Your Email"
|
|
136
|
+
from_email = "no-reply@example.com"
|
|
137
|
+
|
|
138
|
+
return EmailSender.create()\
|
|
139
|
+
.from_address(from_email)\
|
|
140
|
+
.to([user.email])\
|
|
141
|
+
.with_subject(subject)\
|
|
142
|
+
.with_context({
|
|
143
|
+
"username": user.username,
|
|
144
|
+
"verification_link": generate_verification_link(user)
|
|
145
|
+
})\
|
|
146
|
+
.with_text_template(text_verification_path, folder_name="emails")\
|
|
147
|
+
.with_html_template(html_verification_path, folder_name="emails")\
|
|
148
|
+
.send()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Example: Sending a Registration Email
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
def send_registration_email(user):
|
|
155
|
+
html_registration_path = "registration/registration.html"
|
|
156
|
+
text_registration_path = "registration/registration.txt"
|
|
157
|
+
|
|
158
|
+
subject = "Welcome to the Platform!"
|
|
159
|
+
from_email = "no-reply@example.com"
|
|
160
|
+
|
|
161
|
+
return EmailSender.create()\
|
|
162
|
+
.from_address(from_email)\
|
|
163
|
+
.to([user.email])\
|
|
164
|
+
.with_subject(subject)\
|
|
165
|
+
.with_context({"username": user.username})\
|
|
166
|
+
.with_text_template(text_registration_path, folder_name="emails")\
|
|
167
|
+
.with_html_template(html_registration_path, folder_name="emails")\
|
|
168
|
+
.send()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Advantages of this Approach:
|
|
172
|
+
|
|
173
|
+
- **Keeps your logic functional and simple**: It's straightforward to use and easy to test.
|
|
174
|
+
- **Keeps your email templates modular and easy to override**: Templates are organized in subfolders (e.g., `registration`, `verification`), making them easier to manage.
|
|
175
|
+
- **Clean and maintainable codebase**: You donβt have to subclass `EmailSender` each time, reducing complexity.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## π Templates
|
|
183
|
+
|
|
184
|
+
Templates must reside inside a dedicated `email_templates/` directory, which should exist inside your Django template directory.
|
|
185
|
+
|
|
186
|
+
This folder can contain your own structure to help organise different types of emails. For example:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Example
|
|
190
|
+
|
|
191
|
+
project/
|
|
192
|
+
βββ templates/
|
|
193
|
+
β βββ email_templates/
|
|
194
|
+
β βββ registration/
|
|
195
|
+
β βββ registration.html
|
|
196
|
+
β βββ registration.txt
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
When calling `with_html_template()` or `with_text_template()`, you can provide the subfolder and filename like so:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
EmailSender.create()
|
|
203
|
+
.with_html_template("registration.html", folder_name="registration")
|
|
204
|
+
.with_text_template("registration.txt", folder_name="registration")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
You **must** have both an `.html` and `.txt` version of the email template. These are required for rich content and email client compatibility.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
## π‘ Tips
|
|
213
|
+
|
|
214
|
+
- You can subclass `EmailSender` for different email types or simply wrap it in functions.
|
|
215
|
+
- Organise your templates by email type (`registration/`, `verification/`, etc.)
|
|
216
|
+
- Subject and context are fully customisable.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
File without changes
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from typing import Optional, List, Dict, Union
|
|
2
|
+
from django.core.mail import EmailMultiAlternatives
|
|
3
|
+
from django.template.loader import render_to_string
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from os.path import join
|
|
6
|
+
|
|
7
|
+
from os import getenv
|
|
8
|
+
|
|
9
|
+
from django_email_sender.exceptions import EmailTemplateDirNotFound, EmailTemplateNotFound, TemplateDirNotFound, EmailSenderError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
13
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
TEMPLATES_DIR = join(BASE_DIR, "templates")
|
|
17
|
+
EMAIL_TEMPLATES_DIR = join(TEMPLATES_DIR, "emails_templates")
|
|
18
|
+
|
|
19
|
+
from os.path import exists
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EmailSender:
|
|
23
|
+
"""
|
|
24
|
+
An email sender that allows you to send emails by
|
|
25
|
+
using a chaining method. This is mostly to be used in
|
|
26
|
+
the Django eco-system.
|
|
27
|
+
|
|
28
|
+
To use it in a Django eco-system, the following settings must be configured:
|
|
29
|
+
|
|
30
|
+
settings.py:
|
|
31
|
+
EMAIL_USE_TLS = True
|
|
32
|
+
EMAIL_HOST = 'smtp.gmail.com'
|
|
33
|
+
EMAIL_PORT = 587
|
|
34
|
+
EMAIL_HOST_USER = 'some@emailhere.com'
|
|
35
|
+
EMAIL_HOST_PASSWORD = 'password'
|
|
36
|
+
|
|
37
|
+
templates:
|
|
38
|
+
- Specify paths to your email templates.
|
|
39
|
+
|
|
40
|
+
Example usages:
|
|
41
|
+
You can easily abstract the `EmailSender` class into specific methods for sending different types of emails.
|
|
42
|
+
|
|
43
|
+
For example, to send a verification email:
|
|
44
|
+
|
|
45
|
+
def send_verification_email(user):
|
|
46
|
+
|
|
47
|
+
subject = "Verify Your Email"
|
|
48
|
+
from_email = "no-reply@example.com"
|
|
49
|
+
|
|
50
|
+
return EmailSender.create()\
|
|
51
|
+
.from_address(from_email)\
|
|
52
|
+
.to([user.email])\
|
|
53
|
+
.with_subject(subject)\
|
|
54
|
+
.with_context({
|
|
55
|
+
"username": user.username,
|
|
56
|
+
"verification_link": generate_verification_link(user)
|
|
57
|
+
})\
|
|
58
|
+
.with_text_template(folder_name="email", template_name="verification.txt")\
|
|
59
|
+
.with_html_template(folder_name="email", template_name="verification.html")\
|
|
60
|
+
.send()
|
|
61
|
+
|
|
62
|
+
Similarly, you can create other email methods such as:
|
|
63
|
+
|
|
64
|
+
def send_registration_email(user):
|
|
65
|
+
|
|
66
|
+
subject = "Welcome to the Platform!"
|
|
67
|
+
from_email = "no-reply@example.com"
|
|
68
|
+
|
|
69
|
+
return EmailSender.create()\
|
|
70
|
+
.from_address(from_email)\
|
|
71
|
+
.to([user.email])\
|
|
72
|
+
.with_subject(subject)\
|
|
73
|
+
.with_context({"username": user.username})\
|
|
74
|
+
.with_text_template(folder_name='emails', template_name='registration.html')\
|
|
75
|
+
.with_html_template(folder_name='emails', template_name='registeration.txt')\
|
|
76
|
+
.send()
|
|
77
|
+
|
|
78
|
+
This approach allows you to abstract email sending into different functions for
|
|
79
|
+
various use cases (e.g., registration, verification, password reset),
|
|
80
|
+
but at the same time allows the email sender class to be a single source of truth while allowing the
|
|
81
|
+
sending logic clean, consistent, and reusable.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self):
|
|
85
|
+
"""
|
|
86
|
+
Initialize an empty email configuration.
|
|
87
|
+
"""
|
|
88
|
+
self.from_email: Optional[str] = None
|
|
89
|
+
self.to_email: List[str] = []
|
|
90
|
+
self.subject: Optional[str] = None
|
|
91
|
+
self.html_template: Optional[str] = None
|
|
92
|
+
self.text_template: Optional[str] = None
|
|
93
|
+
self.context: Dict = {}
|
|
94
|
+
self.headers: Dict = {}
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def create(cls) -> "EmailSender":
|
|
98
|
+
"""
|
|
99
|
+
Class factory method to initialize the EmailSender for method chaining.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
EmailSender: A new instance of EmailSender.
|
|
103
|
+
"""
|
|
104
|
+
return cls()
|
|
105
|
+
|
|
106
|
+
def from_address(self, email: str) -> "EmailSender":
|
|
107
|
+
"""
|
|
108
|
+
Set the sender's email address.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
email (str): The sender's email address.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
EmailSender: The current instance for chaining.
|
|
115
|
+
"""
|
|
116
|
+
self.from_email = email
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def to(self, recipients: Union[str, List[str]]) -> "EmailSender":
|
|
120
|
+
"""
|
|
121
|
+
Set the recipient(s) of the email.
|
|
122
|
+
|
|
123
|
+
If a single email is provided as a string, it converts it into a list.
|
|
124
|
+
Otherwise, it assumes a list of email addresses.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
recipients (Union[str, List[str]]): A single email address or a list of email addresses.
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
EmailSender: The current instance for chaining.
|
|
132
|
+
"""
|
|
133
|
+
self.to_email = [recipients] if isinstance(recipients, str) else recipients
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
def with_subject(self, subject: str) -> "EmailSender":
|
|
137
|
+
"""
|
|
138
|
+
Set the subject of the email.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
subject (str): The subject line.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
EmailSender: The current instance for chaining.
|
|
145
|
+
"""
|
|
146
|
+
self.subject = subject
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def with_context(self, context: Dict) -> "EmailSender":
|
|
150
|
+
"""
|
|
151
|
+
Set the context dictionary for rendering templates.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
context (Dict): Context variables for use in templates.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
EmailSender: The current instance for chaining.
|
|
158
|
+
"""
|
|
159
|
+
self.context = context
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def with_html_template(self, template_name: str, folder_name: str = None) -> "EmailSender":
|
|
163
|
+
"""
|
|
164
|
+
Set the HTML template path.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
template_name (str): The name of the plain text template file (e.g. 'welcome.html').
|
|
168
|
+
folder_name (str, optional): The name of the subfolder inside the 'email_templates' directory
|
|
169
|
+
where this template is located. If None, template is assumed to
|
|
170
|
+
reside directly in the 'email_templates' folder.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
EmailSender: The current instance for chaining.
|
|
174
|
+
"""
|
|
175
|
+
self.html_template = self._create_path(folder_name, template_name)
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def with_text_template(self, template_name: str, folder_name: str = None) -> "EmailSender":
|
|
179
|
+
"""
|
|
180
|
+
Set the plain text template path for the email.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
template_name (str): The name of the plain text template file (e.g. 'welcome.txt').
|
|
184
|
+
folder_name (str, optional): The name of the subfolder inside the 'email_templates' directory
|
|
185
|
+
where this template is located. If None, template is assumed to
|
|
186
|
+
reside directly in the 'email_templates' folder.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
EmailSender: The current instance for method chaining.
|
|
190
|
+
"""
|
|
191
|
+
self.text_template = self._create_path(template_name, folder_name)
|
|
192
|
+
return self
|
|
193
|
+
|
|
194
|
+
def _does_template_path_exists(email_path):
|
|
195
|
+
"""
|
|
196
|
+
Checks whether a given template path exists within the email templates directory.
|
|
197
|
+
|
|
198
|
+
This method performs the following checks:
|
|
199
|
+
- Ensures the base template directory exists (e.g., TEMPLATES_DIR).
|
|
200
|
+
- Ensures the 'email_templates' folder exists within the base directory.
|
|
201
|
+
- Confirms the specific template file exists at the given path.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
email_path (str): The full relative path to the template file to check.
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
TemplateDirNotFound: If the base template directory is missing.
|
|
208
|
+
EmailTemplateDirNotFound: If the 'email_templates' folder is missing.
|
|
209
|
+
TemplateNotFound: If the given email template file does not exist.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
if not exists(TEMPLATES_DIR):
|
|
213
|
+
raise TemplateDirNotFound("The template directory wasn't found")
|
|
214
|
+
if not exists(EMAIL_TEMPLATES_DIR):
|
|
215
|
+
raise EmailTemplateDirNotFound("The emails_templates folder wasn't found")
|
|
216
|
+
if not exists(email_path):
|
|
217
|
+
raise EmailTemplateNotFound("The email template wasn't found")
|
|
218
|
+
|
|
219
|
+
def _create_path(self, template_name: str, folder_name: str = None):
|
|
220
|
+
"""
|
|
221
|
+
Constructs the full path to an email template file inside the 'email_templates' directory.
|
|
222
|
+
|
|
223
|
+
Allows for an optional subfolder to help organise templates by category (e.g., registration, password_reset).
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
template_name (str): Name of the template file (e.g., 'welcome.html').
|
|
227
|
+
folder_name (str, optional): Name of the subfolder inside 'email_templates'. Defaults to None.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
str: The combined relative path to the desired template.
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
ValueError: If template_name is not a string, or folder_name is not a string or None.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
if folder_name != None and not isinstance(folder_name, str):
|
|
237
|
+
raise ValueError("The folder name must be a string or None")
|
|
238
|
+
if not isinstance(template_name, str):
|
|
239
|
+
raise ValueError("The template name must be a string or None")
|
|
240
|
+
|
|
241
|
+
if folder_name is None:
|
|
242
|
+
return join(EMAIL_TEMPLATES_DIR, template_name)
|
|
243
|
+
return join(EMAIL_TEMPLATES_DIR, folder_name, template_name)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def with_headers(self, headers: Optional[Dict] = None) -> "EmailSender":
|
|
247
|
+
"""
|
|
248
|
+
Set custom headers for the email.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
headers (Optional[Dict], optional): A dictionary of headers to include in the email. Default is an empty dictionary.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
TypeError: If headers is not a dictionary.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
EmailSender: The current instance for chaining.
|
|
258
|
+
"""
|
|
259
|
+
if headers is None:
|
|
260
|
+
headers = {}
|
|
261
|
+
if not isinstance(headers, dict):
|
|
262
|
+
raise TypeError(f"Headers must be a dictionary, got {type(headers)} instead.")
|
|
263
|
+
self.headers = headers
|
|
264
|
+
return self
|
|
265
|
+
|
|
266
|
+
def send(self) -> int:
|
|
267
|
+
"""
|
|
268
|
+
Send the email using Django's email backend.
|
|
269
|
+
|
|
270
|
+
This will render the text and HTML templates, attach the HTML alternative, and send the email.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
ValueError: If any required fields are missing before sending.
|
|
274
|
+
EmailSendError: Raises an EmailSendError if an error occurs while sending an email.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
int: The number of successfully delivered messages (typically 1 if successful).
|
|
278
|
+
"""
|
|
279
|
+
if not all([self.from_email, self.to_email, self.subject, self.html_template, self.text_template]):
|
|
280
|
+
raise ValueError("All email components (from, to, subject, html, text) must be set before sending.")
|
|
281
|
+
|
|
282
|
+
self._does_template_path_exists(self.text_template)
|
|
283
|
+
self._does_template_path_exists(self.html_template)
|
|
284
|
+
|
|
285
|
+
text_content = render_to_string(self.text_template, context=self.context)
|
|
286
|
+
html_content = render_to_string(self.html_template, context=self.context)
|
|
287
|
+
|
|
288
|
+
msg = EmailMultiAlternatives(
|
|
289
|
+
subject=self.subject,
|
|
290
|
+
body=text_content,
|
|
291
|
+
from_email=self.from_email,
|
|
292
|
+
to=self.to_email,
|
|
293
|
+
headers=self.headers or None
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
msg.attach_alternative(html_content, "text/html")
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
return msg.send()
|
|
300
|
+
except EmailSenderError as e:
|
|
301
|
+
raise EmailSenderError(str(e))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class EmailSenderError(Exception):
|
|
2
|
+
"""Base exception for all email sender errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EmailTemplateDirNotFound(EmailSenderError):
|
|
7
|
+
"""Raised when the email_templates directory is not found."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EmailTemplateNotFound(EmailSenderError):
|
|
12
|
+
"""Raised when a specific email template file is missing."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TemplateDirNotFound(EmailSenderError):
|
|
17
|
+
"""Raised when a template directory does not exist."""
|
|
18
|
+
pass
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-email-sender
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A chainable Django email sender utility.
|
|
5
|
+
Home-page: https://github.com/EgbieAndersonUku1/django-email-sender
|
|
6
|
+
Author: Egbie Uku
|
|
7
|
+
Author-email: egbieuku@hotmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 3.2
|
|
11
|
+
Classifier: Framework :: Django :: 4.0
|
|
12
|
+
Classifier: Framework :: Django :: 5.0
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENCE
|
|
18
|
+
Requires-Dist: Django<6.0,>=3.2
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
# π§ Django Email Sender Utility
|
|
31
|
+
|
|
32
|
+
A clean, reusable, lightweight and chainable utility class for sending emails in Django using templates. It supports both HTML and plain text templates, context injection, and flexible usage β either directly, via subclassing, or abstracted into functions.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
π§ Why Use This?
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## β¨ Features
|
|
41
|
+
|
|
42
|
+
- Chainable API (`.to()`, `.from_address()`, etc.)
|
|
43
|
+
- Supports HTML and plain text templates
|
|
44
|
+
- Uses Django's template system for dynamic content
|
|
45
|
+
- Easy to integrate and override
|
|
46
|
+
- Encourages clean code and reusability
|
|
47
|
+
- Supports subclassing or functional abstractions
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
π Available Methods
|
|
55
|
+
|
|
56
|
+
Method Description
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- create() # Class factory method to instantiate the EmailSender.
|
|
60
|
+
- from_address(email) # Sets the senderβs email address.
|
|
61
|
+
- to(recipients) # Accepts a string or list of recipient email addresses.
|
|
62
|
+
- with_subject(subject) # Sets the email subject.
|
|
63
|
+
- with_context(context) # Context dictionary used in the templates.
|
|
64
|
+
- with_text_template(path) # Path to the plain text email template.
|
|
65
|
+
- with_html_template(path) # Path to the HTML email template.
|
|
66
|
+
- with_headers(headers) # Optional custom headers as a dictionary.
|
|
67
|
+
- send() # Sends the email. Returns the number of successfully delivered messages.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
π¨ Error Handling
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
- Raises ValueError if required fields are missing.
|
|
75
|
+
- Raises TypeError if headers are not provided as a dictionary.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## π Installation
|
|
80
|
+
|
|
81
|
+
This is a standalone utility. Copy the file into your Django project, or install it as a package using PyPI.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## π§© Requirements
|
|
86
|
+
|
|
87
|
+
- Python 3.8+
|
|
88
|
+
- Django >= 3.2
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## π§ͺ Basic Usage
|
|
93
|
+
|
|
94
|
+
Here's a simple example of how to send an email using `EmailSender`:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from email_sender import EmailSender
|
|
98
|
+
|
|
99
|
+
EmailSender.create()\
|
|
100
|
+
.from_address("no-reply@example.com")\
|
|
101
|
+
.to(["recipient@example.com"])\
|
|
102
|
+
.with_subject("Welcome!")\
|
|
103
|
+
.with_context({"username": "John"})\
|
|
104
|
+
.with_text_template("welcome.txt", folder_name="emails")\
|
|
105
|
+
.with_html_template("welcome.html", folder_name="emails")\
|
|
106
|
+
.send()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Explanation:
|
|
110
|
+
|
|
111
|
+
- `.from_address("no-reply@example.com")`: Specifies the sender's email address.
|
|
112
|
+
- `.to(["recipient@example.com"])`: Specifies the recipient's email address.
|
|
113
|
+
- `.with_subject("Welcome!")`: The subject of the email.
|
|
114
|
+
- `.with_context({"username": "John"})`: Context for the email templates, allowing dynamic insertion of values (e.g., the recipient's name).
|
|
115
|
+
- `.with_text_template("welcome.txt", folder_name="emails")`: The path to the text-based email template. Here, we specify the folder name (`emails`) where the template is stored. If no folder name is provided, it defaults to `email_templates/`.
|
|
116
|
+
- `.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.
|
|
117
|
+
- `.send()`: Sends the email.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## π§± Subclassing
|
|
122
|
+
|
|
123
|
+
You can also subclass the `EmailSender` class to create more specific types of emails.
|
|
124
|
+
|
|
125
|
+
### Example: Password Reset Email
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
class PasswordResetEmail(EmailSender):
|
|
129
|
+
def __init__(self, user):
|
|
130
|
+
super().__init__()
|
|
131
|
+
self.user = user
|
|
132
|
+
|
|
133
|
+
def build(self):
|
|
134
|
+
return self\
|
|
135
|
+
.from_address("no-reply@example.com")\
|
|
136
|
+
.to([self.user.email])\
|
|
137
|
+
.with_subject("Reset Your Password")\
|
|
138
|
+
.with_context({"username": self.user.username, "reset_link": generate_reset_link(self.user)})\
|
|
139
|
+
.with_text_template("reset_password.txt", folder_name="emails")\
|
|
140
|
+
.with_html_template("reset_password.html", folder_name="emails")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Usage:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
PasswordResetEmail(user).build().send()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Here, the `PasswordResetEmail` class uses `reset_password.txt` and `reset_password.html` templates from the `emails` folder.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## π οΈ Function-Based Abstractions
|
|
154
|
+
|
|
155
|
+
For a functional approach, you can also wrap `EmailSender` in specific functions to handle common email use cases.
|
|
156
|
+
|
|
157
|
+
### Example: Sending a Verification Email
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
|
|
161
|
+
def send_verification_email(user):
|
|
162
|
+
html_verification_path = "verification/verification.html"
|
|
163
|
+
text_verification_path = "verification/verification.txt"
|
|
164
|
+
subject = "Verify Your Email"
|
|
165
|
+
from_email = "no-reply@example.com"
|
|
166
|
+
|
|
167
|
+
return EmailSender.create()\
|
|
168
|
+
.from_address(from_email)\
|
|
169
|
+
.to([user.email])\
|
|
170
|
+
.with_subject(subject)\
|
|
171
|
+
.with_context({
|
|
172
|
+
"username": user.username,
|
|
173
|
+
"verification_link": generate_verification_link(user)
|
|
174
|
+
})\
|
|
175
|
+
.with_text_template(text_verification_path, folder_name="emails")\
|
|
176
|
+
.with_html_template(html_verification_path, folder_name="emails")\
|
|
177
|
+
.send()
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Example: Sending a Registration Email
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
def send_registration_email(user):
|
|
184
|
+
html_registration_path = "registration/registration.html"
|
|
185
|
+
text_registration_path = "registration/registration.txt"
|
|
186
|
+
|
|
187
|
+
subject = "Welcome to the Platform!"
|
|
188
|
+
from_email = "no-reply@example.com"
|
|
189
|
+
|
|
190
|
+
return EmailSender.create()\
|
|
191
|
+
.from_address(from_email)\
|
|
192
|
+
.to([user.email])\
|
|
193
|
+
.with_subject(subject)\
|
|
194
|
+
.with_context({"username": user.username})\
|
|
195
|
+
.with_text_template(text_registration_path, folder_name="emails")\
|
|
196
|
+
.with_html_template(html_registration_path, folder_name="emails")\
|
|
197
|
+
.send()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Advantages of this Approach:
|
|
201
|
+
|
|
202
|
+
- **Keeps your logic functional and simple**: It's straightforward to use and easy to test.
|
|
203
|
+
- **Keeps your email templates modular and easy to override**: Templates are organized in subfolders (e.g., `registration`, `verification`), making them easier to manage.
|
|
204
|
+
- **Clean and maintainable codebase**: You donβt have to subclass `EmailSender` each time, reducing complexity.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## π Templates
|
|
212
|
+
|
|
213
|
+
Templates must reside inside a dedicated `email_templates/` directory, which should exist inside your Django template directory.
|
|
214
|
+
|
|
215
|
+
This folder can contain your own structure to help organise different types of emails. For example:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Example
|
|
219
|
+
|
|
220
|
+
project/
|
|
221
|
+
βββ templates/
|
|
222
|
+
β βββ email_templates/
|
|
223
|
+
β βββ registration/
|
|
224
|
+
β βββ registration.html
|
|
225
|
+
β βββ registration.txt
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
When calling `with_html_template()` or `with_text_template()`, you can provide the subfolder and filename like so:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
EmailSender.create()
|
|
232
|
+
.with_html_template("registration.html", folder_name="registration")
|
|
233
|
+
.with_text_template("registration.txt", folder_name="registration")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
You **must** have both an `.html` and `.txt` version of the email template. These are required for rich content and email client compatibility.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
## π‘ Tips
|
|
242
|
+
|
|
243
|
+
- You can subclass `EmailSender` for different email types or simply wrap it in functions.
|
|
244
|
+
- Organise your templates by email type (`registration/`, `verification/`, etc.)
|
|
245
|
+
- Subject and context are fully customisable.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENCE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
django_email_sender/__init__.py
|
|
6
|
+
django_email_sender/email_sender.py
|
|
7
|
+
django_email_sender/exceptions.py
|
|
8
|
+
django_email_sender.egg-info/PKG-INFO
|
|
9
|
+
django_email_sender.egg-info/SOURCES.txt
|
|
10
|
+
django_email_sender.egg-info/dependency_links.txt
|
|
11
|
+
django_email_sender.egg-info/requires.txt
|
|
12
|
+
django_email_sender.egg-info/top_level.txt
|
|
13
|
+
tests/test_email_sender.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Django<6.0,>=3.2
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_email_sender
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="django-email-sender",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
"Django>=3.2,<6.0",
|
|
9
|
+
],
|
|
10
|
+
author="Egbie Uku",
|
|
11
|
+
author_email="egbieuku@hotmail.com",
|
|
12
|
+
description="A chainable Django email sender utility.",
|
|
13
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
14
|
+
long_description_content_type="text/markdown",
|
|
15
|
+
url="https://github.com/EgbieAndersonUku1/django-email-sender",
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Framework :: Django",
|
|
19
|
+
"Framework :: Django :: 3.2",
|
|
20
|
+
"Framework :: Django :: 4.0",
|
|
21
|
+
"Framework :: Django :: 5.0",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
],
|
|
25
|
+
python_requires='>=3.7',
|
|
26
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
from email_sender.email_sender import EmailSender
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestEmailSender(TestCase):
|
|
6
|
+
def test_email_sender_creates_and_sends_email(self):
|
|
7
|
+
|
|
8
|
+
# Example test for email sending logic
|
|
9
|
+
email_sender = EmailSender.create()\
|
|
10
|
+
.from_address("no-reply@example.com")\
|
|
11
|
+
.to(["test@example.com"])\
|
|
12
|
+
.with_subject("Test Email")\
|
|
13
|
+
.with_context({"username": "testuser"})\
|
|
14
|
+
.with_text_template("emails/test.txt")\
|
|
15
|
+
.with_html_template("emails/test.html")
|
|
16
|
+
|
|
17
|
+
# Test if the sender was set correctly
|
|
18
|
+
self.assertEqual(email_sender.from_address, "no-reply@example.com")
|
|
19
|
+
self.assertEqual(email_sender.subject, "Test Email")
|
|
20
|
+
|
|
21
|
+
# Here, you can mock `send_mail` to ensure it gets called
|
|
22
|
+
with self.assertRaises("SomeMockException"):
|
|
23
|
+
email_sender.send()
|