Five complete, runnable starter projects that add a newsletter signup form and a contact form to a static site, with no backend of your own: no serverless function to write, no SMTP credentials, no database. The browser posts JSON straight to a SendBeam form endpoint, which stores the subscriber or emails you the message and returns the text to show.
Pick the folder that matches your stack and you are live in about five minutes.
| Starter | Stack | Host | Needs | Deploy config |
|---|---|---|---|---|
plain-html/ |
None — HTML, CSS and one JS file | Any static host | 🟢 No API key | none needed |
astro-cloudflare-pages/ |
Astro (static) | Cloudflare Pages | 🟢 No API key | public/_headers |
eleventy-netlify/ |
Eleventy + Nunjucks | Netlify | 🟢 No API key | netlify.toml |
nextjs-vercel/ |
Next.js App Router | Vercel | 🟢 No API key | vercel.json |
hugo/ |
Hugo | Any static host | 🟢 No API key | none needed |
Each one ships a signup component, a contact component, an optional Cloudflare Turnstile widget, a hidden check field, inline validation, a thank-you state and its own README.
Every example here is labelled with what it requires from your SendBeam account, so you know before you clone whether you can run it:
| Label | Meaning | Works on |
|---|---|---|
| 🟢 No API key | Posts to a public form endpoint. Nothing secret in your build, nothing to rotate. | Every plan, including Free |
| 🔑 API key | Calls the REST API with an x-api-key header, so the key has to live somewhere server-side. |
Every plan; the plan sets how many writes an hour |
All five starters above are 🟢, so you can run them end to end on a free account. Anything needing a key is labelled 🔑 here and says so again in its own README, with the reason.
git clone https://github.com/sendbeam-io/sendbeam-starters
cd sendbeam-starters/astro-cloudflare-pages # or eleventy-netlify, or nextjs-vercel
cp .env.example .env # paste your two form IDs
npm install && npm run devYou need two form IDs from SendBeam: create a signup form (choose the list it adds people to) and a contact form (enter the address messages go to), then copy each form's ID from its API Endpoint line. A free SendBeam account covers 500 contacts.
There is no server in these projects. Each form does one fetch to a public endpoint:
await fetch(`https://sendbeam.io/api/forms/${FORM_ID}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'reader@example.com',
first_name: 'Sam', // signup forms
// name, subject, message // contact forms
[HIDDEN_FIELD]: '', // your form's hidden check field, left empty
turnstile_token: token, // optional
}),
});The endpoint replies with JSON carrying the thank-you message to render. Form IDs are public by design: they carry no API key, and the endpoint is protected on the server instead. There is nothing secret to leak in your built HTML.
Every form carries one hidden check field, and its name is unique to that form. You will find it on the form's page in SendBeam, just under the embed code, and each starter takes it as configuration next to the form ID. Leave the field empty; a submission that arrives with it filled is discarded, and nothing is stored.
That name ends up in your published page source, which is unavoidable — the browser has to send it. What matters is
that it is not a constant shared by every SendBeam form, and that it is not sitting in a public repository where
thousands of them could be collected at once. A single fixed name, written down somewhere searchable, is what lets one
bot skip every form everywhere; a per-form name means anyone abusing yours has to come and look at your page first.
That is why no starter here has a name written into it, and why scripts/check-labels.mjs fails the build if one is
ever committed.
Signup forms honour double opt-in when the list has it switched on, so the subscriber gets a confirmation email and is only added once they click it. On the Free plan every list behaves this way whatever the setting.
- Newsletter signup that adds contacts to a chosen list, with double opt-in when enabled.
- Contact form that emails you each message, with the sender's address as the reply-to.
- Custom fields — any extra field you declare on the form is stored on the contact. Keys you have not declared are ignored, so a public endpoint cannot be used to stuff arbitrary data into your audience.
- Spam protection without a CAPTCHA wall: the hidden check field, an origin allow-list, rate limits, and Turnstile if you want it.
- No vendor lock-in in the markup — the forms are plain HTML and one
fetch; swap the endpoint and they still work.
You may not need a starter at all. Every active form has a hosted page at https://sendbeam.io/f/<form id> — use it
where there is no site to paste markup into, such as a link in a social bio or a QR code. Signup forms can also open as
a pop-up over any page, which is one script tag and no markup:
<script src="https://sendbeam.io/f/<form id>/popup.js" data-delay="5000" data-once="day" async></script>These starters are for when you want the form to be part of your own page, styled by your own CSS.
All of those are reasonable. The differences worth knowing:
- Your own function means writing, deploying and monitoring code, plus an email provider and its credentials. Fine if you want the control; a lot of moving parts for a signup box.
- Formspree, Web3Forms, Netlify Forms handle contact forms well but stop there. They do not keep a subscriber list, send a newsletter, or manage unsubscribes, so a site that needs both ends up with two products.
- SendBeam does both from one account, and one account covers every site you run rather than billing you per site. If you only ever need a contact form, the dedicated form services are simpler.
- Forms — form types, fields, protection settings
- Contact form backend for Cloudflare Pages
- Newsletter signup on an Astro site
- Form to email without a backend
- Double opt-in on a static site
- API reference and OpenAPI spec
Issues and pull requests are welcome, particularly fixes where a host or framework has changed its defaults. See CONTRIBUTING.md for how to report a broken starter or propose a new one, and MAINTENANCE.md for why there are five and not fifty. Security reports go to SECURITY.md, not the issue tracker. Everyone taking part is covered by the Code of Conduct.
MIT — use these as the basis for your own site, commercial or otherwise.