Language Bindings

Inky provides official bindings for Node.js, PHP, Python, and Ruby. All bindings expose the same core API surface.

For complete working examples with build scripts and email sending, see:
Node.js |
PHP |
Python |
Ruby |
Go

Common API

Every binding provides these functions:

Function Description
transform(html, columns?) Transform Inky HTML into email-safe table markup
transformInline(html) Transform and inline CSS from <style> blocks
transformWithData(html, dataJson) Transform with JSON data merge, then inline CSS
transformHybrid(html) Transform using hybrid output (div + MSO ghost tables)
toPlainText(html) Convert HTML to plain text for multipart email
migrate(html) Migrate v1 syntax to v2 (returns HTML string)
migrateWithDetails(html) Migrate v1 syntax, returns {html, changes[]}
validate(html) Validate template, returns array of diagnostics
version() Get the Inky engine version

Diagnostics from validate() are objects/dicts with severity ("warning" or "error"), rule, and message fields.


Node.js

Package: inky-wasm on npm
Engine: WASM (compiled from Rust via wasm-bindgen)
Requires: Node.js (any recent version)

Install

npm install inky-wasm

API

const inky = require("inky-wasm"); // Transform
const html = inky.transform('<button href="#">Click</button>');
const html16 = inky.transform('<row><column>Wide</column></row>', { columns: 16 }); // Transform + inline CSS
const inlined = inky.transformInline(` <style>.button { background: blue; }</style> <button href="#">Click</button>
`); // Transform with data merge
const merged = inky.transformWithData( '<button href="{{ url }}">{{ text }}</button>', JSON.stringify({ url: "https://example.com", text: "Click" })
); // Migrate v1 to v2
const migrated = inky.migrate('<columns large="6">Content</columns>'); // Migrate with change details
const result = inky.migrateWithDetails('<columns large="6">Content</columns>');
// result.html => '<column lg="6">Content</column>'
// result.changes => ['<columns> -> <column>', ...] // Validate
const diagnostics = inky.validate('<button>No href</button>');
// [{ severity: "error", rule: "button-no-href", message: "..." }] const diagnostics16 = inky.validate(html, { columns: 16 }); // Version
console.log(inky.version()); // "2.0.0"

TypeScript

Type definitions are included. Key types:

interface TransformOptions { columns?: number; }
interface ValidateOptions { columns?: number; }
interface Diagnostic { severity: "warning" | "error"; rule: string; message: string; }
interface MigrateResult { html: string; changes: string[]; }

PHP

Package: foundation/inky on Packagist
Engine: Native shared library via FFI (or PECL extension)
Requires: PHP >= 8.1

Install

composer require foundation/inky

You also need the libinky shared library available. Build it from source:

cargo build -p inky-ffi --release
# produces target/release/libinky.dylib (macOS) or libinky.so (Linux)

Driver Setup

The PHP package auto-detects the best available driver:

Priority Driver Mechanism Best For
1 PECL Extension ext-inky Shared hosting, production
2 FFI ext-ffi + libinky Local dev, self-managed servers

FFI setup -- enable in php.ini:

# Option A: Enable globally (dev)
ffi.enable = true # Option B: Preload mode (production, more secure)
ffi.enable = preload
opcache.preload = /path/to/vendor/inky/preload.php

Note: ffi.enable is a PHP_INI_SYSTEM directive and cannot be changed with ini_set().

API

use Inky\Inky; // Transform
$html = Inky::transform('<button href="#">Click</button>');
$html = Inky::transform('<row><column>Content</column></row>', columns: 16); // Transform + inline CSS
$html = Inky::transformInline('<style>...</style><button href="#">Click</button>'); // Transform with data merge
$html = Inky::transformWithData( '<button href="{{ url }}">{{ text }}</button>', json_encode(['url' => 'https://example.com', 'text' => 'Click'])
); // Migrate
$html = Inky::migrate('<columns large="6">Content</columns>'); // Migrate with details
$result = Inky::migrateWithDetails('<columns large="6">Content</columns>');
// $result['html'] => '<column lg="6">Content</column>'
// $result['changes'] => ['<columns> -> <column>', ...] // Validate
$diagnostics = Inky::validate('<button>No href</button>');
// [['severity' => 'error', 'rule' => 'button-no-href', 'message' => '...']] // Version
echo Inky::version(); // "2.0.0"

Python

Package: inky-email on PyPI
Engine: Native shared library via ctypes
Requires: Python >= 3.8

Install

pip install inky-email

You also need the libinky shared library. Build from source:

cargo build -p inky-ffi --release

The library searches these paths automatically:
1. target/release/ (development)
2. Bundled with the package
3. /usr/local/lib/
4. /usr/lib/

API

import inky # Transform
html = inky.transform('<button href="#">Click</button>')
html = inky.transform('<row><column>Content</column></row>', columns=16) # Transform + inline CSS
html = inky.transform_inline('<style>...</style><button href="#">Click</button>') # Transform with data merge
html = inky.transform_with_data( '<button href="{{ url }}">{{ text }}</button>', '{"url": "https://example.com", "text": "Click"}'
) # Migrate
html = inky.migrate('<columns large="6">Content</columns>') # Migrate with details
result = inky.migrate_with_details('<columns large="6">Content</columns>')
# result['html'] => '<column lg="6">Content</column>'
# result['changes'] => ['<columns> -> <column>', ...] # Validate
diagnostics = inky.validate('<button>No href</button>')
# [{'severity': 'error', 'rule': 'button-no-href', 'message': '...'}] # Version
print(inky.version()) # "2.0.0"

Note: Python uses snake_case -- transform_inline, migrate_with_details.


Ruby

Package: inky-email on RubyGems
Engine: Native shared library via Fiddle
Requires: Ruby >= 2.7

Install

gem install inky-email

Or in your Gemfile:

gem "inky-email"

You also need the libinky shared library. Build from source:

cargo build -p inky-ffi --release

The library searches these paths automatically:
1. target/release/ (development)
2. Bundled with the gem
3. /usr/local/lib/
4. /usr/lib/

API

require "inky" # Transform
html = Inky.transform('<button href="#">Click</button>')
html = Inky.transform('<row><column>Content</column></row>', columns: 16) # Transform + inline CSS
html = Inky.transform_inline('<style>...</style><button href="#">Click</button>') # Transform with data merge
html = Inky.transform_with_data( '<button href="{{ url }}">{{ text }}</button>', '{"url": "https://example.com", "text": "Click"}'
) # Migrate
html = Inky.migrate('<columns large="6">Content</columns>') # Migrate with details
result = Inky.migrate_with_details('<columns large="6">Content</columns>')
# result[:html] => '<column lg="6">Content</column>'
# result[:changes] => ['<columns> -> <column>', ...] # Validate
diagnostics = Inky.validate('<button>No href</button>')
# [{severity: "error", rule: "button-no-href", message: "..."}] # Version
puts Inky.version # "2.0.0"

Building the Shared Library

PHP, Python, and Ruby bindings all depend on the libinky shared library from inky-ffi. To build it:

cd /path/to/inky
cargo build -p inky-ffi --release

This produces:
- macOS: target/release/libinky.dylib
- Linux: target/release/libinky.so
- Windows: target/release/inky.dll

For production, copy the library to a system path (/usr/local/lib/) or bundle it with your package.

Building the WASM Module

The Node.js binding uses the WASM module from inky-wasm:

cd crates/inky-wasm
wasm-pack build --target nodejs

This generates the .wasm file and JS/TS wrapper in pkg/.