Preview & send-test

Two ways to look at a template: a make target that serves the rendered mails locally, and a view inside a running site. Both render through render() with the committed fixtures, so what you see is what a recipient gets — and both can also show you the committed .pt itself, which needs no fixture at all.

The authoring loop

make build-emails      # compile emails/ into the package; commit the .pt files
make check-emails      # the lint and the staleness gate CI runs
make preview-emails    # render the committed templates with the committed fixtures

The index make preview-emails writes carries a .pt link per template alongside the per-language html / txt ones. It is written for every registered template whether its fixture resolved or not — the one artifact there that cannot fail for lack of one.

@@emailkit-preview

Manager-only. Lists every registered template, renders each one in an iframe against its committed fixture, with a language switcher, a mode switcher and a theme-token panel.

It is two views rather than one:

  • Name
    @@emailkit-preview
    Description

    The chrome — the template list, the language switcher, the theme-token panel and the send-test form.

  • Name
    @@emailkit-preview-body
    Description

    The rendered mail and nothing else, loaded by the chrome in an <iframe>.

Why two. A transactional email carries a full stylesheet written for mail clients — body { } rules, table resets, !important utilities. Inlining that markup into the chrome would let it restyle the Plone UI and let the Plone UI flatter it, so the one thing this view exists to show would be the one thing it shows wrongly. An iframe is the boring browser-level answer and costs one extra registration.

The three modes

render() returns two things, and the preview shows both. A third mode shows the committed .pt itself.

  • Name
    html
    Description

    render()'s HTML part, with the committed fixture. The default.

  • Name
    text
    Description

    render()'s plaintext part, with the same fixture. Half the output, and the half nobody looks at until a client shows it.

  • Name
    .pt
    Description

    The committed .pt served to the same iframe as text/html, with no render() pass and no fixture. Nothing is substituted: ${item/title} stands where its value would be, and every tal:condition branch shows at once.

The .pt mode is the only one that works without a fixture, which is what it is for: a template compiled and committed before anyone has written tests/fixtures/<name>.py, or one installed from an egg with no source tree. In both cases it is otherwise the one template you cannot look at.

The file shown is the one render() would compile, z3c.jbot overrides included.

Send test

A button that mails the currently previewed template, fixture and language to the logged-in user's own address.

Browser previews lie, Outlook doesn't. This is the only way to find out what a client actually does with your markup, and it closes the loop for the cost of one form.

Where fixtures come from

tests/fixtures/<name>.py, inside the checkout of whichever add-on ships the template — not inside the installed package. tests/ is not shipped, and must not be: it is not importable from a released egg.

So the fixture is resolved by walking up from the template directory to find the checkout. Its absence is reported as a plain fact rather than an error: an egg installed without its source tree is the normal production case. The page then offers the .pt mode, which needs no fixture — in the template list and beside the explanation both.

Which Plone default mails show up here

Two of the three do not appear in the preview list, and cannot: password reset and registration are rendered by a stock Plone view, so they speak that view's namespace and render() — which supplies a flat context — can never render them. They are shipped, tested and overridable; just not discoverable.

The username reminder does appear, as imio.emailkit:get_username. Stock Plone has no template for that mail at all — it is a hardcoded plaintext string — so this package overrides the view rather than a file, and owning the view is what buys the flat dialect back. It renders through render() like any consumer template.

Use imio.emailkit:notification as the worked example of an ordinary template.