PGP encrypted / multipart templated emails for Django

Overview

Created by Stephen McDonald

Introduction

django-email-extras is a Django reusable app providing the ability to send PGP encrypted and multipart emails using Django templates. These features can be used together or separately. When configured to send PGP encrypted email, the ability for Admin users to manage PGP keys is also provided.

A tool for automatically opening multipart emails in a local web browser during development is also provided.

Dependencies

Installation

The easiest way to install django-email-extras is directly from PyPi using pip by running the command below:

$ pip install -U django-email-extras

Otherwise you can download django-email-extras and install it directly from source:

$ python setup.py install

Usage

Once installed, first add email_extras to your INSTALLED_APPS setting and run the migrations. Then there are two functions for sending email in the email_extras.utils module:

  • send_mail
  • send_mail_template

The former mimics the signature of django.core.mail.send_mail while the latter provides the ability to send multipart emails using the Django templating system. If configured correctly, both these functions will PGP encrypt emails as described below.

Sending PGP Encrypted Email

PGP explanation

Using python-gnupg, two models are defined in email_extras.models - Key and Address which represent a PGP key and an email address for a successfully imported key. These models exist purely for the sake of importing keys and removing keys for a particular address via the Django Admin.

When adding a key, the key is imported into the key ring on the server and the instance of the Key model is not saved. The email address for the key is also extracted and saved as an Address instance.

The Address model is then used when sending email to check for an existing key to determine whether an email should be encrypted. When an Address is deleted via the Django Admin, the key is removed from the key ring on the server.

Sending Multipart Email with Django Templates

As mentioned above, the following function is provided in the email_extras.utils module:

send_mail_template(subject, template, addr_from, addr_to,
    fail_silently=False, attachments=None, context=None,
    headers=None)

The arguments that differ from django.core.mail.send_mail are template and context. The template argument is simply the name of the template to be used for rendering the email contents.

A template consists of both a HTML file and a TXT file each responsible for their respective versions of the email and should be stored in the email_extras directory where your templates are stored, therefore if the name contact_form was given for the template argument, the two template files for the email would be:

  • templates/email_extras/contact_form.html
  • templates/email_extras/contact_form.txt

The attachments argument is a list of files to attach to the email. Each attachment can be the full filesystem path to the file, or a file name / file data pair.

The context argument is simply a dictionary that is used to populate the email templates, much like a normal request context would be used for a regular Django template.

The headers argument is a dictionary of extra headers to put on the message. The keys are the header name and values are the header values.

Configuration

There are two settings you can configure in your project's settings.py module:

  • EMAIL_EXTRAS_USE_GNUPG - Boolean that controls whether the PGP encryption features are used. Defaults to True if EMAIL_EXTRAS_GNUPG_HOME is specified, otherwise False.
  • EMAIL_EXTRAS_GNUPG_HOME - String representing a custom location for the GNUPG keyring.
  • EMAIL_EXTRAS_GNUPG_ENCODING - String representing a gnupg encoding. Defaults to GNUPG latin-1 and could be changed to e.g. utf-8 if needed. Check out python-gnupg docs for more info.
  • EMAIL_EXTRAS_ALWAYS_TRUST_KEYS - Skip key validation and assume that used keys are always fully trusted.

Local Browser Testing

When sending multipart emails during development, it can be useful to view the HTML part of the email in a web browser, without having to actually send emails and open them in a mail client. To use this feature during development, simply set your email backend as follows in your development settings.py module:

EMAIL_BACKEND = 'email_extras.backends.BrowsableEmailBackend'

With this configured, each time a multipart email is sent, it will be written to a temporary file, which is then automatically opened in a local web browser. Suffice to say, this should only be enabled during development!

