Developer guide

API Documentation

An email build-quality pipeline: inline your CSS, run static preflight, and gate CI on new issues. Works with any email service.

Need business-specific checks? Preview totals, roles, expiry text and links, then save the rules in your project Workbench. Optional contract.contentChecks uses id, selector, match (equals, contains, excludes, exists or absent), value for text matches, and optional scenarios. Named scenarios become required matrix coverage. Single-HTML releases cannot satisfy scoped rules; use a matrix. API checks with options.projectId return a separate contract result; release checks block when a saved requirement fails.

Try the receipt, invitation and password-reset workflows on the matrix page. Edit business requirements and download a project-rules bundle to import into Workbench. Import replaces the complete policy: review approval and client settings first. Findings omit matched text; rule snapshots and policy exports retain configured expected text. Use synthetic fixtures.

Try a template scenario matrix for translations, long names and conditional content. Choose rendered HTML files to build it without writing JSON, edit the scenario IDs, then download the input manifest for reuse. Input manifests include your HTML; evidence reports contain findings and fingerprints. POST /api/projects/:id/matrix accepts a version 1 matrix with templateId, optional full commitSha, and scenarios: [{ id, html }]. Pro/Enterprise saves one immutable release; each scenario consumes one check. Save policy.requiredScenarios to block missing cases and single-HTML shortcuts. The free /api/playground-matrix endpoint previews up to 3 scenarios without saving. SDK: releaseMatrix(projectId, matrix). GitHub Action: matrix-file plus project-id. Rendering happens in your own CI; static checks do not prove visual layout or delivery.

Check target email clients against eight CSS feature families from pinned Can I email data. Each finding includes the recorded client version, test date and source conditions. Save policy.compatibility with version: 1, clients and failOn: never, unsupported or risk (Pro/Enterprise). Unmapped features remain unassessed; this is static evidence, not current-client rendering. POST /api/playground-compatibility previews without an account.

Keep rules portable: GET /api/projects/:id/policy-transfer exports saved rules; POST imports a complete version 1 bundle and replaces the policy (Pro/Enterprise). Download readable release evidence at GET /api/projects/:id/releases/:runId/report or from Workbench history. HTML reports omit the original email body and API key; they are unsigned copies with approval state at export. Matrix previews and the Action also export HTML reports.

Authentication

Project and account API requests require a Bearer token in the Authorization header. The browser preview endpoints listed below work without a key. Get your free API key from the Dashboard.

header
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Inline CSS

POST /api/inline

Send HTML with <style> tags and get back email-ready HTML with all CSS inlined.

Request

curl
curl -X POST https://inlinerapi.netlify.app/api/inline \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"html":"<style>.red{color:red}</style><p class=\"red\">Hello</p>"}'

Success Response (200)

json
{
  "success": true,
  "result": "<p style=\"color:red\">Hello</p>",
  "meta": {
    "warnings": [],
    "processing_time_ms": 12,
    "quota_remaining": null,
    "plan": "free",
    "usage_this_month": 12
  }
}

Error Responses

401Missing or invalid API key

{ "error": "invalid_api_key", "message": "..." }

429Rate limit reached

{ "error": "rate_limited", "message": "..." }

400Invalid HTML or missing field

{ "error": "missing_html", "message": "Missing \"html\" field" }

Advanced Options

Pass an options object to control Juice's behavior:

json
{
  "html": "<style>...</style><p>...</p>",
  "options": {
    "preserveMediaQueries": true,
    "preserveFontFaces": true,
    "preserveImportant": true,
    "removeStyleTags": true,
    "applyWidthAttributes": true,
    "inlinePseudoElements": true
  }
}

All options default to true. Omit the entire options field for sensible defaults.

Node.js Example

node
const KEY = process.env.INLINER_KEY;

const res = await fetch('https://inlinerapi.netlify.app/api/inline', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: '<style>h1{color:blue}</style><h1>Hello</h1>',
  }),
});

const data = await res.json();
console.log(data.result);
// → <h1 style="color:blue">Hello</h1>

Preflight Check

Static source-code analysis of email HTML (ruleset email-static-v1): flags scripts, forms, missing alt text, Gmail clipping risk, insecure links, and more. It is not a real email-client screenshot or a deliverability score, and it never fetches external URLs from your HTML — everything runs as local static parsing.

POST/api/check(API key required · 5MB max)
curl
curl -X POST https://inlinerapi.netlify.app/api/check \
  -H "Authorization: Bearer $INLINER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html":"<style>h1{color:blue}</style><h1>Hello</h1>"}'

// → {
//   ruleset: "email-static-v1",
//   htmlHash: "sha256:…",
//   status: "pass" | "warn" | "fail",
//   summary: { error, warning, info, total, truncated },
//   metrics: { bytes, elements, links, images, styleTags, inlineStyleAttributes },
//   issues: [ { code, severity, message, selector, help } ],   // ≤200, stable order
//   meta: { request_id, usage: { plan, used, limit, remaining, reset_at } }
// }

