Skip to content
Sendoff
Esc
↑↓navigate↵open⌘Jpreview
On this page

Subscribers

How people join and leave, how bounces and complaints are handled, and how to add a signup form to your own site.

Every subscriber has one of five statuses:

Status Meaning Gets email
pending Signed up, hasn’t confirmed yet No
active Confirmed, or imported from another provider Yes
unsubscribed Unsubscribed. They can resubscribe from the same link. No
bounced Their address hard-bounced Never again
complained They marked an email as spam Never again

Ask the agent “how many subscribers do I have?” or “is jane@example.com subscribed?” to see counts or look someone up.

The signup page

Your hostname’s home page, like https://news.example.com, is a signup form showing the name and description from your settings. Link to it from your site and social profiles.

It uses double opt-in: a new address gets a confirmation email and becomes active only after opening the link in it and pressing Confirm subscription. Opening the link alone doesn’t confirm, because some mail filters open every link in an email as it arrives. Cloudflare Turnstile and a hidden field keep bots out. The form always says “check your inbox”, whether the address is new, pending or already subscribed, so it can’t be used to find out who’s on your list. A confirmation goes to the same address at most once every 10 minutes.

A form on your own site

To keep your existing signup form, have your site’s server call Sendoff’s REST API. Ask the agent for a key:

Create an API key for my website’s signup form.

Store it as a server-side secret, like SENDOFF_API_KEY, then subscribe addresses from your server. In a Next.js server action:

"use server";

export const subscribe = async (email: string) => {
  const response = await fetch("https://news.example.com/api/v1/subscribers", {
    body: JSON.stringify({ email }),
    headers: {
      Authorization: `Bearer ${process.env.SENDOFF_API_KEY}`,
      "Content-Type": "application/json",
    },
    method: "POST",
  });

  if (!response.ok) {
    throw new Error("Couldn't subscribe. Please try again.");
  }
};

The API does double opt-in too, and returns 202 for every valid address, just like the signup page. Never put the key in browser code: the API sends no CORS headers, so browsers can’t call it anyway.

Adding someone yourself

Add jane@example.com to my list.

The agent calls add_subscriber, which sends Jane a confirmation email. Only if someone asked you directly to be added should the agent skip the confirmation. Nobody who unsubscribed, bounced or complained is re-added this way. People who unsubscribed can sign up again from the signup page; bounced and complained addresses can’t.

To bring over a whole list, see Importing your list.

Unsubscribes

There are two ways out, and both take effect immediately:

  • The link in the footer opens a page with an unsubscribe button. The link itself doesn’t unsubscribe anyone, because email security scanners open every link in an email. The same page lets people resubscribe if they clicked by mistake.
  • The one-click unsubscribe that Gmail, Apple Mail and Yahoo show near the top of the email works without opening a page.

If someone replies asking to be removed, ask the agent to unsubscribe them.

Bounces and complaints

SES reports bounces and complaints to Sendoff through an SNS topic that setup wires up:

  • A hard bounce (the address doesn’t exist) marks the subscriber bounced.
  • A complaint (marked as spam) marks them complained.
  • A soft bounce (a full mailbox, a temporary failure) is logged, and the subscriber stays active.

Bounced and complained subscribers are never emailed again. SES’s account-wide suppression list, which setup turns on, blocks the same addresses as a second line of defense.

Replies

Replies go to your reply-to address, or to the sender if you haven’t set one. They’re ordinary email: Sendoff doesn’t receive or store them.

Last updated on

Was this page helpful?