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:
<!-- 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> 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:
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>:
<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:
<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:
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:
<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:
/(pk_[A-Za-z0-9_-]+)/subscribe/?(?:[?#].*)?$/ Programmatic API
Usage
Import and instantiate Mailto with your publish key, then call subscribe():
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 GET your endpoint configuration (5-second timeout)
- 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
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:
<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:
<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:
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 } |
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:
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 Endpoint config is fetched — SDK sees that Turnstile is required
- 2 Turnstile script is injected into the page (memoized — only injected once even with multiple forms)
- 3 A challenge token is obtained from Cloudflare
- 4 Token is included in the subscription POST alongside the email data