Errors: 401 invalid key · 400 invalid_json / missing_html / parse_failed · 413 html_too_large · 429 quota_exceeded (with used/limit/resetAt) or rate_limited · 503 temporarily unavailable.

Anonymous variant for quick tests: POST /api/playground-check (no key, 500KB max, 10 requests/minute per IP, returns the same result shape without usage).

Preflight Baselines — CI fails only on new issues (Pro/Enterprise)

Save a baseline of a project's issues once, then pass {"options":{"projectId":"…"}} to /api/check. The response gains a comparison object classifying every issue as new, resolved, or persistent vs the baseline — so your CI gate can fail only on new problems. Baselines store issue metadata only (codes, severities, normalized selectors) — never your HTML content. Free plans can read and delete baselines but cannot create or refresh them.

curl
# 1) Create / atomically refresh the baseline from current HTML (Pro/Enterprise)
curl -X PUT https://inlinerapi.netlify.app/api/projects/$PROJECT_ID/preflight-baseline \
  -H "Authorization: Bearer $INLINER_KEY" -H "Content-Type: application/json" \
  -d '{"html":"<html>…</html>"}'

# 2) Read it back
curl https://inlinerapi.netlify.app/api/projects/$PROJECT_ID/preflight-baseline -H "Authorization: Bearer $INLINER_KEY"

# 3) Delete it (any plan; idempotent — second call returns 204)
curl -X DELETE https://inlinerapi.netlify.app/api/projects/$PROJECT_ID/preflight-baseline -H "Authorization: Bearer $INLINER_KEY"

# 4) Check with comparison
curl -X POST https://inlinerapi.netlify.app/api/check \
  -H "Authorization: Bearer $INLINER_KEY" -H "Content-Type: application/json" \
  -d '{"html":"<html>…</html>","options":{"projectId":"'$PROJECT_ID'"}}'

// → comparison: {
//   baselineId: "bl_…", compatible: true,
//   newIssues: […], resolvedIssues: […], persistentIssues: […],
//   summary: { new: {error,warning,info,total}, resolved: {…}, persistent: {…} }
// }
// Without a baseline yet: compatible:false, reason:"baseline_not_found" (check still succeeds)

Release Guard

Gate a release on the preflight verdict, review it, and apply one-click fixes — all against an immutable snapshot of what you're about to send. Release Guard is available on the project list in the Dashboard and through the API below.

Workflow

  1. 1Release Check — POST /api/check runs the same static ruleset as the preview in the Workbench.
  2. 2Release Run — POST /api/projects/{id}/releases freezes your HTML into an immutable snapshot (run_number, verdict pass / warn / block, preflight issues, policy snapshot).
  3. 3Policy — GET/PUT /api/projects/{id}/policy decides when a run is block and whether approval is required.
  4. 4Approval — POST /api/projects/{id}/approvals records an approved or rejected decision.
  5. 5Safe Fix — POST /api/projects/{id}/releases/{runId}/safe-fix proposes and (Pro/Enterprise) applies fixes to your HTML.

POST /api/projects/{id}/releases — create an immutable run. Requires a trigger (web / ci / sdk / manual) and returns the frozen snapshot with run_number and verdict. Release runs have their own monthly allowance per customer (Free: 3 web-triggered runs; Pro: 500; Enterprise: 5,000) — independent of the preflight quota. On Free, ci and sdk triggers return 402 upgrade_required, and the run list shows only the 3 most recent runs (history is never deleted). Runs are immutable: they never change after creation, and Safe Fix never rewrites a run.

Policy

GET /api/projects/{id}/policy returns { failOn, requireApproval, approvers } (defaults: failOn: "error", requireApproval: false, approvers: []). PUT patches only the fields you send. failOn ∈ error | warning | never. Reading the policy is available on every plan; PUT (customizing it) is Pro/Enterprise — Free returns 402 upgrade_required. The policy is frozen into each run as policy_snapshot — changing the policy later never rewrites the verdict of a past run.

Approval

POST /api/projects/{id}/approvals with { run_id, decision: "approved" | "rejected", note? }. Authenticate with your API key (project owner) or the project's share token via X-Share-Token. Approving is a Pro/Enterprise capability of the project owner's plan — Free projects return 402 upgrade_required. Each run can be decided once — a second decision returns 409 approval_already_final.

Safe Fix

POST /api/projects/{id}/releases/{runId}/safe-fix with { action: "propose" } returns suggested fixes (available on every plan). apply and undo are Pro/Enterprise features — Free returns 402 upgrade_required with a propose-only result. Fixes are applied to your HTML, never to the immutable run.

