SDK Documentation

Installation & Setup Guide

Add email subscriptions to any website in minutes — no backend, no database, no server required.

Introduction

The @mailto.foo/sdk is a headless email subscription library with two independent entry points:

1 CDN Script Tag

Drop in one <script> tag and point your existing HTML form at the API. No JavaScript required from you — the SDK handles everything automatically.

2 npm / ESM

Install the package and call mailto.subscribe() programmatically from React, Vue, Svelte, or any JS framework.

Both modes share the same underlying flow: fetch your endpoint config, optionally acquire a Turnstile token for bot protection, then POST the subscription data.

Submissions aren't limited to just an email address — any extra fields you add (name, message, plan, source, etc.) are captured automatically and show up alongside the subscriber in your dashboard. See Extra Fields.

Quick Start

The fastest way to collect subscribers — two steps, zero backend:

index.html
<!-- 1. Load the SDK -->
<script src="https://cdn.jsdelivr.net/npm/@mailto.foo/sdk@1.5.0/dist/sdk.min.js"></script>

<!-- 2. Point your form at your publish key -->
<form action="https://api.mailto.foo/pk_YOUR_PUBLISH_KEY/subscribe" data-verbose>
  <input type="email" name="email" placeholder="you@example.com" required>
  <button type="submit">Subscribe</button>
</form>
Replace pk_YOUR_PUBLISH_KEY with the key from your mailto.foo dashboard.

AI Setup

AI-Assisted Setup

If you're using an AI coding agent (Claude Code, Cursor, or anything else that reads AGENTS.md), you can skip reading the rest of this page. Run the SDK's init command from your project root:

bash
npx @mailto.foo/sdk init

This writes everything your agent needs to know about the SDK into AGENTS.md at your project root — creating the file if it doesn't exist, or appending to it if it does. From there, just ask your agent to add a subscribe or contact form, and it will wire up your publish key, custom events, and extra fields beyond email.


CDN

Installation

Add the SDK to any HTML page with a single script tag. Place it in the <head> or just before </body>:

html
<script src="https://cdn.jsdelivr.net/npm/@mailto.foo/sdk@1.5.0/dist/sdk.min.js"></script>

The CDN bundle is self-contained and runs automatically on page load. It scans for forms and wires up any that match the subscribe URL pattern — no additional JavaScript needed from you.

Latest vs pinned version

The example above pins to @1.5.0. To always get the latest minor/patch release, use @1 instead:

html
<script src="https://cdn.jsdelivr.net/npm/@mailto.foo/sdk@1/dist/sdk.min.js"></script>

npm / ESM

Installation

Install with your package manager of choice:

bash
npm install @mailto.foo/sdk
# or
yarn add @mailto.foo/sdk
# or
pnpm add @mailto.foo/sdk

The package ships with full TypeScript types and an ESM build. It works in any modern JS environment: React, Vue, Svelte, SolidJS, Astro, Next.js, Nuxt, and more.


CDN Forms

Usage

After loading the CDN script, the SDK automatically detects any <form> whose action URL contains your publish key followed by /subscribe:

index.html
<form action="https://api.mailto.foo/pk_YOUR_PUBLISH_KEY/subscribe">
  <input type="email" name="email" placeholder="you@example.com" required>
  <button type="submit">Subscribe</button>
</form>

By default the SDK operates silently — it intercepts the submission, posts to the API, and fires custom events (see Custom Events). No visible UI change happens unless you add data-verbose.

URL pattern matched

The SDK looks for this pattern in the form's action attribute:

regex
/(pk_[A-Za-z0-9_-]+)/subscribe/?(?:[?#].*)?$/

Programmatic API

Usage

Import and instantiate Mailto with your publish key, then call subscribe():

js
import { Mailto } from '@mailto.foo/sdk'

const mailto = new Mailto('pk_YOUR_PUBLISH_KEY')

async function handleSubmit(email) {
  try {
    await mailto.subscribe({ email })
    console.log('Subscribed!')
  } catch (err) {
    // err is a plain Error — err.message contains the API response
    console.error('Failed:', err.message)
  }
}

Each call to subscribe() performs two network requests:

  1. 1 GET your endpoint configuration (5-second timeout)
  2. 2 POST the subscription data to the API

If bot protection is enabled, a third Turnstile request happens between steps 1 and 2 (see Bot Protection).

TypeScript example

ts
import { Mailto } from '@mailto.foo/sdk'

const mailto = new Mailto('pk_YOUR_PUBLISH_KEY')

async function subscribe(email: string): Promise<void> {
  await mailto.subscribe({ email })
}

Verbose Mode

Usage

Add the data-verbose attribute to your form to enable automatic UI management:

index.html
<form action="https://api.mailto.foo/pk_YOUR_PUBLISH_KEY/subscribe" data-verbose>
  <input type="email" name="email" placeholder="you@example.com" required>
  <button type="submit">Subscribe</button>
</form>

When data-verbose is present, the SDK will:

  • Create status elements automatically if they are not already present in the form
  • Disable the submit button during the submission to prevent double-clicks
  • Display a success message when the subscription completes
  • Display an inline error message when the subscription fails

Without data-verbose, the SDK runs silently and only fires the custom events — giving you full control over the UI.

Extra Fields

Usage

Any form fields beyond email are automatically captured and passed to the API as arbitrary string key-value pairs:

index.html
<form action="https://api.mailto.foo/pk_YOUR_PUBLISH_KEY/subscribe" data-verbose>
  <input type="email" name="email" placeholder="Email address" required>
  <input type="text"  name="first_name" placeholder="First name">
  <select name="plan">
    <option value="free">Free tier</option>
    <option value="pro">Pro</option>
  </select>
  <button type="submit">Subscribe</button>
</form>

The same applies when using the programmatic API — pass any additional fields as properties on the object:

js
await mailto.subscribe({
  email: 'user@example.com',
  first_name: 'Ada',
  plan: 'pro',
})

Custom Events

Events & Errors

The CDN auto-enhancement fires three custom events directly on the form element during the subscription lifecycle:

Event When detail
mailto:submitting Submission started
mailto:success Subscription succeeded
mailto:error Submission failed { message: string }
js
const form = document.querySelector('[action*="/subscribe"]')

form.addEventListener('mailto:submitting', () => {
  console.log('Submitting…')
})

form.addEventListener('mailto:success', () => {
  console.log('Subscribed!')
})

form.addEventListener('mailto:error', (event) => {
  console.error(event.detail.message)
})

Error Handling

Events & Errors

The subscribe() method throws a plain Error when something goes wrong. Always wrap calls in a try/catch:

js
import { Mailto } from '@mailto.foo/sdk'

const mailto = new Mailto('pk_YOUR_PUBLISH_KEY')

try {
  await mailto.subscribe({ email })
} catch (err) {
  // err.message contains the error from the API
  showErrorBanner(err.message)
}

For CDN forms, listen for the mailto:error event instead (see Custom Events). The event's detail.message property holds the same error string.


Bot Protection

Advanced

Cloudflare Turnstile bot protection can be enabled per endpoint from your mailto.foo dashboard. When active, the SDK handles the entire Turnstile flow automatically — no changes to your code are needed.

What happens under the hood when bot protection is on:

  1. 1 Endpoint config is fetched — SDK sees that Turnstile is required
  2. 2 Turnstile script is injected into the page (memoized — only injected once even with multiple forms)
  3. 3 A challenge token is obtained from Cloudflare
  4. 4 Token is included in the subscription POST alongside the email data
When bot protection is enabled, each subscription involves three network requests total: config fetch → Turnstile challenge → subscribe POST.