Skip to content

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 the data-stalkmail attribute.
  • 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 (or your-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.