mail
Icon:
<i class="fa fa-envelope"></i>
We have other mail related apps:
Mandrill (do not use). Do NOT use this app. The code was moved to the
mail
app.
Prerequisites
Setup Celery (using Redis)…
The mail app supports sending mail using Mandrill or SparkPost
Requirements
Add the mail app to requirements/local.txt
:
-e ../../app/mail
Management Commands
check-mail-size
Uses file_size_as_base64
to check the size of file attachments when
encoded to base64?:
django-admin check-mail-size 100
Tip
The parameter is the pk
of the Mail
model
(source code in mail/management/commands/
).
email-has-clean-history
Check the email address to see if it has a clean history i.e. has not been rejected for spam, bouncing etc:
django-admin email-has-clean-history code@pkimber.net
Tip
The source code is in the mail
app in
mail/management/commands/email-has-clean-history.py
email-init-rejected
To reject an email address after checking Mandrill. Has the following parameters:
email address (case is ignored)
Reason for rejection,
hard-bounce
,soft-bounce
orspam
Name of the template (should be an optional parameter)
django-admin email-init-rejected code@pkimber.net soft-bounce notify-weekly
Tip
The source code is in the mail
app in
mail/management/commands/email-init-rejected.py
mail_send
Send all pending mail messages:
django-admin mail_send
Tip
Check MailManager
, get_mail_to_send
for selection criteria.
Settings
In your settings/base.py
, add mail
to your apps e.g:
LOCAL_APPS = (
'project',
...
'mail',
And add the default from address:
DEFAULT_FROM_EMAIL = 'patrick@hatherleigh.info'
Add the template type:
MAIL_TEMPLATE_TYPE = get_env_variable("MAIL_TEMPLATE_TYPE")
# if you are not using third party templates, you can use:
MAIL_TEMPLATE_TYPE = "django"
Warning
Don’t forget to set the EMAIL_BACKEND
to use Mandrill or
Sparkpost as appropriate.
In project/management/commands/start_scheduler.py
(see APScheduler (with Redis) for more information):
# process_mail
scheduler.add_job(
"mail.tasks:schedule_process_mail",
"interval",
minutes=60,
id="schedule_process_mail",
max_instances=1,
replace_existing=True,
)
Deploy
A host_name
for your site will automatically be generated by Salt. If you
need to override this, then you can set the host_name
in the pillar. For
more information, see Host Name.
Note
Our standard is to use host_name
rather than the Django
build_absolute_uri
method for adding links to email messages.
Usage
Important
The slug
value for the template name should always be in
lower case. We will make all Mandrill template names lower case
- using underscores instead of a space.
Create a Template
from django.conf import settings
from mail.models import MailTemplate
# slug for the email template
PAYMENT_THANKYOU = 'payment_thankyou'
MailTemplate.objects.init_mail_template(
PAYMENT_THANKYOU,
'Thank you for your payment',
(
"You can add the following variables to the template:\n"
"{{ NAME }} name of the customer.\n"
"{{ DATE }} date of the transaction.\n"
"{{ DESCRIPTION }} transaction detail.\n"
"{{ TOTAL }} total value of the transaction."
),
False,
MailTemplate.MANDRILL,
subject='Thank you for your payment',
description="We will send you the course materials.",
)
Queue an email
Note
In the examples below, self.object
is an object which the email
will be linked to.
To queue an email without using a template:
from mail.models import Notify
from mail.service import queue_mail_message
email_addresses = [n.email for n in Notify.objects.all()]
if email_addresses:
queue_mail_message(
self.object,
email_addresses,
subject,
message,
)
else:
logging.error(
"Cannot send email notification of payment. "
"No email addresses set-up in 'mail.models.Notify'"
)
To add attachments, add a list of file names to the queue_mail_message
function e.g:
from mail.service import queue_mail_message
queue_mail_message(
self.object,
['web@pkimber.net'],
subject,
message,
attachments=['/temp/note.doc'],
# attachments to be a simple path rather than a copy of the 'FileField'
# attachments_use_path=True,
)
Note
Attachments are only available on the queue_mail_message
function. They do not work with templates (queue_mail_template
).
Queue an email template
from mail.service import queue_mail_template
context = {
'test@pkimber.net': {
"DATE": created.strftime("%d-%b-%Y %H:%M:%S"),
"DESCRIPTION": description,
"NAME": "Re: {}".format(subject),
"TOTAL": "123.34",
},
}
queue_mail_template(
self.object,
'enquiry_acknowledgement',
context,
)
A dictionary or list can be added to the context if required e.g:
context = {
'test@pkimber.net': {
"cake": ['Carrot', 'Ginger'],
"fruit": {'a': 'Apple'},
},
}
Note
The data will be serialized and deserialized using json.dumps
and
json.loads
, so not all data types have their types preserved (for
some unknown reason) e.g. datetime
becomes a string.
Send queued emails
from django.db import transaction
from mail.tasks import process_mail
# with a transaction
transaction.on_commit(lambda: process_mail.send())
# without a transaction
process_mail.send()
To send email, you can use the mail_send
management command e.g:
django-admin mail_send
Rejected Mail
If you look for rejected email addresses using Mandrill:
# mandrill - find rejected email
# https://www.kbsoftware.co.uk/crm/ticket/5388/
scheduler.add_job(
"mail.tasks:schedule_find_rejected_email",
"cron",
hour=2,
minute=30,
id="schedule_find_rejected_email",
max_instances=1,
replace_existing=True,
)
Testing
Unit Testing
Pick some code from the following (just a quick reference):
from mail.models import Message
from mail.tests.factories import MailTemplateFactory
MailTemplateFactory(slug=Enrol.MAIL_TEMPLATE_CHARGE)
NotifyFactory()
# check email template context
assert 1 == Message.objects.count()
message = Message.objects.first()
assert 1 == message.mail_set.count()
mail = message.mail_set.first()
assert 4 == mail.mailfield_set.count()
assert {
'description': '2 of 3 instalments',
'enrol_description': '1 x Apple @ 100.00',
'name': enrol.checkout_name,
'total': '£{:.2f}'.format(Decimal('50.00')),
} == {f.key: f.value for f in mail.mailfield_set.all()}
Maintenance
Warning
Only run the following command on a test site. It will mark all emails as sent (which you wouldn’t want on a live site)!
This will mark all emails as sent:
from django.utils import timezone
from mail.models import Mail
Mail.objects.filter(sent__isnull=True).update(sent=timezone.now())
Mandrill
Mandrill Requirements
Add the following to requirements/base.txt
:
djrill==<current version e.g. 1.3.0>
See Requirements for the current version…
Note
We have started using django-anymail for SparkPost.
It would probably be a good idea to replace djrill
with
django-anymail at some stage
(for more information, see SparkPost).
Mandrill Settings
Add the following to settings/production.py
:
# mandrill
EMAIL_BACKEND = 'djrill.mail.backends.djrill.DjrillBackend'
MANDRILL_API_KEY = get_env_variable('MANDRILL_API_KEY')
MANDRILL_USER_NAME = get_env_variable('MANDRILL_USER_NAME')
Warning
Don’t forget to set the EMAIL_BACKEND
to use Mandrill.
Mandrill Development
For Mandrill add the following to your .private
file e.g:
export MANDRILL_API_KEY="your-api-key"
export MANDRILL_USER_NAME="notify@hatherleigh.info"
Mandrill Deploy
In the salt pillar sls
file for your site, add the
mandrill_api_key
and mandrill_user_name
e.g:
sites:
my_site:
celery: True
env:
mail_template_type: <'sparkpost' | 'mandrill' | 'django'>
mandrill_api_key: your-api-key
mandrill_user_name: <mandrill user name>
Mandrill Template
Tip
The example_mail_template management command has some sample code showing the following features.
Mandrill Templates are usually designed in MailChimp and then Sent to Mandrill.
When you Queue an email template, you can add variables to the templates. To add line breaks to these variables:
context={
"test@pkimber.net": {
"COLOURS": "1. Red<br />2. Green<br />3. Blue",
You can also add HTML links:
context={
"test@pkimber.net": {
"ACTION": '<a href="https://www.kbsoftware.co.uk/" mc:disable-tracking>Click here!</a>',
Tip
The mc:disable-tracking
attribute prevents click tracking and makes
the generated HTML much easier to read
(from Can I disable click-tracking on selected links in my email?).
Warning
For some reason HTML links do not work when loaded from the Mandrill View content option in Outbound, Activity. I am hoping they work when the email is live!
To disable tracking in a MailChimp template:
Click the chevron icon <>
.
Add mc:disable-tracking
to the anchor tag e.g.
The variable name for unsubscribe is UNSUB
:
To unsubscribe from this list <a href="*|UNSUB|*" mc:disable-tracking="">click here...</a><br />
SMTP
SparkPost
Google Mail
SMTP settings to send mail from a printer, scanner, or app:
Note
I failed to get this working for several days.
It finally worked when I set Require TLS encryption: to No.
I then realised that EMAIL_USE_TLS
was not in setttings, so it was
defaulting to False
.
Add the following to settings/production.py
:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp-relay.gmail.com'
EMAIL_HOST_USER = get_env_variable('EMAIL_HOST_USER')
EMAIL_PORT = 587
EMAIL_USE_TLS = True
Standard SMTP
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = get_env_variable('EMAIL_HOST')
EMAIL_HOST_USER = get_env_variable('EMAIL_HOST_USER')
EMAIL_PORT = int(get_env_variable('EMAIL_PORT'))
EMAIL_USE_TLS = get_env_variable_bool('EMAIL_USE_TLS')
SparkPost
Tip
We have started using django-anymail instead of sparkpost
to
allow use of the EU account
and to
add track options.
Links
SparkPost Requirements
Add the following to requirements/base.txt
:
sparkpost==<current version e.g. 1.3.0>
See Requirements for the current version.
SparkPost Settings
Add the following to settings/base.py
:
# sparkpost
EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend'
SPARKPOST_API_KEY = get_env_variable('SPARKPOST_API_KEY')
SPARKPOST_OPTIONS = {
'track_opens': False, # or True as required
'track_clicks': False, # or True as required
'transactional': True,
}
SparkPost Development
Add the following to your .private
file e.g:
export SPARKPOST_API_KEY="your-api-key"
SparkPost Deploy
In the salt pillar sls
file for your site add the sparkpost_api_key
e.g:
sites:
my_site:
celery: True
env:
mail_template_type: <'sparkpost' | 'mandrill' | 'django'>
sparkpost_api_key: your-api-key
Tip
SparkPost for API, Domains and SMTP
Convert from Mandrill
In requirements/base.txt
include the line:
sparkpost==<current sparkpost version>
e.g. at the time of writing the latest version is 1.3.0:
sparkpost==1.3.0
If mandrill was previously used remove the line:
djrill==2.1.0
In your .private
file for the project include the lines:
export SPARKPOST_API_KEY='<your sparkpost api key>'
unset MANDRILL_API_KEY
unset MANDRILL_USER_NAME
If mandrill was previously included in your project remove the lines:
export MANDRILL_API_KEY='<your mandrill api key>'
export MANDRILL_USER_NAME='<your mandrill username>'
In settings/base.py
include the line:
DEFAULT_FROM_EMAIL = 'user@example.com'
EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend'
SPARKPOST_API_KEY = get_env_variable('SPARKPOST_API_KEY')
SPARKPOST_OPTIONS = {
'track_opens': False, # or True as required
'track_clicks': False, # or True as required
'transactional': True,
}
If mandrill was previously included in the project remove the lines (search
for EMAIL_BACKEND
in the settings directory to make sure you get all
occurances):
EMAIL_BACKEND = 'djrill.mail.backends.djrill.DjrillBackend'
MANDRILL_API_KEY = get_env_variable('MANDRILL_API_KEY')
MANDRILL_USER_NAME = get_env_variable('MANDRILL_USER_NAME')
In settings/base.py
alter the THIRD_PARTY_APPS
(included in INSTALLED_APPS
) to include the sparkpost
app for example:
THIRD_PARTY_APPS = (
'easy_thumbnails',
'reversion',
'captcha',
'sparkpost',
)
If mandrill was previously used in a the project remove the following line
from THIRD_PARTY_APPS
:
'djrill',
Tips
Celery (or cron)
If you are not using Celery add the the mail_send
cron command e.g:
sites:
my_site:
celery: True
cron:
mail_send:
schedule: "*/5 * * * *"
env:
mail_template_type: <'sparkpost' | 'mandrill' | 'django'>
sparkpost_api_key: your-api-key
Password Reset
When testing the password reset workflow, make sure you use a valid email
address for a user. On the standard demo data, this will be web@pkimber.net