Add newsletter signup to a site

Any site can collect newsletter subscribers through the Inkform platform's public subscribe API — no database, no email provider, no server code of your own required. This guide covers the framework's <SubscribeForm /> component, which is what the platform itself sends over the wire; if your site isn't on @inkform/framework yet, skip to Without the framework and call the API directly.

This is one guide for every site. Follow it the same way whether you're integrating mlnotes, the blog, or a brand-new project — the API contract never changes per site.

1. Get a project ID

Every subscriber is scoped to a project in the Inkform dashboard. Create (or open) your site's project there and copy its project ID — you'll need it below.

2. Install the framework

npm install @inkform/framework

3. Drop in the form

import { SubscribeForm } from '@inkform/framework/subscribe-form';
import '@inkform/framework/styles.css';
 
<SubscribeForm
  projectId={process.env.NEXT_PUBLIC_INKFORM_PROJECT_ID}
  apiBaseUrl={process.env.NEXT_PUBLIC_INKFORM_PLATFORM_URL ?? 'https://api.inkform.dev'}
/>

apiBaseUrl must be the platform's API origin — https://api.inkform.dev, not the apex. The platform routes by host, and only api. serves /api/newsletter/*. A POST to https://inkform.dev/api/newsletter/subscribe gets a 308 to www.inkform.dev, which 307s on to app.inkform.dev; since redirect responses carry no CORS headers, the browser's fetch fails on the chain instead of subscribing anyone.

The endpoint is CORS-open, so this works from any domain. Set both as env vars so the form can be left out entirely (render it conditionally) until the project ID actually exists:

# .env.local
NEXT_PUBLIC_INKFORM_PROJECT_ID=
NEXT_PUBLIC_INKFORM_PLATFORM_URL=https://api.inkform.dev

Prefer an MDX shortcut inside docs content? Register NewsletterCTA in your mdx-components.tsx (see this site's own mdx-components.tsx for the pattern) and use:

<NewsletterCTA heading="Subscribe for updates" />

What happens after submit

  1. The form POSTs { projectId, email, source } to /api/newsletter/subscribe.
  2. Inkform creates a pending subscriber and emails a confirmation link (double opt-in — required before any campaign send).
  3. The subscriber clicks the link, flips to active, and starts receiving campaigns sent from the dashboard for that project.

That's the whole integration. Sending, unsubscribe links, open/click tracking, and bounce handling are all handled on the Inkform side — your site never touches subscriber data or Resend directly.

Without the framework

Not using @inkform/framework? POST directly:

await fetch('https://api.inkform.dev/api/newsletter/subscribe', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ projectId, email, source: 'site' }),
});

A 200 means the subscriber was created or already existed; a 4xx body carries { error } — e.g. an invalid email or an unknown projectId. A 429 means a rate limit tripped (per IP, per address, or per project); it carries a Retry-After header — surface "try again shortly" rather than retrying in a loop.

Signing up the same address repeatedly won't re-send the confirmation email: one confirmation per address per 15 minutes, however many times it's requested. The response is identical either way, so a repeated signup still looks like a success to the visitor.