Documentation

Integrate in minutes.

Your website never touches SMTP. It holds two values and calls two endpoints — the service does delivery, spam filtering, and the branded auto-reply.

1The two-value contract

Create a form in your dashboard and copy its ID. Give your site exactly two values:

FORM_API_URL

The service origin — where challenges and submissions go.

FORM_ID

One form's UUID. Each site (or each form) gets its own.

2The copy-paste helper

Works in Astro, Next.js, Nuxt, Vue, Svelte, or a plain <script type="module">.

form-service.js
// form-service.js — drop into any project (framework-agnostic)
const API_URL = import.meta.env.PUBLIC_FORM_API_URL; // or process.env / hardcode
const FORM_ID = import.meta.env.PUBLIC_FORM_ID;      // UUID from your dashboard

async function sha256Hex(text) {
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, "0")).join("");
}

export async function submitForm(fields, { lang = "en" } = {}) {
  // 1. Get a proof-of-work challenge
  const c = await (await fetch(`${API_URL}/api/challenge`)).json();

  // 2. Solve it (costs a bot CPU, costs your user ~a blink)
  let nonce = 0;
  const prefix = "0".repeat(c.difficulty);
  while (!(await sha256Hex(`${c.challenge}:${nonce}`)).startsWith(prefix)) nonce++;

  // 3. Submit the fields
  const res = await fetch(`${API_URL}/api/submit/${FORM_ID}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      ...fields,
      _lang: lang,
      _gotcha: "",              // honeypot — leave empty
      pow_challenge: c.challenge,
      pow_timestamp: c.timestamp,
      pow_nonce: String(nonce),
    }),
  });
  if (!res.ok) throw new Error((await res.json()).error ?? "Submission failed");
  return res.json();
}

3Use it

your-form-handler.js
import { submitForm } from "./form-service";

await submitForm({
  name: "Ada Lovelace",
  email: "ada@example.com",     // used for the branded auto-reply
  message: "I'd like a quote.",
});
  • email field — if present, the submitter receives your branded auto-reply.
  • _lang — "en" or "fr" — selects the auto-reply language.
  • _gotcha — hidden honeypot input. Humans leave it empty; bots fill it and get silently dropped.
  • files — send base64 payloads — they are uploaded to storage and arrive as download links.

4Let your AI do it

Using Claude, Cursor, or Copilot? We publish a machine-readable install file at /llm-install.md — paste this prompt and your AI does the whole integration, correctly, on the first try:

prompt.txt — paste into your AI assistant
Read https://inlett.vercel.app/llm-install.md
and integrate Inlet into this project exactly as it instructs.
My values: FORM_API_URL=https://inlett.vercel.app FORM_ID=<paste yours>

Claude Code users: drop our skill file .claude/skills/inlet/SKILL.md and Claude wires forms, honeypots and webhooks by itself in any project.

5Webhooks (optional)

Add a webhook URL to a form and every stored lead is POSTed to your endpoint as submission.created, signed Stripe-style in the X-Inlet-Signature header — verify it and reject replays in five lines:

verify-webhook.js
// Verify Inlet's webhook signature (Node) — read the RAW body first.
import crypto from "node:crypto";

export function verifyInletWebhook(rawBody, sigHeader, secret, toleranceSec = 300) {
  const p = Object.fromEntries(sigHeader.split(",").map(s => s.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${p.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(p.t)) < toleranceSec;
  return fresh && crypto.timingSafeEqual(Buffer.from(p.v1), Buffer.from(expected));
}

Delivery is fire-and-forget with one retry — a down endpoint can never block or lose a lead. Endpoints must be https.

What happens server-side

Four spam gates. Honeypot, proof-of-work verification, NLP keyword filter, reverse-DNS VPN/cloud blocking. Blocked attempts are logged, never delivered.

Two emails. The owner gets the lead notification; the submitter gets an auto-reply in your client's branding — logo, colors, sender name. SMTP fallback rotation means a failing credential never loses a lead.

Ready to stop wiring email into every site?