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.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxInline CSS
POST /api/inline
Send HTML with <style> tags and get back email-ready HTML with all CSS inlined.
Request
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)
{
"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:
{
"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
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.
/api/check(API key required · 5MB max)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.
# 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
- 1Release Check —
POST /api/checkruns the same static ruleset as the preview in the Workbench. - 2Release Run —
POST /api/projects/{id}/releasesfreezes your HTML into an immutable snapshot (run_number, verdict pass / warn / block, preflight issues, policy snapshot). - 3Policy —
GET/PUT /api/projects/{id}/policydecides when a run is block and whether approval is required. - 4Approval —
POST /api/projects/{id}/approvalsrecords an approved or rejected decision. - 5Safe Fix —
POST /api/projects/{id}/releases/{runId}/safe-fixproposes 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.
{
"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.
- 1Try the Playground — inline and preflight HTML instantly, no key required.
- 2Go to the Dashboard, enter your email, and click Get New Key — your
sk_live_…key appears on screen. - 3Create a project from the Upload page, or with
POST /api/projectsbelow. - 4Save your first preflight baseline (Pro/Enterprise) so CI fails only on new issues.
- 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/. 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
// 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
# 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 baselineThe 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.