Ten runnable, numbered examples showing how to use the Inky
email framework from Go, via the github.com/foundation/inky-go binding —
from the smallest possible transform up to a transactional-email capstone
and a CMS-templating-engine integration.
This is a Stage C port of the reference PHP suite
(inky-example-php): the
same ten examples, against the same required output markers, defined in
that repo's SUITE.md (the language-neutral porting contract). Only driver
code differs; templates, SCSS themes, layouts, includes, and JSON fixtures
are copied verbatim from the PHP repo.
Requires Inky v2. See the main inky repo.
- Go >= 1.21 (developed against 1.25)
- A Rust toolchain, to build the
libinkyshared library:(this repo must be checked out as a sibling ofcd ../inky && cargo build -p inky-ffi --release
inky/andinky-go/— thereplacedirective ingo.modand the dylib lookup below both assume that layout) github.com/foundation/inky-go, pulled in via a localreplacedirective ingo.modpointing at../inky-gogithub.com/flosch/pongo2/v6(pulled in automatically bygo mod download) — used only by example 10
Every go build/go run/go test invocation in this repo needs the
Rust-built libinky dylib on both the linker's and the dynamic loader's
search path:
export CGO_LDFLAGS="-L/Users/joeworkman/Developer/inky/target/release"
export DYLD_LIBRARY_PATH=/Users/joeworkman/Developer/inky/target/release # macOS
# export LD_LIBRARY_PATH=/Users/joeworkman/Developer/inky/target/release # Linuxinky-go's cgo directive links -linky against the default
/usr/local/lib; CGO_LDFLAGS above adds target/release to the search
path ahead of that default so the locally-built dylib is found without
installing it system-wide. cmd/runall spawns every example (and its
verify/) as a go run subprocess; since os/exec inherits the parent
process's environment by default, setting these two variables once, on the
top-level invocation, is sufficient — no per-subprocess wiring needed.
The PHP suite's SUITE.md asks every Stage C port to re-verify two things
in its own binding/runtime:
- Does the local path-dependency mechanism break the dylib lookup?
No. Go's
replacedirective ingo.mod(replace github.com/foundation/inky-go => /Users/joeworkman/Developer/inky-go) is the Go analogue of Composer's symlinked path repository — but unlike that PHP case (which happened to work only because PHP resolves__DIR__through the symlink to the binding's real location before its relative dylib lookup runs), there is no such question to re-verify here at all:inky-go's cgoLDFLAGSare resolved by the linker via theCGO_LDFLAGSenvironment variable at build time, not by walking a relative path computed from the package's own source location. Thereplacedirective works exactly as documented, no special wiring needed. - Does the stability-policy question (PHP's
minimum-stability: dev/prefer-stable: true) have a Go equivalent? No. Go modules have no "stability tag" resolution step the way Composer does — areplacedirective to a local filesystem path is used verbatim regardless of what version string appears in the correspondingrequireline (this repo'sgo.moduses the standardv0.0.0-00010101000000-000000000000placeholder pseudo-version for thatrequire, satisfied entirely by thereplace). Nothing to configure.
cd ../inky && cargo build -p inky-ffi --release # build libinky once
cd ../inky-example-go
export CGO_LDFLAGS="-L$(cd ../inky && pwd)/target/release"
export DYLD_LIBRARY_PATH="$(cd ../inky && pwd)/target/release" # macOS; use LD_LIBRARY_PATH on Linux
go run ./cmd/runall # runs every examples/*/main.go, writes dist/
go run ./cmd/runall -verify # same, plus each example's verify/main.gogo run ./cmd/runall -verify prints NN-name: ok for all ten examples.
Output lands in dist/NN-name/ (gitignored).
| # | Name | Teaches |
|---|---|---|
| 01-quickstart | quickstart | The smallest possible thing Inky does: inky.Transform turns a <button> into table markup, no layout or data involved. |
| 02-build-pipeline | build-pipeline | The full build pipeline in one call: shared layout + includes + a linked SCSS theme, all resolved and inlined. |
| 03-data-merge | data-merge | Merging JSON data into a template: variables, a conditional, and a loop rendered as real <tr> rows. |
| 04-theming | theming | Building the identical template twice with a different linked SCSS theme each time. |
| 05-plain-text | plain-text | Deriving a plain-text alternative alongside the HTML for multipart transactional email. |
| 06-validate-gate | validate-gate | Using inky.Validate as a pre-send CI gate: block on errors, let warnings through. |
| 07-migrate | migrate | Upgrading a v1 Inky template to v2 syntax programmatically, with a reviewable change report. |
| 08-outlook-hybrid | outlook-hybrid | Building for Outlook desktop: hybrid column layout, bulletproof VML buttons, <outlook>/<not-outlook> branching. |
| 09-transactional | transactional (capstone) | A real three-email transactional set (welcome, receipt, password reset) built through emailrenderer.EmailRenderer, a small production-shaped service type. |
| 10-twig-cms | twig-cms | Integrating Inky into a Jinja2-family CMS templating engine (this port uses pongo2, not Go's text/template — see that example's header comment for why): both valid processing orders, timed, plus the one <raw> rule that makes the fast path safe. |
Run any single example directly, e.g. go run ./examples/03-data-merge —
every main.go is a self-contained, top-to-bottom tutorial with comments
at each decision point. Each example also has its own verify/ smoke test,
e.g. go run ./examples/03-data-merge/verify.
If you're wiring Inky into a Twig/Jinja2-family CMS engine, start at
09-transactional to see the production-shaped
emailrenderer.EmailRenderer wrapper (emailrenderer/emailrenderer.go),
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 main.go and
newsletter.inky.pongo2 header comment for the full trade-off, including
the one <raw>-plus-InlineCSS: false rule that makes the faster,
build-once-per-template order safe.
go.mod replace -> ../inky-go; pongo2 dependency
internal/support/ the Go analogue of bootstrap.php: ExampleDir()/Dist() helpers every example uses
emailrenderer/ small production-shaped render/theme/cache wrapper (example 09; the pattern example 10 adapts by hand)
shared/ brand layout, includes, SCSS themes used by examples 01-08
examples/NN-name/ one directory per example: main.go (tutorial) + verify/main.go (smoke test)
examples/09-transactional/emails/ self-contained base-root template tree (see below)
examples/10-twig-cms/emails/ self-contained base-root template tree (see below)
dist/ build output (generated, gitignored)
cmd/runall/ runs every example (go run ./cmd/runall [-verify])
cmd/send/ multipart send demo reading example 05's output
- Examples 01–08 reference shared fixtures (brand layout, includes,
SCSS themes) in
shared/at the repo root, via../../shared/...traversals from each example'sexamples/NN-name/directory (which is passed asbasePathtoinky.Build) — two levels up, not one; see the PHP suite'sSUITE.md"Runtime requirements" §2 for why (basePathis always the original directory passed toinky.Build, never re-derived per included file). This keeps eight independent lessons from each carrying their own copy of the same handful of bytes. - Examples 09 and 10 (the capstones) each ship a self-contained
emails/tree instead — onebasePathcontaining its ownlayouts/,themes/, andincludes/copies, with every internal src/href written root-relative (no../anywhere inside the tree). This models the shape a real CMS integration actually looks like on disk: one directory an app points its renderer at, self-contained, that a deployment can copy or version as a unit. Seeemails/layouts/main.html's own header comment in each capstone for the exact resolution rule.
inky-example-php'sSUITE.md— the language-neutral porting spec this port implements- Getting Started
- Component Reference
- Language Bindings