Getting Started

Installation

Homebrew (macOS/Linux)

brew install foundation/inky/inky

npm

npm install -g inky-wasm

Cargo (from source)

cargo install inky-cli

Direct download

curl -fsSL https://get.inky.email/install.sh | sh

Your First Email

1. Scaffold a project

inky init my-email
cd my-email

This creates:

my-email/
├── inky.config.json
├── src/
│ ├── layouts/
│ │ └── default.html
│ ├── styles/
│ │ └── theme.scss
│ ├── partials/
│ │ ├── header.inky
│ │ └── footer.inky
│ └── emails/
│ └── welcome.inky
└── dist/

2. Edit your template

<!-- src/emails/welcome.inky -->
<layout src="../layouts/default.html" title="Welcome!" preheader="Thanks for joining.">
<container> <row> <column sm="12" lg="12"> <h1>Welcome!</h1> <p>We're glad you're here.</p> <button href="https://example.com/get-started">Get Started</button> </column> </row>
</container>

3. Build

inky build

Output goes to dist/. That's it.

CLI Commands

inky build

Transform .inky and .html files into email-ready HTML.

# Single file (auto-outputs .html alongside .inky)
inky build email.inky # Single file to specific output
inky build email.inky -o output.html # Directory to directory
inky build src/ -o dist/ # Pipe from stdin
echo '<button href="#">Click</button>' | inky build # Skip CSS inlining (on by default)
inky build email.inky --no-inline-css # Skip framework CSS injection
inky build email.inky --no-framework-css # Custom column count (default: 12)
inky build email.inky --columns 16 # Strict mode -- exit 1 on warnings
inky build src/ -o dist/ --strict # Hybrid output (div + MSO ghost tables)
inky build email.inky --hybrid # Generate plain text version alongside HTML
inky build src/ -o dist/ --plain-text # Use per-template data files (data/welcome.json for src/welcome.inky)
inky build src/ -o dist/ --data data/ # VML bulletproof buttons for Outlook
inky build email.inky --bulletproof-buttons

inky watch

Rebuild automatically on file changes.

inky watch src/emails -o dist

Watches all .inky and .html files, plus any referenced partials and layouts. When a partial or layout changes, all templates rebuild. When a single template changes, only that file rebuilds.

inky validate

Check templates for common email issues.

inky validate email.inky
inky validate src/
Rule Severity What it checks
v1-syntax warning Deprecated v1 syntax
missing-alt warning Images without alt text
generic-alt warning Generic alt text like "image", "logo", or single character
button-no-href error Buttons without href
empty-link error/warning Empty href (error) or placeholder # href (warning)
insecure-link warning Links using http:// instead of https://
bad-shortlink warning URL shorteners that get blocked (bit.ly, youtu.be, t.co, etc.)
mailto-in-button warning mailto: href on a <button> component
missing-container warning No <container> element
missing-preheader warning No preheader/preview text
gmail-clipping warning/error HTML approaching or exceeding Gmail's 102KB clip limit
style-block-too-large warning <style> > 8KB (Gmail strips entire block)
img-no-width warning Images without width (breaks Outlook)
deep-nesting warning Tables nested > 5 levels
low-contrast warning Text/background color fails WCAG AA contrast ratio
outlook-unsupported-css warning CSS grid, flexbox, or border-radius (Outlook ignores)
gmail-strips-class warning Class names with . or : that Gmail strips
spam-all-caps warning Over 20% of text is ALL CAPS
spam-exclamation warning Three or more consecutive exclamation marks
spam-image-heavy warning High image-to-text ratio
spam-missing-unsubscribe warning No unsubscribe link found
spam-suspicious-phrases warning 3+ common spam trigger phrases detected

Exit codes: 0 success, 1 errors, 2 warnings (with --strict).

inky serve

Start a local dev server with live preview and auto-reload.

inky serve src/emails
inky serve src/emails --port 8080
inky serve src/emails --data data.json

Opens an index page at http://localhost:3000 listing all templates. Click any template to preview the rendered output. Edits to source files or data automatically trigger a browser reload.

inky spam-check

Check templates for common spam triggers.

inky spam-check email.inky
inky spam-check src/

Checks for ALL CAPS text, excessive exclamation marks, high image-to-text ratio, missing unsubscribe link, and common spam trigger phrases. Exit code 1 if any issues found.

inky migrate

Convert v1 syntax to v2. See the Migration Guide.

