Contacts and forms
Your own sign-up forms post to stet, and stet records each person in your own Postgres: which list they joined, when, and from which page. Every email you send can carry a one-click unsubscribe that stet serves. Your own email provider does the sending.
Contacts live in a database. A snapshot-only site adds one in the first step.
01Add a database
Install the Postgres driver, point STET_DATABASE_URL at your database, and run upgrade. It creates the contact tables and prints the store block to add to stet.config.json.
$ npm install pg
$ npx stet upgrade --store pg
…
applied 003_contacts.sql
add this to stet.config.json (this command never edits it):
{
"store": {
"adapter": "pg",
"urlEnv": "STET_DATABASE_URL"
}
}02Create a list
A group is one list people can join: a newsletter, a waitlist, early access. It opens as soon as you create it.
$ npx stet contacts group add newsletter --name "Harbour newsletter"
group newsletter: created, open — no properties
join endpoint: POST <your forms mount>/join/newsletter03Point your form at it
createStetFormsHandler serves two routes: the join your form posts to, and the unsubscribe page your emails link to. Mount it at /api/stet in your app, or in a small Worker for a static site.
Your form posts the address as JSON and gets {"ok":true} back. The contacts reference has the full setup for Next.js and for a Worker.
// the forms mount
import { createStetFormsHandler } from '@getstet/stet/server';
const forms = createStetFormsHandler({
store, secret,
unsubscribeBase: 'https://example.com/api/stet',
guard: rateLimit,
onJoin: sendWelcome,
});
// your form
POST /api/stet/join/newsletter {"email":"[email protected]"}
→ {"ok":true}04Welcome and unsubscribe
When someone joins, stet calls your onJoin with who they are and a ready-made unsubscribe link, and your email provider sends the welcome. The link opens a page stet serves, and one click records the unsubscribe.
// what onJoin receives
{ group: "newsletter", email: "[email protected]", isNew: true,
unsubscribeUrl: "https://example.com/api/stet/unsubscribe?token=…", … }05See who joined
contacts list shows a group's members. contacts get shows one person: when and where they joined, and whether they unsubscribed. export and erase answer a person's request for their data.
$ npx stet contacts get [email protected]
[email protected] in project default — first seen 26 Sep 2026 08:56
newsletter joined 26 Sep 2026 08:56 from https://harbour.example/ (form footer)
suppressed: marketing, 26 Sep 2026 08:56 (one-click)Next
- Email — adopt the templates your welcome email is built from
- Check — the checks that run before anything lands
- Contacts reference — every option, error and recipe