All notable changes to Maildeno are documented here. Maildeno follows Semantic Versioning. The JavaScript/TypeScript, Python, and PHP SDKs are versioned independently — the JS SDK is currently at v2.1.4, the Python SDK at v2.0.4, and the PHP SDK at v2.0.1.
Entries are grouped by release date. Where multiple SDKs shipped the same change on the same day, they’re listed together under one heading instead of being repeated per SDK; where SDKs shipped different changes on the same day, each gets its own subsection.
2026-07-10
JavaScript/TypeScript v2.1.3 · Python v2.0.3 · PHP v2.0.0
Rendering engine updated (JavaScript/TypeScript, Python)
PHP: Wasm/FFI engine replaced with a native binary — breaking
Changed — breaking
Rendering moved off wasm/FFI entirely. Previous builds rendered through an embedded engine.wasm via PHP FFI bindings to wasmtime (ext-ffi + a ~15–25MB wasmtime shared library per platform). That path — MaildenoEngine, engine.wasm, the wasmPath/libPath config keys, and the lib/ directory — is removed. NativeEngine is now the only engine: no FFI extension, no wasmtime runtime to distribute, and a bundled binary typically well under 1MB per platform.
-
ext-ffiis no longer referenced anywhere incomposer.json. -
symfony/processis now a hardrequire(^6.4 || ^7.0), not asuggest— it’s needed for every render, not an optional integration.
Added
-
NativeEngine— the render engine. Runs a small platform-nativemaildeno-engineexecutable (viasymfony/process), bundled with the package underbin/<platform>/.MaildenoClientresolves and uses it automatically — no configuration required for normal use. -
RenderEngine— the interfaceNativeEngineimplements, soMaildenoClient(and its 3rd constructor argument, for injecting a pre-built or stub engine) isn’t coupled to one concrete implementation. -
MaildenoClientaccepts an optionalenginePathconfig key with an exact binary path, for the rare case of overriding auto-detection (a non-standard binary location, or testing against a stub).
| This lands the same day as the JS/Python engine refresh above — both come out of the same unified cross-platform engine build pipeline described in that entry’s Internal note. PHP simply goes one step further, dropping Wasm entirely in favor of a native binary. |
See PHP Rendering and PHP Caching for current behavior.
2026-07-06
PHP v1.0.0
Initial stable release of the PHP SDK, with full feature parity with the JavaScript/TypeScript SDK (v2.1.2).
Added
-
MaildenoClient— fetches template JSON from the Maildeno API, caches it, and renders via the embedded Wasm engine. Public surface mirrors the JS SDK:render(),renderHtml(),renderReact(),renderMjml(),listCached(),deleteCached(),clearCache(), and the deprecatedinvalidate()alias. -
Caching —
MemoryStore(in-process, TTL, oldest-entry eviction) andDiskStore(one atomic JSON file per template, survives restarts) behind aTemplateCachefacade. Defaults: 5-minute TTL, 50 entries. -
Stale-on-error fallback — when the TTL has expired and the API is unreachable, the last known-good template is used and
RenderResult::$fromStaleCacheis set totrue. -
MaildenoError— single error type with a stringcode, HTTPstatus, and optional validationissues. Status mapping matches the JS SDK (401/403/404/422 → codes; network →NETWORK_ERROR; timeout →TIMEOUT). -
Minify— whitespace compaction forhtml,mjml, andreact-email, a faithful port ofminify.tswith shared golden test vectors. -
MaildenoEngine— FFI-to-wasmtime bridge running the sameengine.wasmas the other SDKs; rendered output is byte-for-byte identical. -
Injectable HTTP transport (
HttpTransport, defaultCurlTransport) and optional engine injection, for custom clients and testing. -
Zero-dependency autoloader and a 97-test framework-free suite.
The MaildenoEngine Wasm/FFI bridge shipped here was replaced four days later — see PHP v2.0.0 above.
|
2026-06-21
JavaScript/TypeScript v2.1.2 · Python v2.0.2
Fixed
Menu component spacing corrected for mobile screens. Left, right, and bottom margins were misaligned on small viewports. Margins are now consistent across breakpoints.
Preheader spacer sequence added for HTML and MJML templates. Preheader text lacked the invisible-character padding sequence needed to prevent email clients from pulling in unwanted body text as preview copy. The standard spacer string (͏ × 7) is now included after the preheader content in both plain HTML and MJML output.
2026-06-12
JavaScript/TypeScript v2.1.1 · Python v2.0.1
Fixed
-
Visibility context now handles non-string values. Context passed as a number or boolean (e.g.
context: { premium: true }in JS,context={"premium": True}in Python) was read as empty, so visibility rules comparing against it never matched and the affected rows were always hidden. Numbers and booleans are now coerced for comparison —2matches"2", andtrue/Truematches"true". String context was unaffected. -
Visibility context keys are now matched case-insensitively. Capitalized or camelCase keys (e.g.
Premium,orderCount) previously failed to resolve. The ruletagmust still match the context key name —isPremiumandpremiumremain distinct.
2026-06-07
JavaScript/TypeScript v2.1.0 · Python v2.0.0
JavaScript/TypeScript v2.1.0
Added
-
Disk cache strategy — set
cache: { type: "disk", path: "…" }to persist template JSON to the local filesystem. Disk-cached entries survive process restarts and are shared across workers on the same filesystem. -
Unified
cacheconfig object —type,path,ttl, andmaxEntriesnow live under a singlecachekey. -
client.listCached()— returns the IDs of all templates currently in the cache. -
client.deleteCached(templateId)— removes a single template from the cache. Replacesinvalidate(). -
CacheConfigtype exported from the package root.
Changed
-
client.clearCache()is now async (was sync). Awaiting it is required in disk mode; memory mode resolves immediately, so existing code that does notawaitstill works in practice. -
client.invalidate(templateId)is now async and deprecated — it delegates todeleteCached()internally, so existing code keeps working.
Removed — breaking
-
cacheTtltop-level config option — moved intocache.ttl. -
cacheMaxEntriestop-level config option — moved intocache.maxEntries.
See JS Caching for the full caching reference.
Python v2.0.0
Changed — breaking
-
Rendering is now local. The SDK fetches template JSON once via
GET /v1/sdk/template/{id}and renders in-process using the embedded Wasm engine. Your merge tags & visibility context never leave your server. -
POST /v1/sdk/renderis no longer called. Existing direct-HTTP integrations keep working until the endpoint is removed (see theDeprecationresponse header). -
RenderResultgains an optionalfrom_stale_cache: bool = Falsefield. Code that reads.output,.target, or.template_idis unaffected. -
invalidate(template_id)is deprecated — usedelete_cached(template_id).
Added
-
wasmtimeruntime dependency — the Wasm engine that runs the embedded renderer (Linux, macOS, and Windows on Python 3.9+). -
engine.wasmshipped inside themaildenopackage, located viaimportlib.resources(works in wheels, editable installs, virtualenvs, Lambda layers, and Docker). -
In-process template cache — template JSON is cached after the first fetch; subsequent renders to the same
template_idhave zero network overhead. -
Stale-on-error fallback — if the TTL expires and the server is unreachable, the SDK renders from the last known-good cached copy and sets
result.from_stale_cache = Truerather than raising. -
cache=constructor parameter — memory (default) or disk. -
list_cached(),delete_cached(template_id), andclear_cache()cache-management methods. -
CacheConfigandTemplateJsonTypedDicts exported from the package root. -
Thread-safe Wasm singleton — loaded lazily on the first render and reused for the process lifetime, with a lock for exactly-once initialisation.
-
Async Wasm via thread executor —
AsyncMaildenoClientdispatches renders to asyncio’s default thread-pool executor so the event loop is never blocked.
See Python Caching for the full caching reference.
2026-06-05
JavaScript/TypeScript v2.0.0
Changed — breaking
-
Rendering is now local. The SDK fetches template JSON once via
GET /v1/sdk/template/{id}and renders in-process using the embedded engine. Your merge tags & visibility context never leave your server. -
RenderResultgains an optionalfromStaleCache?: booleanfield. Code that destructures{ templateId, target, output }is unaffected. -
POST /v1/sdk/renderis deprecated and no longer called by the SDK. Existing direct-HTTP integrations keep working until the endpoint is removed (see theDeprecationresponse header).
Added
-
In-process template cache — subsequent calls to the same
templateIdrender with zero network overhead. -
Stale-on-error fallback — renders from the last known-good cached copy and sets
result.fromStaleCache = truewhen the server is unreachable. -
cacheTtlandcacheMaxEntriesconfig options (both deprecated in v2.1 — use thecacheobject). -
client.invalidate(templateId)andclient.clearCache()(invalidate()deprecated in v2.1 — usedeleteCached()). -
Output minification.
-
TemplateJsontype exported from the package root.
2026-05-29
JavaScript/TypeScript v1.0.0 · Python v1.0.0
JavaScript/TypeScript SDK
-
Initial stable release of
maildenonpm package -
MaildenoClientwithrender(),renderHtml(),renderReact(),renderMjml()methods -
Full TypeScript type exports
-
Structured
MaildenoErrorwithcode,message,status, andissuesproperties
Python SDK
-
Initial stable release of
maildenoPyPI package -
MaildenoClient(sync,httpx) andAsyncMaildenoClient(async,httpx.AsyncClient) -
Both context-manager and long-lived client patterns supported
-
Bring-your-own
httpxclient for advanced transport customisation -
Full PEP 561 type stubs (
mypy/pyrightcompatible)