Signup forms
Every newsletter has a hosted signup page and an embeddable form. The embed renders the form directly on your page inside a Shadow DOM - no iframe - so it loads fast and your page styles cannot leak into it. It reads its copy, fields, colors and font from your Form and embed and Design settings.
Before you embed
- Turn the form on in Settings → Form and embed. A disabled form renders nothing.
- Copy your newsletter slug - for example
your-newsletter-slug. The embed uses it in thedata-stalkmailattribute. - If you set a domain allowlist, add the hostname that will serve the form. Requests from the same origin always work; an empty allowlist accepts any site.
- Prefer no code? The hosted page at
/join/your-newsletter-slug(oryour-newsletter-slug.stalkmail.com/join) is ready to share as soon as the form is enabled.
The script
The embed is one script tag. It fetches the form settings from the public form API and inserts the form immediately after the script tag.
<script src="https://stalkmail.com/embed.js" data-stalkmail="your-newsletter-slug"></script>Because the form is inserted right after the script, the element surrounding the script controls the form width. That difference is the only thing between the two options below.
Option 1 - Drop-in embed
The simplest placement. The form takes the width of its parent, so dropping the script straight into the page body makes it fill the page.
Plain HTML/JS
<body>
<h2>Read the latest issue</h2>
<p>New writing in your inbox, once a week.</p>
<script src="https://stalkmail.com/embed.js" data-stalkmail="your-newsletter-slug"></script>
</body>React
Use Next's Script component. Note that it appends the remote script to <body>, so the form lands at the end of the page and fills it.
import Script from "next/script"
export function SignupEmbed() {
return (
<Script
src="https://stalkmail.com/embed.js"
data-stalkmail="your-newsletter-slug"
strategy="afterInteractive"
/>
)
}Option 2 - Fixed width with a container
Wrap the script in an element with a width to keep the form compact. This is the right choice for footers, sidebars and inline placements, where a page-wide form would look out of place. Set any width you like - the form fills the container, not the viewport.
Plain HTML/JS
<div style="max-width: 22rem">
<script src="https://stalkmail.com/embed.js" data-stalkmail="your-newsletter-slug"></script>
</div>React
With next/script the script is moved to <body>, so a surrounding container is ignored. Create the script inside the container instead. Because the container is React-owned, removing it also removes the script and the form it injected.
"use client"
import { useEffect, useRef } from "react"
export function SignupEmbed({ slug }: { slug: string }) {
const containerRef = useRef<HTMLDivElement>(null)
const injectedSlugRef = useRef<string | null>(null)
useEffect(() => {
const container = containerRef.current
if (!container || injectedSlugRef.current === slug) return
// React runs effects twice in development. A second script would be
// detached by the time it loads, so inject it only once per slug.
injectedSlugRef.current = slug
const script = document.createElement("script")
script.src = "https://stalkmail.com/embed.js"
script.async = false
script.setAttribute("data-stalkmail", slug)
container.appendChild(script)
}, [slug])
return <div ref={containerRef} style={{ maxWidth: "22rem" }} />
}If the page is served by StalkMail itself, a relative /embed.js also works and keeps the form's API calls same-origin.
What the form uses
- Form and embed supplies the heading, description, button label, placeholders, optional name field, stacked or inline layout, the on/off toggle and the domain allowlist.
- Design supplies the accent, background, text and muted colors, the font and the corner radius. The same tokens style the website, issue pages, join page and embed.
- Confirm signups decides whether a new address is subscribed immediately or receives a confirmation email first. The form shows the matching success message either way.
Behavior and troubleshooting
- Submissions go to
POST /api/forms/your-newsletter-slug/subscribe. Double opt-in, the honeypot and per-IP rate limiting all apply, just as on the hosted page. - If the form does not appear, open the browser console. The loader logs a message prefixed with
[StalkMail]when the form is disabled or the current domain is not allowed. - To inspect the exact settings the embed will use, request
GET /api/forms/your-newsletter-slug- it returns the form copy, layout and design tokens.