Browser preview ≠ real email clients

The preview in the Workbench is a screenshot rendered by a headless browser of your current HTML — it is not a real Gmail or Outlook render, and it is not a deliverability score. The Release Guard gate is based on static checks; test final HTML in your own real mailboxes before sending at scale.

Usage & Plan

GET /api/usage (Bearer) returns your current plan and month-to-date usage for screenshots, release checks, release runs, and inline calls — the same numbers the server enforces. release_runs is a separate monthly allowance (Free: 3 web-triggered runs; Pro: 500; Enterprise: 5,000) with its ownused / limit / remaining /period — independent of the preflight quota.

json
{
  "success": true,
  "usage": {
    "plan": "pro",
    "month": "2026-08",
    "screenshot": { "used": 37, "limit": 500, "resetAt": "2026-09-01T00:00:00.000Z" },
    "preflight": { "used": 8, "limit": 10000, "resetAt": "2026-09-01T00:00:00.000Z" },
    "release_runs": {
      "used": 3,
      "limit": 500,
      "remaining": 497,
      "period": "monthly",
      "month": "2026-08",
      "resetAt": "2026-09-01T00:00:00.000Z"
    },
    "inline": { "used": 4, "limit": null, "resetAt": "2026-09-01T00:00:00.000Z" }
  }
}

The response never contains your API key, your email address, or any HTML body.

Quotas & Limits

Inline CSS
unlimited on every plan (API key required)
Free
5 screenshots/month, watermarked, reset on 1st of each month (UTC)
Pro
500 screenshots/month, no watermark, reset on 1st of each month (UTC)
Enterprise
5,000 screenshots/month, no watermark, reset on 1st of each month (UTC)
Preflight checks — Free
100/month (UTC reset)
Preflight checks — Pro
10,000/month (UTC reset)
Preflight checks — Enterprise
100,000/month (UTC reset)
Release runs — Free
3 web-triggered runs/month; CI/SDK triggers not included; latest 3 runs visible
Release runs — Pro
500/month (UTC reset), all triggers
Release runs — Enterprise
5,000/month (UTC reset), all triggers
IP rate limit
60 requests/minute per IP
Playground (no key)
10 requests/minute per IP

Exceeding your screenshot quota returns 402 Screenshot Quota Exceeded. Exceeding your preflight quota returns 429 quota_exceeded with used/limit/resetAt. Upgrade your plan for more.

Getting Started — the launch path

Start with the free browser checks, then create a key and project. Baselines and CI gates require Pro or Enterprise; the Dashboard tracks the setup steps available on your plan.

  1. 1Try the Playground — inline and preflight HTML instantly, no key required.
  2. 2Go to the Dashboard, enter your email, and click Get New Key — your sk_live_… key appears on screen.
  3. 3Create a project from the Upload page, or with POST /api/projects below.
  4. 4Save your first preflight baseline (Pro/Enterprise) so CI fails only on new issues.
  5. 5Connect the Node SDK or GitHub Action.

SDK & GitHub Action

Automate inlining, preflight, and baseline comparison from your own code or CI.

For scenario checks, open the matrix builder and expand Automate checks in GitHub. Configure your existing renderer and scenario HTML paths, then download a setup ZIP with the workflow, a Node matrix assembly script and instructions. You can also use an existing matrix-generating script or download a committed snapshot of the current draft.

SDK source: copy the repository's SDK directory into your project as sdk/. Its runtime has no npm dependencies; the example below loads these local files. The GitHub Action below is available from its pinned repository subfolder and uses Node 24.

Node.js SDK

node
// Copy the repository's sdk directory to ./sdk first.
// Save as check-email.cjs. Set INLINER_KEY in your environment, then:
// node check-email.cjs
const { InlinerAPI } = require('./sdk');

async function main() {
  const api = new InlinerAPI({ apiKey: process.env.INLINER_KEY });
  const { result } = await api.inline('<style>.red{color:red}</style><p class="red">Hi</p>');
  const report = await api.check(result);
  console.log(report);
}
main().catch(error => { console.error(error.message); process.exitCode = 1; });

GitHub Action

action
# Node 24 Action, pinned to an immutable repository commit
- uses: awslew/inlinerapi/action@05549f9125b94c41897f77050d41f429a311ac32
  with:
    api-key: ${{ secrets.INLINER_API_KEY }}
    files: 'emails/**/*.html'
    output-dir: 'dist/emails'
    preflight: 'true'
    fail-on: 'error'
    project-id: ${{ vars.INLINER_PROJECT_ID }} # optional: saved project baseline

The Action posts a preflight report to the job summary. Static source checks only — not real email-client screenshots or deliverability scores.

Need help?

Email ababwooohallo@163.com or try the Playground to test without an API key.