Shipping templates from your add-on

One entry point pointing at one dict. That is the whole registration mechanism — imio.emailkit uses exactly the same one for its own templates, so it is its own first consumer.

Declare the entry point

entry_points = {
    "imio.emailkit.templates": [
        "imio.pm.notifications = imio.pm.notifications:emailkit",
    ],
}

Point it at a dict

# imio/pm/notifications/__init__.py
emailkit = {
    "directory": "templates",          # relative to the package
    "templates": {
        "item_published": {
            "subject": _("email_subject_item_published"),
            "preheader": _("email_preheader_item_published"),  # optional
        },
        "meeting_convocation": {
            "subject": _("email_subject_meeting_convocation"),
        },
    },
}
  • Name
    directory
    Type
    str, default 'templates'
    Description

    Where the compiled .pt files live, relative to your package.

  • Name
    subject
    Type
    i18n msgid
    Description

    Lives in the registration, translated per recipient language at send time. There is no metadata sidecar and no front-matter round-trip.

  • Name
    preheader
    Type
    i18n msgid, optional
    Description

    The hidden inbox-preview line next to the subject — the highest-visibility email feature everyone forgets, and every inbox shows it. Omitted, the div collapses to nothing.

Names are namespaced at lookup: imio.pm.notifications:item_published. An unknown name raises TemplateNotFound carrying the list of names that are registered.

The entry-point group is scanned once at start-up and cached.

Lay the package out

src/imio/pm/notifications/
├── emails/                      # Maizzle project — dev-only, excluded from sdist
│   ├── package.json
│   ├── maizzle.config.js        # extends the kit base config
│   └── src/templates/
│       ├── item_published.vue
│       └── meeting_convocation.vue
├── templates/                   # ✅ committed build output
│   ├── item_published.pt
│   ├── item_published.txt.pt    # hand-authored plaintext twin
│   ├── meeting_convocation.pt
│   └── meeting_convocation.txt.pt
└── tests/
    ├── fixtures/item_published.py
    └── golden/item_published.fr.html

In MANIFEST.in:

recursive-include src/imio/pm/notifications/templates *.pt
prune src/imio/pm/notifications/emails

The emails/ sources are dev-only; the compiled .pt files are what ship.

Wire up the buildout

[buildout]
parts = ... emails

[emails]
recipe = imio.recipe.emailkit
eggs = ${instance:eggs}
# compile-on-install = false   (default)
# kit-mode = path | copy       (default: path)
# node-bin = node

The recipe resolves the part's eggs, collects every distribution exposing the imio.emailkit.templates entry point, resolves the design kit from the imio.emailkit egg, and generates three scripts:

  • Name
    bin/compile-emails [--package NAME] [--watch] [--new NAME]
    Description

    Wire the kit, npm ci if node_modules is stale against the lockfile, run Maizzle, rename *.html to *.pt, move into templates/. --new NAME scaffolds the four files a template needs — a .vue skeleton, a fixture, a golden placeholder and a registration stub to paste.

  • Name
    bin/check-emails [--package NAME]
    Description

    The staleness gate plus the authoring lint. This is the CI gate.

  • Name
    bin/preview-emails [--package NAME]
    Description

    Watch the .vue sources, compile, render through render() with the committed fixtures, serve with live reload and a language switcher.

The CI contract

Two gates, for every add-on that ships templates:

  1. bin/check-emails --package <self> — the build output is not stale, and the sources pass the lint.
  2. Golden-file tests — runtime rendering is intact.

What each template needs

  • <name>.pt — the compiled output, committed.
  • <name>.txt.pt — the hand-authored plaintext twin, where plaintext quality matters. Without it, render() falls back to naive extraction and logs a deprecation.
  • tests/fixtures/<name>.py — a CONTEXT dict.
  • tests/golden/<name>.<lang>.html and .txt — the snapshots.

bin/compile-emails --new <name> scaffolds all four. Templates have a rigid shape; making the right structure the path of least resistance beats documenting it.