Skip to main content

A reusable Django app to send emails via GOV.UK notify robustly and with good observability

Project description

django-govuk-notify-outbox logo - a blue envelope with a tick in a green circle

django-govuk-notify-outbox

A reusable Django app to send emails via GOV.UK notify robustly and with good observability.


Contents


Features

  • Sends emails outside of the request/response cycle (via Celery)
  • Automatically retries communication with GOV.UK Notify up to a maximum number / maximum amount of time
  • Usage is consistent with the send_email_notification function from notifications-python-client
  • Emails and updates from GOV.UK Notify (including HTTP errors and Python exceptions) are stored in Django models...
  • ... so can be linked (via foreign key or many-to-many relation) to other models to be used in the user-facing application
  • ... and are visible in the Django admin interface

Prerequisites

  • An existing (even if newly created) Django project
  • ... which is configured with Celery and Celery Beat to run background tasks
  • A GOV.UK Notify service API Key

Installation

  1. Install django-govuk-notify-outbox from PyPI.

    Typically you would do this by adding django-govuk-notify-outbox as a dependency to your existing Django project, for example with uv:

    uv add django-govuk-notify-outbox
    
  2. Add django_govuk_notify_outbox to settings.INSTALLED_APPS

    INSTALLED_APPS = [
        # ...
        "django_govuk_notify_outbox",
    ]
    
  3. Add settings.DJANGO_GOVUK_NOTIFY_OUTBOX__API_KEY with your GOV.UK Notify API key, typically from an environment variable.

    import os
    
    DJANGO_GOVUK_NOTIFY_OUTBOX__API_KEY = os.environ.get('DJANGO_GOVUK_NOTIFY_OUTBOX__API_KEY')
    
  4. Add django_govuk_notify_outbox.tasks.sync to settings.CELERY_BEAT_SCHEDULE on a frequent schedule, for example every 5 seconds. If there is nothing to do, this will finish quickly and without connecting to GOV.UK notify.

    CELERY_BEAT_SCHEDULE = {
        "sync": {
            "task": "django_govuk_notify_outbox.tasks.sync",
            "schedule": 5,
        },
    }
    
  5. Run python manage.py migrate to create the models. Often projects are setup to do this automatically on startup.

Usage

Fire and forget

Once installed, emails can be sent from any part of your Django project with the django_govuk_notify_outbox.utils.send_email_notification function, which is designed to be consistent with the "fire and forget"-style send_email_notification function from notifications-python-client

from django_govuk_notify_outbox.utils import send_email_notification

notifications_client.send_email_notification(
    email_address="amala@example.com",
    template_id="9d751e0e-f929-4891-82a1-a3e1c3c18ee3",
)

Attaching to your own models

The return value of send_email_notification is an instance of the Django model django_govuk_notify_outbox.models.EmailMessage, and it can be attached to your own models in the usual way via ForeignKey or ManyToManyField. Each EmailMessage is updated through the email sending process until is is delivered or failed.

For example:

# In your app's models.py
from django.db import models
from django_govuk_notify_outbox.models import EmailMessage

class MyModel(models.Model):
    email_messages = models.ManyToManyField(EmailMessage)

# Anywhere in your app
email_message = notifications_client.send_email_notification(
    email_address="amala@example.com",
    template_id="9d751e0e-f929-4891-82a1-a3e1c3c18ee3",
)
my_model_instance = MyModel.create()
my_model_instance.email_messages.add(email_message)
print(email_message.status)  # See below

Email status descriptions

Each EmailMessage.status field can be equal to one of 10 possible values. For the value that reflect a GOV.UK Notify status, see https://docs.notifications.service.gov.uk/python.html#email-status-descriptions for details.

State                                         Description
sending-to-notify         The email is about to be sent to GOV.UK Notify (or has very recently been sent).
retrying-send-to-notify         The email has failed sending to GOV.UK Notify at least once and will be retried (or has very recently been retried).
failed-send-to-notify         The email has failed sending to GOV.UK Notify and will not be retried.
sent-to-notify         The email has been sent to GOV.UK Notify
notify-created         GOV.UK notify reports the email in the created state, which essentially means queued: send will be attempted in the next few seconds.
notify-sending         GOV.UK notify reports the email in the sending state.
notify-delivered         GOV.UK notify reports the email in the delivered state.
notify-temporary-failure         GOV.UK notify reports the email in the temporary-failure state. Note that this does not mean that the email will be retried automatically, but more reflects that if another identical email is sent, it may succeed.
notify-technical-failure         GOV.UK Notify reports the email in the technical-failure state, which indicates a technical issue between Notify and its email provider. It will not be retried automatically.
notify-permanent-failure         GOV.UK notify reports the email in the permanent-failure state, which indicates it is not likely to succeed if another identical email is sent.