Comments
  • Flexible body html type

    Flexible body html type

    A new keyword argument is introduced (body_html_type) to allow for setting mimetype in send_mail and send_mail_template() functions. It defaults to 'text/html', just as it was in the original code.

    The change allows to e.g. correctly open encrypted emails in gmail with mailvelope. Such scenario requires to set body_html_type to 'application/pgp-encrypted' or 'text/plain'. Without it, using the default value, opening an email results in "Error! Could not read this encrypted message: Error: Unknown ASCII armor type" for the scenario.

    The change is supposed to be neutral and extends the basic use and api. Thus, I hope it can be merged. Thanks in advance!

    Adrian

    opened by adibo 9
  • New PyPI Release?

    New PyPI Release?

    Hey Stephen,

    Any chance we can get a new PyPI release soon? It appears that https://github.com/stephenmcd/django-email-extras/pull/42 is necessary for Django 1.11 support.

    Thanks!

    opened by ericamador 8
  • Allow failed encryption handling

    Allow failed encryption handling

    If the encryption of a mail body fails, e.g. because the key is expired, python-gnupg just returns an empty string, which then becomes the mail body. See python-gnupg docs:

    Note Any public key provided for encryption should be trusted, otherwise encryption fails but without any warning. This is because gpg just prints a message to the console, but does not provide a specific error indication that the Python wrapper can use.

    This pull request aims to make it easier to add custom error handling in this case. It's easy to tell that the encryption failed: encrypted == "" and body != "". In this case utils.get_failed_encryption_body(body, addr) would be called, where body is the original/unencrypted body. That method just returns settings.FAILED_ENCRYPTION_BODY, which is set to "" (so, it's the same result as before), but could be overwritten with a more useful message. More important (for me), utils.get_failed_encryption_body(body, addr) can be easily monkey patched, which allows to add any kind of custom error handling and to return whatever could be useful.

    opened by theithec 6
  • Fully mimic the signature of django.core.mail.send_mail

    Fully mimic the signature of django.core.mail.send_mail

    The current signature of email_extras.utils.send_mail:

    def send_mail(subject, body_text, addr_from, addr_to, fail_silently=False,
                  attachments=None, body_html=None, connection=None, headers=None):
    

    is supposed to mimic the signature of django.core.mail.send_mail:

    def send_mail(subject, message, from_email, recipient_list,
                  fail_silently=False, auth_user=None, auth_password=None,
                  connection=None, html_message=None):
    

    but it doesn't quite manage it.

    This ensures that our send_mail function completely mimics Django's send_mail function.

    Changes:

    opened by blag 5
  • Python3 compatibility

    Python3 compatibility

    Hi Stephen,

    First off, thanks for making a great and useful app. I would like to use this app in a python 3 environment. Would it be possible to release a version that has python 3 support?

    As far as I can tell, only a few changes are required:

    • In utils.py:54 the unicode() call has to be replaced by django.utils.encoding.smart_text() or something similar.
    • In models.py the unicode() functions have to be replaced by str() functions, a @python_2_unicode_compatible decorator can be added for python2 compatibility. (see https://docs.djangoproject.com/en/dev/topics/python3/)
    opened by gompster 5
  • Pass on non-HTML mail

    Pass on non-HTML mail

    This is useful if you want to open HTML parts of emails in your web browser and you want to see the non-HTML parts in your terminal, or logged to a file.

    This adds a new option EMAIL_EXTRAS_ACTUAL_DEBUG_BACKEND and passes email onto it.

    The default for this is Django's console backend, so non-HTML parts will display in the developer's terminal. To completely turn that off, the option should be django.core.mail.backends.dummy.EmailBackend.

    opened by blag 4
  • Licensing issues

    Licensing issues

    Hi, I'm trying to figure out how the GPL plays with Python imports.

    I'm working on a closed-source project where we want to sign and encrypt certain email (read: password reset emails, notifications of changes to sensitive settings, suspicious authentication/authorization notifications, etc., but especially password reset emails).

    I'd like to use django-email-extras because it seems to be the most mature and maintained project that supports this, and it's licensed under the BSD-3 clause license.

    ~~However, it depends on and imports Python code from python-gnupg, which is licensed under the GPLv3. And the GPL being copyleft, that would seem to imply that - as long as this project depends on and imports code from python-gnupg - this project could only be licensed under a GPLv3 compatible license. And the BSD-3 clause is compatible, but if I depend on or import code from django-email-extras, the viral nature of copyleft licenses would mean that my code would then have to be licensed under a GPLv3 compatible license.~~

    ~~Can anybody assuage my fears of using this project? Does the FSF consider Python imports to be dissimilar from C's #includes and binary linking? Is there something I'm not seeing that prevents the viral nature of the GPL from subsuming code that imports code from this project?~~

    ~~Or should I keep looking for a similar project with completely compatible licensing requirements?~~

    Thanks!

    Edit: Answered my own question. See my comment

    opened by blag 3
  • Fix for empty unencrypted list

    Fix for empty unencrypted list

    While using email_extras (ver. 0.3.2) I came across an exception while attempting to send encrypted mail to a set of addresses for which gpg keys existed (= no unencrypted emails expected). In this case unencrypted + encrypted (from send_mail) had a form [[], ['mail1@...'], ['mail2@...']] and encrypt_if_key() failed. My pull request solves this problem. Hopefully, it's acceptable. If so, I would much appreciate a swift release of the updated pip module (0.3.3).

    Last but not least, thanks a lot for your great work! :)

    opened by adibo 3
  • Support sending to multiple recipients in a single email. Ref #11

    Support sending to multiple recipients in a single email. Ref #11

    Hi Stephen

    Taken a while since I was distracted but I return with a PR with regards to #11.

    I've tested with encrypted and non-encrypted messages and it behaves as we discussed/you suggested in the ticket.

    Happy to rework things if the implementation is not to your taste, or leave you to do so.

    opened by bentappin 3
  • BrowsableEmailBackend issue

    BrowsableEmailBackend issue

    I was getting an error in backends. It said that BaseEmailBackend was not defined. I added the line:

    from django.core.mail.backends.base import BaseEmailBackend

    and now it works. I don't know how this worked before.

    Thanks for your packages!

    Glenn

    opened by glennbach 2
  • Sending to multiple recipients

    Sending to multiple recipients

    Hello

    At the moment (correct me if I'm wrong) it seems like we cannot send an email to multiple recipients with the same email. I assume the code is structured like this to support encryption.

    Is there any room for modification of the library to support this?

    Some thoughts on approaches ...

    1. Modifying send_mail to support both single and multiple recipient modes could be a bit messy and may also break users expectations. For example, you could not send to multiple recipients and support USE_GNUPG. (Maybe an exception could be thrown?)
    2. Adding a new multiple recipient send mail function could end up duplicating functionality in the existing send_mail function.

    I'm happy to have a go and submit a PR if this is something that you want in the library.

    Thanks for your time.

    Ben.

    opened by bentappin 2
  • Missing migrations in 0.3.4

    Missing migrations in 0.3.4

    With the newest release 0.3.4 it seems there is a missing migration which makes lots of CI fail. It'd be necessary to add a new version as soon as possible including those migrations.

    opened by Moliholy 1
  • Make tag for distinguish installed version

    Make tag for distinguish installed version

    Hi Stephen, it would be nice to make tag for distinguish installed version. Latest merge fix compatibility with Django 1.11. But this important change can not be reflected by pip installation! Can you create tag please?

    Thanks Zdeněk

    opened by zbohm 1
  • Add tests

    Add tests

    Should be applied after/on top of #39. I will rebase this on top of master once that gets merged in.

    The first six commits are from #39.

    TODO:

    • [x] Fix some bugs uncovered by tests
    • [x] Add tests for email_extras.utils.send_mail function
    • [x] Add tests for the model save() and delete() functions
    • [x] Add tests for the encrypting backend from #39
    • [x] Add tests BrowsableEmailBackend
    • [x] Add tests for has_add_permission on Address admin
    • [x] Improve coverage for existing test cases (key.email_addresses property)
    • [x] Add tests for signing outgoing mail
    • [x] Add tests for management command
    • [x] Add tests for forms
    • [x] Add tests for default exception handlers, and tests to make sure custom exception handlers are called
    • [x] Figure out what the behavior should be when ALWAYS_TRUST is False and add tests for that ~~- is this even useful? Everything broke when I turned that off...~~ Test for this added from @theithec's comment
    • [x] Add Travis CI integration
    • [x] Add flake8 check
    • [x] Add Travis build status badge and Coveralls coverage status badge to README

    ~~There's only one line of crypto code that isn't tested: email_extras/backends.py:72~~

    This PR now has 100% test coverage for django-email-extras!

    Further work for future PRs:

    • Add create, update, and delete functions to the queryset on the models so queryset functions operate the same way model functions do
    • Move the save() and delete() functions from the models into their own pre_save, post_save, and pre_delete signal handlers and wire them up in email_extras/apps.py. This should allow us to use serialized fixtures in tests.
    • Make the models swappable
    opened by blag 6
  • Add encrypting backend mixin and mix it in with Django's built-in backends

    Add encrypting backend mixin and mix it in with Django's built-in backends

    This is my first attempt at this, so feedback and criticism is more than welcome!

    This is extremely similar to email_extras.utils.send_mail, but it's a mixin for mail backends.

    The main problem I have with django-email-extras is that third-party app developers have to explicitly opt-in to using it by calling our send_mail function. I am using django-accounts and django-allauth to handle user registration/login/forgotten passwords, and they use Django's built-in one from django.core.mail.

    I think encryption should be as easy to implement and use as possible (while still remaining actually secure), so this is an attempt in that direction. With this backend configured all mail Django sends will be sent through this backend, and opportunistically encrypted along the way (if the user has uploaded a key).

    I also mixed it in with Django's built-in backends, so there's Encrypting*EmailBackend for the Console, Locmem, Filebased, and Smtp backends.

    ~~I am explicitly not adding code to upload the key to keyservers because python-gnupg does not yet support generating key revocation certificates, so I don't want users to upload keys they can't easily revoke.~~ I added code to the new email_signing_key management command to automatically upload the key to one of more specified keyservers.

    TODO:

    • [x] Add management command/s for generating signing key? uploading to keyservers? with flag to skip if already exists?
    • [x] Optionally sign outgoing email with generated key
    • [x] Remove more specific exceptions and add a configurable exception handler for failed messages. I think this is a better way to handle things than simply throwing exceptions, because third party apps won't be expecting failed encryption exceptions or properly handle them.
    • [x] Add user friendly messages when SIGNING_KEY_FINGERPRINT specified a key that doesn't exist
    • [x] Document the process for how to add a signing key:
      1. Adjust EMAIL_EXTRAS_SIGNING_KEY_DATA
      2. ~~Fire up server, browse to admin for keys~~
      3. ~~Hit the "Generate signing key" button~~ Run the email_signing_key command to generate a signing key
      4. Copy displayed fingerprint
      5. Set EMAIL_EXTRAS_SIGNING_KEY_FINGERPRINT to copied fingerprint
      6. Restart server

    Edit: Thought of more/better ways to go about things.

    opened by blag 4
Owner
stephenmcd
stephenmcd
Churn Emails Inbox - Churn Emails Inbox Using Python

Churn Emails Inbox In this project, I have used the Python programming langauge

null 2 Nov 13, 2022
Envia-emails - A Python Program that creates emails

Envia-emails Os emails é algo muito importante e usado. Pensando nisso, eu criei

José Rodolfo 2 Mar 5, 2022
Django module to easily send emails/sms/tts/push using django templates stored on database and managed through the Django Admin

Django-Db-Mailer Documentation available at Read the Docs. What's that Django module to easily send emails/push/sms/tts using django templates stored

LPgenerator 250 Dec 21, 2022
Use Django admin to manage drip campaign emails using querysets on Django's User model.

Django Drip Drip campaigns are pre-written sets of emails sent to customers or prospects over time. Django Drips lets you use the admin to manage drip

Zapier 630 Nov 16, 2022
A script based on an article I wrote on decluttering emails.

Decluttering_Email A script based on an article I wrote on decluttering emails. What does this program do? This program is a python script that sends

Ogheneyoma Obomate Okobiah 6 Oct 21, 2021
Mailrise is an SMTP server that converts the emails it receives into Apprise notifications

Mailrise is an SMTP server that converts the emails it receives into Apprise notifications. The intended use case is as an email relay for a home lab or network. By accepting ordinary email, Mailrise enables Linux servers, Internet of Things devices, surveillance systems, and outdated software to gain access to the full suite of 60+ notification services supported by Apprise, from Matrix to Nextcloud to your desktop or mobile device.

Ryan Young 293 Jan 7, 2023
Heimdall watchtower automatically sends you emails to notify you of the latest progress of your deep learning programs.

This software automatically sends you emails to notify you of the latest progress of your deep learning programs.

Zhenyue Qin 22 Dec 6, 2021
Send Emails through the terminal , fast and secure

Send Emails through the terminal , fast and secure

null 11 Aug 7, 2022
Convert emails without attachments to pdf and send as email

Email to PDF to email This script will check an imap folder for unread emails. Any unread email that does not have an attachment will be converted to

Robert Luke 21 Nov 22, 2022
A spammer to send mass emails to teachers. (Education Purposes only!)

Securly-Extension-Spammer A spammer to send mass emails to teachers. (Education Purposes only!) Setup Just go a securly blocked page(You can do this b

null 3 Jan 25, 2022
Python library for sending emails.

Mail.py Python library for sending emails. Installation git clone https://github.com/SunPodder/Mail.py cd Mail.py python setup.py install Usage Imp

Sun 3 Oct 15, 2021
Collection of emails sent from the Hungarian gov and Viktor Orbán to the citizens of Hungary

Public list of Hungary and Viktor Orbán's emails since March 2021 Collection of emails sent from the Hungarian government and Viktor Orbán to the citi

Miguel Sozinho Ramalho 1 Mar 28, 2022
A python program capable of accessing passwords associated with emails through leaked databases.

passfind A python program capable of accessing passwords associated with emails through leaked databases. A python program capable of accessing passwo

null 6 Aug 14, 2022
This Tool Is For Sending Emails From A Terminal(Termux/Kali) etc.

This is a Basic python script to send emails from a Terminal(Termux/Kali) are the only tested currently.

AnonyVox 2 Apr 4, 2022
It s a useful project for developers ... It checks available and unavailable emails

EmailChecker It s a useful project for developers ... It checks available and unavailable emails Installation : pip install EmailChecker Domains are

Sidra ELEzz 19 Jan 1, 2023
Bulk send personalized emails using a .csv file and Gmail API (via EZGmail)

GSender Bulk send personalized emails using a .csv file and Gmail API (via EZGmail). Installation Install requirements.txt. Follow the EZGmail Install

null 1 Nov 23, 2021
Pysces (read: Pisces) is a program to help you send emails with an user-customizable time-based scheduling.

Pysces (Python Scheduled-Custom-Email-Sender) Pysces (read: Pisces) is a program to help you send emails with an user-customizable time-based email se

Peter 1 Jun 16, 2022
Will iterate through a list of emails on an attached csv file and email all of them a message of your choice

Email_Bot Will iterate through a list of emails on an attached csv file and email all of them a message of your choice. Before using, make sure you al

J. Brandon Walker 1 Nov 30, 2021
This python script will generate passwords for your emails, With certain lengths, And saves them into plain text files.

How to use. Change the Default length of genereated password in default.length.txt Type the email for your account. Type the website that the email an

null 2 Dec 26, 2021