inky migrate email.inky # preview to stdout
inky migrate src/ -o migrated/ # directory to directory
inky migrate src/ --in-place # rewrite files in-place

inky init

Scaffold a new project.

inky init my-project

CSS

Framework CSS

Inky includes a built-in SCSS framework for responsive email styles. It is injected automatically during build. Override variables in your layout (see the full Style Reference for all available variables):

<!-- Inline SCSS overrides -->
<style type="text/scss">
$primary-color: #ff6600;
$global-font-family: Georgia, serif;
</style> <!-- Or link to an external file -->
<link rel="stylesheet" href="theme.scss">

Disable with --no-framework-css.

CSS Inlining

CSS inlining is on by default. It moves <style> blocks and <link> stylesheets into inline style attributes. Media queries and at-rules that can't be inlined are preserved in a <style> block at the end of <body>.

# Default behavior: transform + inline
inky build email.inky # Skip inlining
inky build email.inky --no-inline-css

Layouts and Includes

Layouts

A layout wraps your emails in shared HTML. Use <yield> where content should go:

<!-- src/layouts/default.html -->
<!DOCTYPE html>
<html>
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width"> <title>$title|$</title>
</head>
<body> <span class="preheader">$preheader|$</span> <table class="body"> <tr><td class="center" align="center" valign="top"> <center><yield></center> </td></tr> </table>
</body>
</html>

Reference it from your email:

<layout src="../layouts/default.html" title="Hello" preheader="Quick update">
<container> <row><column>Content here</column></row>
</container>

Includes (Partials)

<include src="../partials/header.inky" logo="https://example.com/logo.png"> <container> <row><column>Email body</column></row>
</container> <include src="../partials/footer.inky">

Partials can include other partials (max depth: 10).

Custom Components

Create reusable components with the ink- prefix. Any tag like <ink-NAME> resolves to the file components/NAME.inky in your project.

Define a component (src/components/cta.inky):

<row> <column sm="12" lg="12"> <center> <button href="$href$" class="$color|primary$">$text|Learn More$</button> </center> <yield> </column>
</row>

Use it in your email:

<ink-cta href="https://example.com" text="Get Started"> <p>Extra content below the button</p>
</ink-cta>
  • Attributes become template variables ($href$, $text$, $color$)
  • Inner content replaces <yield> in the component
  • Self-closing works too: <ink-cta href="https://example.com" />
  • Components can nest inside other components
  • Variable defaults use the $name|default$ syntax
  • Max nesting depth: 10 (prevents circular references)

Configure the components directory in inky.config.json:

{ "components": "src/components"
}

The default directory is components relative to the input file.

Template Variables

Pass variables as attributes on <layout> and <include> tags. Use $name$ placeholders inside the referenced file:

$title$ <!-- required, left as-is if not provided -->
$title|My Email$ <!-- falls back to "My Email" -->
$preheader|$ <!-- falls back to empty string -->

Data Merging

Inky can merge JSON data into your templates using --data:

inky build email.inky --data data.json
{"user": {"name": "Alice"}, "cta_url": "https://example.com"}
<button href="{{ cta_url }}">Hello {{ user.name }}!</button>

This is off by default — without --data, merge tags pass through untouched. See the full Data Merging guide.

Template-Friendly

Inky auto-detects and preserves common template syntaxes. No <raw> tags needed:

Syntax Languages
{{ variable }} Handlebars, Mustache, Jinja2, Twig, Blade
<%= expression %> ERB, EJS
<% code %> ERB, EJS, ASP
{% tag %} Jinja2, Twig, Nunjucks, Django
${expression} ES6 template literals
*\|MERGE_TAG\|* Mailchimp
%%variable%% Salesforce Marketing Cloud

Configuration File

Place inky.config.json in your project root:

{ "src": "src/emails", "dist": "dist", "columns": 12, "data": "data.json", "data_dir": "data", "hybrid": false, "plain_text": false, "bulletproof_buttons": false
}

Optional fields:
- data — merge all templates with a single JSON data file (see Data Merging)
- data_dir — directory of per-template JSON data files (data/welcome.json pairs with src/welcome.inky)
- hybrid — use hybrid <div> + MSO ghost table output (see Hybrid Output)
- plain_text — generate .txt plain text version alongside each HTML file
- bulletproof_buttons — generate VML bulletproof buttons for Outlook on all <button> components

With this in place, just run inky build or inky watch with no arguments.

CLI flags always override config file values.