Theme

Subscription management

The unsubscribe link in every email opens a page Mimeo hosts, on your domain, that asks for one confirming click. Its text is yours to change, or it can send people on to a page of yours after they confirm — and these public, token-authenticated endpoints are what a page of yours would call to offer resubscribe. This page has a complete working example.

Mimeo's unsubscribe page

Out of the box, {{ unsubscribe_url }} in your footer resolves per recipient to

https://mimeo.yourdomain.com/unsubscribe/<token>

on your connected sending domain (or your Mimeo's own host until one is connected). Opening the link never unsubscribes anyone: the page asks "Stop these emails?", names the address and the sender, and the person is out when they press its Unsubscribe button — one click on the page, and only that click. Once unsubscribed, the page says so and offers a Resubscribe button for the accidental tap. Resubscribing shows the reverse: "You're subscribed again", with an Unsubscribe button.

The one-click header URL (/u/<token>) opened as a plain link — what an older mail client does with it — also lands here.

Making it yours

The page's text is yours to change under Settings → General → Email footer, with a preview that walks through every state of the page: the status line, headline, confirmation text, resubscribe button (its text and color) and the small text under it for the just-unsubscribed state, and the same set for the state after someone resubscribes. {email} in either confirmation becomes the person's address. Every field has a default; blank means the default. The same fields are the sending.unsubscribe_page.* settings over the settings API, MCP and your definitions repo.

Sending people to your own page

Set a Custom unsubscribe URL under Settings → General → Email footer (the setting is sending.unsubscribe_page_url, writable through the settings API, MCP, and your definitions repo). The footer link still opens Mimeo's page, and the person still confirms and unsubscribes there — the URL only changes where they land next. Instead of Mimeo's confirmation they're sent straight on to your URL, so it can be a plain "you're out" page with no logic at all.

If you'd like that page to offer resubscribe (or show the address), the person's signed token rides along:

Your page then calls the endpoints below with it. Clear the setting and people stay on Mimeo's page.

Tokens

Every one of these endpoints is authenticated by a signed token:

Non-expiring is intentional. Someone finding an old email in their archive two years from now must still be able to unsubscribe. An expired opt-out link is a complaint waiting to happen.

Endpoints

All of these are public — no bearer token, because they run in your subscribers' browsers. CORS is open (*) so your page can call them from any origin you host it on.

Get status

GET /api/v1/subscription/:token
{
  "email": "[email protected]",
  "unsubscribed": false,
  "unsubscribed_at": null,
  "reason": null
}

Call this on page load. Showing people the address they're managing prevents the most common confusion — someone with several addresses not knowing which one they just opted out.

Call it with fetch() (or anything that doesn't ask for text/html). A browser navigating to this URL is a person, not a page — email sent before Mimeo's page existed linked its footer straight here — so that request is redirected to Mimeo's unsubscribe page instead of showing JSON.

Unsubscribe

POST /api/v1/subscription/:token/unsubscribe
Content-Type: application/json

{ "reason": "too_frequent" }

reason is optional. The call:

Returns the same status shape as the GET, reflecting the new state.

Resubscribe

POST /api/v1/subscription/:token/resubscribe

Reverses it. Worth offering on the confirmation screen — accidental unsubscribes are common, and the alternative is that they're gone. Like unsubscribe, it's idempotent and returns the status shape; the example below wires it to a single button.

One-click unsubscribe (RFC 8058)

POST /u/:token

This is the target mail clients POST to when someone uses the unsubscribe button Gmail and Apple Mail show next to the sender name. You never call it yourself. It always returns 200, because a mail client treating an error as "unsubscribe failed" is worse than any failure it could report.

Mimeo sets the List-Unsubscribe headers that point here on every broadcast and sequence send.

Unsubscribe is global. There's one on/off state per person, not per list or topic. Don't build a preference-center UI with per-list toggles — there's nothing behind them.

Build your unsubscribe page

A complete working page, in one file, with no dependencies — for when a plain landing page isn't enough and you want your own to show the address and offer resubscribe. It fetches status on load, shows the address, and wires both buttons. Change BASE to your Mimeo, host it anywhere, set its URL as your Custom unsubscribe URL, and style it as your own. It reads the token from ?token=, which is how Mimeo passes it by default. Because Mimeo has already unsubscribed the person by the time they arrive, the page opens on its "you're unsubscribed" state.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Email preferences</title>
  </head>
  <body>
    <main>
      <p id="loading">Loading…</p>

      <section id="form" hidden>
        <h1>Unsubscribe</h1>
        <p>You're subscribed as <strong id="email"></strong>.</p>

        <label for="reason">Why are you leaving? (optional)</label>
        <select id="reason">
          <option value="">Prefer not to say</option>
          <option value="too_frequent">Too many emails</option>
          <option value="not_relevant">Not relevant to me</option>
          <option value="never_signed_up">I never signed up</option>
        </select>

        <button id="unsubscribe" type="button">Unsubscribe me</button>
      </section>

      <section id="done" hidden>
        <h1>You're unsubscribed</h1>
        <p>We won't email you again. Changed your mind?</p>
        <button id="resubscribe" type="button">Resubscribe</button>
      </section>
    </main>

    <script>
      // Your Mimeo — the connected sending domain's link host, or the app host.
      const BASE = "https://mimeo.yourdomain.com/api/v1/subscription";

      // Mimeo appends ?token=… to your Custom unsubscribe URL. (If you'd rather
      // have it in the path, put {token} in the URL and read it from there.)
      const token = new URLSearchParams(location.search).get("token");

      const el = (id) => document.getElementById(id);

      async function call(path, body) {
        const res = await fetch(BASE + "/" + token + path, {
          method: body === undefined ? "GET" : "POST",
          headers: { "Content-Type": "application/json" },
          body: body === undefined ? undefined : JSON.stringify(body),
        });
        if (!res.ok) throw new Error("Request failed: " + res.status);
        return res.json();
      }

      function render(state) {
        el("loading").hidden = true;
        el("email").textContent = state.email;
        el("form").hidden = state.unsubscribed;
        el("done").hidden = !state.unsubscribed;
      }

      el("unsubscribe").addEventListener("click", async () => {
        render(await call("/unsubscribe", { reason: el("reason").value }));
      });

      el("resubscribe").addEventListener("click", async () => {
        render(await call("/resubscribe", {}));
      });

      call("")
        .then(render)
        .catch(() => {
          el("loading").textContent = "That link isn't valid.";
        });
    </script>
  </body>
</html>

Things worth keeping when you restyle it