Ten runnable, numbered examples showing how to use the Inky email framework from Node.js — from the smallest possible transform up to a transactional-email capstone and a nunjucks/Total-CMS-style integration.
This is the Node.js port of the PHP reference suite
(see its SUITE.md for the full,
language-neutral porting spec that both suites implement against). The same
ten examples, against the same required output markers, exist here.
Requires Inky v2. See the main inky repo.
The WASM/Node binding has no build. inky's WASM build
(bindings/node) runs in a sandboxed WASM runtime with no filesystem
access, so it cannot resolve <layout src="...">, <include src="...">,
or <link href="*.scss"> — those all require reading files relative to a
base_path, which only build needs. validate and
migrate/migrate_with_details take a template string directly and never
touch disk, so they're fully available in WASM — the gap is build
alone. What the WASM binding DOES export directly (see
bindings/node/inky.d.ts): transform, transform_with_config,
transform_inline, transform_with_data, transform_hybrid,
to_plain_text, migrate, migrate_with_details, validate,
validate_with_config, version — all filesystem-free, single-call
component/text transforms.
So this port uses two driver paths, chosen per example:
- WASM binding directly (
lib/wasm.mjs) — examples 01, 06, 07. These never needed a layout: 06'svalidateand 07'smigrate/transformInlineoperate on a legacy template and its migrated form that are bare components with no filesystem refs, and neithervalidatenormigrateneedsbuild's filesystem resolution in the first place — exactly the gap above, and exactly why these two land on WASM directly instead of the CLI. inkyCLI subprocess (lib/cli.mjs) — examples 02, 03, 04, 05, 08, 09, 10. These need real<layout>/<include>/<link href="*.scss">resolution (02, 03, 04, 05, 08, and both capstones'emails/trees), or (08 specifically) an option the WASM binding doesn't expose at all (bulletproof_buttons—transform_hybrid()takes no such parameter). Every such example shells out to a realinky build/validate/migrateinvocation rather than faking the API surface, and says so in its own header comment.
Exact command used (see lib/cli.mjs): the prebuilt release binary at
../inky/target/release/inky, if present; otherwise falls back to
cargo run -q -p inky-cli --release --manifest-path ../inky/Cargo.toml --.
Both produce identical output — the fallback just costs a compile the
first time.
The suite never loosens or reinterprets SUITE.md's required output
markers to make them CLI-shaped: the markers are about the resulting files
in dist/NN-name/, and a CLI invocation produces the same files a library
call would, because both call the same inky-core pipeline underneath.
- Node.js >= 18
- A Rust toolchain, to build
libinky's CLI and WASM artifacts:(this repo must be checked out as a sibling ofcd ../inky cargo build -p inky-cli --release # target/release/inky, used by lib/cli.mjs cd bindings/node wasm-pack build ../../crates/inky-wasm --target nodejs --out-dir . --out-name inky
inky/—lib/wasm.mjsandlib/cli.mjsboth resolve../inky/...relative paths) nunjucks(installed vianpm install) — used only by example 10, as the closest Jinja-family templating engine to Twig available in the Node ecosystem (Total CMS's own engine is Twig; there's no Twig-for-Node)
cd ../inky && cargo build -p inky-cli --release
cd bindings/node && wasm-pack build ../../crates/inky-wasm --target nodejs --out-dir . --out-name inky
cd ../../../inky-example-node
npm install
npm run examples # runs every examples/*/run.mjs, writes dist/
npm run verify # same, plus greps every output for its required markersnpm run verify prints ok for all ten examples. Output lands in
dist/NN-name/ (gitignored).
| # | Name | Teaches | Path |
|---|---|---|---|
| 01-quickstart | quickstart | The smallest possible thing Inky does: transform() turns a <button> into table markup, no layout or data involved. |
WASM |
| 02-build-pipeline | build-pipeline | The full build pipeline in one call: shared layout + includes + a linked SCSS theme, all resolved and inlined. | CLI |
| 03-data-merge | data-merge | Merging JSON data into a template: variables, a conditional, and a loop rendered as real <tr> rows. |
CLI (--data) |
| 04-theming | theming | Building the identical template twice with a different linked SCSS theme each time. | CLI (stdin + cwd) |
| 05-plain-text | plain-text | Deriving a plain-text alternative alongside the HTML for multipart transactional email. | CLI (--plain-text) |
| 06-validate-gate | validate-gate | Using validate() as a pre-send CI gate: block on errors, let warnings through. |
WASM |
| 07-migrate | migrate | Upgrading a v1 Inky template to v2 syntax programmatically, with a reviewable change report. | WASM (migrateWithDetails + transformInline) |
| 08-outlook-hybrid | outlook-hybrid | Building for Outlook desktop: hybrid column layout, bulletproof VML buttons, <outlook>/<not-outlook> branching. |
CLI |
| 09-transactional | transactional (capstone) | A real three-email transactional set (welcome, receipt, password reset) built through EmailRenderer, a small production-shaped service class. |
CLI (via lib/email-renderer.mjs) |
| 10-twig-cms | twig-cms | Integrating Inky into a Jinja-family-templated CMS (nunjucks standing in for Twig): both valid processing orders, timed, plus the one <raw> rule that makes the fast path safe. |
CLI + nunjucks |
Run any single example directly, e.g. node examples/03-data-merge/run.mjs
— every run.mjs is a self-contained, top-to-bottom tutorial with comments
at each decision point.
If you're wiring Inky into a Jinja-family-templated CMS (Total CMS's own
engine is Twig; this port substitutes nunjucks since there's no Node Twig
binding), start at 09-transactional to see the
production-shaped EmailRenderer wrapper (lib/email-renderer.mjs), then
read 10-twig-cms for the CMS-specific question:
should the templating engine or Inky run first? Both orders are
implemented, timed, and proven to agree — see that example's run.mjs and
emails/newsletter.inky.njk header comment for the full trade-off,
including the one <raw>-plus---no-inline-css rule that makes the
faster, build-once-per-template order safe.
package.json scripts: examples / verify / send; nunjucks dependency
bootstrap.mjs dist-dir helper (inkyExample()) shared by every run.mjs
lib/wasm.mjs thin wrapper around the WASM binding (../inky/bindings/node)
lib/cli.mjs subprocess wrapper around the inky CLI (build/validate/migrate)
lib/email-renderer.mjs EmailRenderer — Node port of src/EmailRenderer.php (example 09)
shared/ brand layout, includes, SCSS themes used by examples 02-05 and 08
examples/NN-name/ one directory per example: run.mjs (tutorial) + verify.mjs (smoke test)
dist/ build output (generated, gitignored)
run-all.mjs runs every example (npm run examples / verify)
send.mjs multipart send demo reading example 05's output
Examples 09 and 10 each ship their own self-contained emails/ tree
(emails/layouts/, emails/themes/, emails/includes/) instead of
referencing shared/ — see SUITE.md's "two fixture conventions" for why.
- 07-migrate's "build the migrated template" step uses WASM's
transformInline()instead of shelling out to the CLI, sincelegacy-v1.inky(and its migrated v2 form) never references a shared layout/theme — proving it "still builds cleanly" needs no filesystem access here, unlike the general case. - 08-outlook-hybrid uses the CLI even though it sits in the same
"05–08" grouping the porting brief associated with the WASM path — two
independent reasons: it needs the shared layout/theme (filesystem), and
bulletproof_buttonshas no WASM-exposed equivalent at all (transform_hybrid()takes no such parameter). See that example'srun.mjsheader comment. - 10-twig-cms substitutes nunjucks for Twig (no Twig binding exists
for Node); the template file is
newsletter.inky.njk, notnewsletter.inky.twig. The lesson (Order A vs. Order B,<raw>as load-bearing rather than defense-in-depth,--no-inline-css) is unchanged — only the templating engine playing "the CMS's own engine" role differs. Nunjucks' syntax for everything this template uses ({{ }}, {% if %}, {% for %},{%- -%}whitespace control,{# #}comments, custom filters) is Jinja-compatible with Twig, so the template reads the same either way.
../inky-example-php/SUITE.md— the language-neutral porting spec (the Stage C contract) this suite implements- Getting Started
- Component Reference
- Language Bindings