Happy path

5 statuses make up the happy path when there are no problems sending an email:

stateDiagram-v2

    sendingToNotify: sending-to-notify
    sentToNotify: sent-to-notify
    notifyCreated: notify-created
    notifySending: notify-sending
    notifyDelivered: notify-delivered

    [*] --> sendingToNotify
    sendingToNotify --> sentToNotify
    sentToNotify --> notifyCreated
    notifyCreated --> notifySending
    notifySending --> notifyDelivered
    notifyDelivered --> [*]

Happy and unhappy paths

How EmailMessage.status can change in all cases of both success and failure is shown in the following diagram:

stateDiagram-v2

    sendingToNotify: sending-to-notify
    retryingSendToNotify: retrying-send-to-notify
    sentToNotify: sent-to-notify
    failedSendToNotify: failed-send-to-notify
    notifyCreated: notify-created
    notifySending: notify-sending
    notifyDelivered: notify-delivered
    notifyPermanentFailure: notify-permanent-failure
    notifyTemporaryFailure: notify-temporary-failure
    notifyTechnicalFailure: notify-technical-failure

    [*] --> sendingToNotify

    sendingToNotify --> retryingSendToNotify
    retryingSendToNotify --> failedSendToNotify
    sendingToNotify --> sentToNotify
    retryingSendToNotify --> sentToNotify

    failedSendToNotify --> [*]

    sentToNotify --> notifyCreated
    notifyCreated --> notifySending

    notifySending --> notifyDelivered
    notifySending --> notifyPermanentFailure
    notifySending --> notifyTemporaryFailure
    notifySending --> notifyTechnicalFailure

    notifyPermanentFailure --> [*]
    notifyTemporaryFailure --> [*]
    notifyTechnicalFailure --> [*]

    notifyDelivered --> [*]

Configuration

Configuration is via settings that go into your Django project's settings.py

Setting Description
DJANGO_GOVUK_NOTIFY_OUTBOX__API_KEY The GOV.UK Notify API key for the service
DJANGO_GOVUK_NOTIFY_OUTBOX__SEND_TO_NOTIFY_RETRY_INTERVALS A list of how long to wait in seconds between retrying send to GOV.UK Notify
Default: [5,10,60,120,240,480,960,1920]
DJANGO_GOVUK_NOTIFY_OUTBOX__SEND_TO_NOTIFY_ABANDON_AFTER How long in seconds to wait to abandon send to GOV.UK notify, even if there are remaining retries according to the DJANGO_GOVUK_NOTIFY_OUTBOX__SEND_TO_NOTIFY_RETRY_INTERVALS setting. This is used to not send emails if the background worker has been offline for a long period of time and so the emails are no longer relevant.
Default: 3600

License

MIT

Project details


Download files

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

Source Distribution

django_govuk_notify_outbox-1.5.0.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

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

django_govuk_notify_outbox-1.5.0-py3-none-any.whl (18.3 kB view details)

Uploaded Python 3

File details

Details for the file django_govuk_notify_outbox-1.5.0.tar.gz.

File metadata

File hashes

Hashes for django_govuk_notify_outbox-1.5.0.tar.gz
Algorithm Hash digest
SHA256 cb1e0e1a33602ebb995359d5fe89b81c2e4e7116c3e897910dedae86a198e615
MD5 3e238ef44ec6f203fc8a353c6f1461e5
BLAKE2b-256 a61ee3c1a1d0668302a7d08e8c5d9951a8df9ed9e914bdf511dd03b51ef67ed3

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_govuk_notify_outbox-1.5.0.tar.gz:

Publisher: release.yml on uktrade/django-govuk-notify-outbox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_govuk_notify_outbox-1.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_govuk_notify_outbox-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 01eca875b4611b2d5731ede3a89abb7a3d31ddaf7fbab838dcb8ebdbc7cc9bfb
MD5 afa33ebf92862e6befe466dd43776464
BLAKE2b-256 4fedf9d2a9a822473288ccbab1f8be1339c6a4eea2e1c89b6bacf9c351dcb246

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_govuk_notify_outbox-1.5.0-py3-none-any.whl:

Publisher: release.yml on uktrade/django-govuk-notify-outbox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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