inputlogic / django-api-starter

Boilerplate for starting Django DRF / API projects
2 stars 0 forks source link

Improve management of Mail templates #91

Open staydecent opened 3 years ago

staydecent commented 3 years ago

We want to avoid calling Mail templates that don't exist in production. We also want to avoid calling mail templates with the wrong or missing data.

The first part is to create a registry of Mail templates. This way, we have an explicit list of the templates we expect to exist in production.

Possible ways to define the registry:

  1. In settings

    MAIL_REGISTRY = {
    'Forgot Password': {
        'email': 'example@example.com',
        'reset_url': 'example.com/forgot/1234/12'
    },
    'Confirm Email': {
        'email': 'example@example.com',
        'confirm_url': 'example.com/confirm/1234/12'
    },
    ...
    }
  2. With decorators

    @mail('Forgot Password')
    def forgot_password(email, reset_url):
    ...

Open to other ideas for managing the registry as well.

Beyond just having a registry, it would be ideal to automate syncing templates from local development to the staging and productions servers. Iterating on a solution that @adriaanwm has implemented before:

This is just one idea to automate syncing of templates from local development to the staging and production servers. Ideas are welcomed!

adriaanwm commented 3 years ago

Maybe it should be a function call

send_forgot_password = mail.register('Forgot Password', {'email': 'example@example.com', 'reset_url': 'example.com/reset/123/123'})

and mail.register can automatically check for changes via hash of the name and keys

def register(name, data):
    hash = sha256_name_and_data_keys(name, data)
    try:
        existing = MailDefinition.objects.get(hash=hash)
        if not existing.is_synced_to_production:
              sync(existing)
    except MailDefinition.NotFound:
        old_versions = MailDefinition.objects.filter(name=name).order_by('version')
        version = 1 if len(old_versions) === 0 else (old_versions[-1].version + 1)
        definition = MailDefinition.objects.create(name=name, hash=hash, version = last_version + 1, data_example=data_example)
        sync(definition)

    def send(**kwargs):
          # handle potential errors like kwargs not matching data
          # send email
    return send
adriaanwm commented 3 years ago
class MailDefinition(models.Model):
    active_template = ForeignKey('Template', null=True, blank=True)
    name = ...
    data = ...
    version = ...
    hash = ... # unique true

class Template(models.Model):
    mail_definition = ForeignKey('MailDefinition')
    body = ...
    layout = ForeignKey('Layout')

This will let the admin work on different templates for the same email. A mistake editing an active template in production could be embarassing. Maybe even block templates from being edited while they are active.

adriaanwm commented 3 years ago

Not sure best name for MailDefinition... maybe MailType ?