prelo
Plugins / Newsletter signup

Newsletter signup

Signup form with double opt-in, delivering to Resend, Mailchimp or Buttondown.

Agithub.com/anderdigitalMarketingprelo >= 0.4Updated May 2026Official
Use this plugin
1
2
or point your agent at the spec:
Your agent reads the spec and writes the code into your project. Review the diff like any change.

What this adds

A newsletter signup form you can drop on any page, with proper double opt-in: subscribers confirm by clicking a link in their inbox before they count. Confirmed subscribers are pushed to the provider you already use — Resend, Mailchimp or Buttondown — or simply kept in your own database if you'd rather export a CSV.

How it works

The spec below adds a subscriber content type, two custom API routes (subscribe + confirm), and a small form component. Pending subscribers are drafts; confirming publishes them and forwards them to the configured provider. Your provider API key stays server-side in an env var — the browser never sees it.

The spec your agent followsexpand ▾
You are adding newsletter signup with double opt-in to a Prelo project. Read `prelo docs config` and `prelo docs api` if unsure.

1. **Content type.** In `cms.config.js`:

   ```js
   subscriber: {
     label: "Subscribers",
     fields: {
       email:  { type: "text", required: true },
       token:  { type: "text" },          // confirm token, cleared after use
       source: { type: "text" },          // page the signup came from
     },
     studio: { columns: ["email", "source"] },
   },
   ```

   Pending = draft, confirmed = published. Because the public API returns only published entries and this type should not be public at all, also ensure the frontend never links or renders `/api/subscriber` data.

2. **Provider config.** Support three providers behind env vars — implement only the adapter(s) the user asks for, defaulting to "local only" when no provider env is set:
   - `NEWSLETTER_PROVIDER` = `resend` | `mailchimp` | `buttondown` (unset = local)
   - Resend: `RESEND_API_KEY`, `RESEND_AUDIENCE_ID` → `POST https://api.resend.com/audiences/{id}/contacts`
   - Mailchimp: `MAILCHIMP_API_KEY`, `MAILCHIMP_LIST_ID` (datacenter from the key suffix) → member upsert with `status: "subscribed"`
   - Buttondown: `BUTTONDOWN_API_KEY` → `POST https://api.buttondown.email/v1/subscribers`

   Put the adapter in one file (e.g. `newsletter-provider.js` next to `cms.config.js`) with a single `push(email)` export. Note in code comments that these are third-party services with their own terms.

3. **Routes.** In `cms.config.js` `routes:` (mounted under `/api/x/`):
   - `"POST /newsletter"` — validate email format; if the email already exists as a subscriber, respond `200 {"ok":true}` (do not leak existence). Otherwise create a **draft** `subscriber` with a random 32-byte hex `token`, and email a confirm link `https://<site>/api/x/newsletter/confirm?token=…` using the project's SMTP config (the env vars `prelo doctor` reports). If SMTP is missing, respond `503` with a clear error — double opt-in cannot work without outbound email.
   - `"GET /newsletter/confirm"` — look up the draft by token; if found, set it to published, clear the token, call the provider adapter (failures logged, not fatal), and redirect (302) to `/?subscribed=1` or a `/thanks` page if one exists.

   Rate-limit `POST /newsletter` by IP (5/10 min) using an `x_newsletter_hits` table created in `hooks.boot`, same pattern as the contact-form plugin.

4. **Form component.** Add a `NewsletterSignup` component in the frontend: single email input + button, posts JSON to `/api/x/newsletter`. Success state: "Check your inbox to confirm." Inline error state. If the project uses page sections, register a `newsletterSignup` section (optional `heading` text field) so the client can place it from Studio.

5. **Verify.** `prelo check`; subscribe with a test address; confirm the draft appears in Studio; open the confirm link; confirm the entry flips to published and (if configured) reaches the provider.

After it's built

Subscribers are listed in Studio under Subscribers — drafts are unconfirmed, published are confirmed. Delete a row to unsubscribe locally; provider-side unsubscribes are handled by the provider's own links in your campaigns.