Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

imio.recipe.emailkit

A zc.buildout recipe that gives every imio.emailkit consumer three scripts: bin/compile-emails, bin/check-emails and bin/preview-emails.

SPEC.md §5 in the repository root is the specification. This README is how to use it.

Usage

[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              (resolution: PATH by default)

The part resolves the eggs, collects every distribution whose ZCML registers <emailkit:templates> (§4), records each one's emails/ and templates/ directory, resolves the design kit out of the imio.emailkit egg (§3), and writes the three scripts.

A plain buildout run invokes no Node, touches no emails/ directory, and imports no consumer code. That is not a happy accident, it is the point: §5's "Explicitly rejected" section rules out compiling at buildout time because it "would make Node a production dependency across ~350 applications and couple deployments to npm availability". compile-on-install exists for deployments that deliberately accept Node at deploy time, and it defaults to false.

Options

Option Default What it does
eggs required the distributions to scan. ${instance:eggs} normally.
kit-mode path how the design kit is wired into each consumer's Maizzle build.
node-bin node the Node executable. npm and npx are taken beside it.
compile-on-install false run compile-emails as an install step. Opt-in.

Every one of them is also a command-line flag on the generated scripts, so trying --kit-mode copy does not mean editing buildout.cfg.

The scripts

bin/compile-emails [--package NAME] [--watch] [--new NAME]

Wire the kit → npm ci in emails/ if node_modules is stale against the lockfile → npx maizzle build → copy the hand-authored plaintext twins back in. Non-zero on any failure, and every package is attempted before it gives up, so one broken addon does not hide the state of the others.

There is no rename step and no move step: Maizzle 6's output.extension emits .pt directly and the consumer's own output.path writes into templates/ (docs/DECISIONS.md, §10.2). What is copied is emails/twins/*.txt.pt, because maizzle build empties its output directory and would otherwise delete a committed twin.

--watch delegates to Maizzle's dev server, which shows build-time output: raw ${item/title}, unexpanded tal:repeat. Use preview-emails --watch for the loop you actually want.

--new NAME scaffolds the four files a template needs — a .vue skeleton, a fixture, a golden placeholder and a registration stub to paste — and refuses to overwrite anything without --force. The skeleton starts on the right side of every §3 authoring rule.

bin/check-emails [--package NAME]

The CI gate. Two gates:

  1. Staleness. Snapshot the committed .pt, build in place, diff, restore. Exit 1 with a per-file diff. Every .pt under the package is covered, not just templates/ — imio.emailkit's own build also emits §8's jbot overrides elsewhere — while directories the build never wrote into are left alone.
  2. The authoring lint, python -m imio.emailkit.lint <paths>. Called, not reimplemented: the rules are about the templates, so they live with the runtime that ships them. A missing lint module fails the gate rather than skipping it; --no-lint makes skipping a deliberate, visible choice.

--lint-only runs gate 2 alone, which needs no Node.

bin/preview-emails [--package NAME] [--watch]

Compile, render every registered template through render() with its committed fixture (§7), and serve the result with a language switcher and live reload. What you look at is the mail, not the build output.

It needs no ZODB and no zope.conf: render() is a pure function of (template, context, registry state), so a minimal ZCML load is enough for real placeholder substitution and real FR/NL/DE translations. Two things are therefore not real — portal_url is empty, and the theme tokens come from the kit's own defaults, which --theme token=value overrides. For a preview against a real site's branding, use @@emailkit-preview (§6.3).

--no-compile renders the committed .pt as they are, which is a fast way to see what is actually in git.

What a consumer addon has to do

Three things, once.

1. Lay the addon out as §4 describes. emails/ may sit inside the package or at the checkout root; both are found.

src/acme/notifications/
├── emails/                     # dev only; prune it from the sdist
│   ├── package.json
│   ├── maizzle.config.js
│   ├── .kit/                   # generated; ignores itself
│   ├── twins/                  # hand-authored *.txt.pt, if any
│   └── src/templates/*.vue
└── templates/*.pt              # committed build output

2. Write emails/maizzle.config.js against ./.kit/. Three imports and one override; recipe/tests/consumer is a working example.

import { defineConfig } from '@maizzle/framework'
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'

import { kitBaseConfig } from './.kit/maizzle.config.base.js'

const here = dirname(fileURLToPath(import.meta.url))
const kit = kitBaseConfig()

export default defineConfig({
  ...kit,
  output: { ...kit.output, path: resolve(here, '..', 'templates') },
})

emails/.kit/ is materialised by compile-emails before every build and is identical in shape in both kit-modes, so a consumer's config never mentions the mode. In path mode it holds a two-line re-export of the kit inside the installed imio.emailkit egg, so Maizzle resolves components straight out of site-packages (§10.1's zero-copy mode); in copy mode it holds the kit itself. Consumers never vendor kit files (§3).

Do not add "type": "module" to emails/package.json. Without it Maizzle loads the config through jiti, which transpiles the ESM syntax in the kit file — a file that, in path mode, lives outside any npm tree and has no package.json of its own to declare its module type.

3. Register in ZCML and run the gates in CI (§4, §7):

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:emailkit="http://namespaces.imio.be/emailkit"
    i18n_domain="acme.notifications"
    >
  <include package="imio.emailkit" file="meta.zcml" />
  <emailkit:templates>
    <emailkit:template name="welcome" subject="[email_subject_welcome] Welcome" />
  </emailkit:templates>
</configure>
bin/check-emails --package acme.notifications

kit-mode: which one?

path unless something forces you off it. It is zero-copy, it cannot go stale, and Phase 0 verified that Maizzle resolves components.source from an absolute path outside the project root. copy exists as §5's documented fallback and is verified to produce byte-identical output; reach for it if a future Maizzle or a packaging environment stops resolving files outside the npm tree.

Development

This distribution lives in the imio.emailkit repository as a sibling directory (docs/DECISIONS.md), released separately. Its tests run in two environments, because no single one has both buildout and the Plone runtime:

make recipe-test      # both runs; the union covers every test
make buildout-test    # the SPEC §9 phase 4 acceptance test, end to end
make buildout-clean   # remove everything that writes

test-buildout.cfg at the repository root is the harness; test-buildout-pypi.cfg is the same thing resolving from PyPI, i.e. the literal "git clone && buildout".

Metadata

Release files for imio.recipe.emailkit 1.0.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for imio.recipe.emailkit 1.0.0b1
File Size Uploaded
imio_recipe_emailkit-1.0.0b1.tar.gz 70.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for imio.recipe.emailkit 1.0.0b1
File Interpreter ABI Platform
imio_recipe_emailkit-1.0.0b1-py3-none-any.whl Python 3 none any Details

Total release size: 125.5 kB

Release files / imio_recipe_emailkit-1.0.0b1.tar.gz

Download URL imio_recipe_emailkit-1.0.0b1.tar.gz
Size 70.0 kB
Tags Source
SHA-256 checksum
How to use checksums
0f87580e837270b3fffa5622c5a0a178374777be4be8dd153ffa7f3c51730dff
BLAKE2b-256 checksum
How to use checksums
110b50211eda8712e16253a5db9c2a0943db75c4740f159d01c81c5b7607c074
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.12

Release files / imio_recipe_emailkit-1.0.0b1-py3-none-any.whl

Download URL imio_recipe_emailkit-1.0.0b1-py3-none-any.whl
Size 55.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6793e8058c5a18628f3da049d87afab417a92dd10d3c313725c8fa3c073b2c45
BLAKE2b-256 checksum
How to use checksums
6f899e48f480b94afcaf077b603704873f53aeab16ac690b9a7bb77b46d387a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.12
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page