Unsubscribes and Opt-Outs

Every marketing email Leed sends carries an unsubscribe link, the link lands on a page built into your own site, and the result is written against the contact before the confirmation page finishes loading. There is nothing to configure to make that work, and nothing you can turn off.

What the recipient sees

Two pages, both on your domain, both wearing your site’s fonts, colors and head markup because they are built by your site build alongside everything else.

  1. The unsubscribe page. Five reasons as radio buttons — one must be chosen — plus a separate checkbox reading Remove me from all future communications, and an Unsubscribe button.
  2. The confirmation page. A heading reading You’ve been unsubscribed. and a link back to your homepage.
The default Leed unsubscribe page, showing five reason options, the do-not-contact checkbox and the Unsubscribe button The default confirmation page, reading "You've been unsubscribed" above a link back to the homepage

There is one piece of indirection nobody guesses from the outside, and it matters if you are debugging: the tracked unsubscribe link does not return HTML. It returns the URL of your unsubscribe page, and your site fetches and renders that page in place, at the tracked URL. That is why the form can submit back to the same address with .unsubscribe swapped for .out — the browser never left.

sequenceDiagram
    autonumber
    participant R as Recipient
    participant S as Your site
    participant D as Leed
    R->>S: GET /e/o/{batch}/{hash}.unsubscribe
    S->>D: Record a click_unsubscribe event
    S-->>R: 200 with the URL of your unsubscribe page
    S->>S: Fetch that page and render it at the same URL
    R->>S: Submit the form to .out?reason=...&donotcontact=true
    S->>D: Write the opt record, set unsubscribed on the send, record opt_out
    S-->>R: 302 to your confirmation page

What Leed records

Submitting the form writes three things at once:

  • An opt record against the contact, carrying the reason, the do-not-contact flag, the time, and the form or campaign the send was tied to.
  • The unsubscribed flag on that send’s recipient row. This is the number the Unsubscribes column reports in Sent Emails — unsubscribes attributed to the message that caused them.
  • An opt-out event on the contact’s activity timeline.
What the recipient seesStored reasonWhere it comes from
Too many emailstoo_manythe unsubscribe form
Content does not meet my needs or interestsirrelevantthe unsubscribe form
Content was not what I expectedmismatchthe unsubscribe form
I never signed up for these emailsnot_methe unsubscribe form
Other reasonotherthe unsubscribe form
Remove me from all future communicationsnot a reason — sets the do-not-contact flag alongside whichever reason was chosenthe unsubscribe form
—spam_complaintwritten by Leed, not by a person, when a mail provider reports the message as spam — see bounces and deliverability

A reason is required, so every opt-out you see carries one. The do-not-contact checkbox is optional and is the stronger of the two signals.

Opted in, opted out, do not contact

Leed keeps a history of opt records, not a single flag, and reads the most recent one to decide whether a contact is mailable.

StateHow it is createdCan receive marketing email?Can receive a form response email?
Opted inno opt records at all, or the newest record is an opt-in with do-not-contact clearYesYes
Opted outthe newest record is an opt-outNoNo
Do not contactthe newest record has the do-not-contact flag setNoNo

A contact who has never interacted with you at all is treated as opted in — the absence of a record is not a refusal. This is why a contact you imported from a CSV is mailable the moment the import finishes, and why the responsibility for having permission is yours rather than the product’s.

A later form fill re-subscribes a contact

There is no re-subscribe control anywhere in the CMS. A form fill is the only path back to a mailable state, and that is a side effect rather than a feature.

Where opt-outs are enforced

Opt state is checked in three separate places, at three different moments, and the differences between them explain a number you will otherwise find puzzling.

  1. When you press Send. The composer resolves the contact group and drops contacts carrying the do-not-contact flag. If nothing is left it refuses the send with 400 No opted in users in group. Note what this stage does not do: a plain opt-out, with no do-not-contact flag, is still counted here.
  2. When the batch fans out. Before any message is built, Leed drops everyone who is not opted in — plain opt-outs included — and everyone whose address has hard-bounced. This is the stage that produces the recipient rows, and therefore the Recipients number in Sent Emails.
  3. When each message is built. If the contact opted out between the batch being accepted and their message being rendered, the send for that recipient is refused outright.

The visible consequence is that the Recipient Preview count in the composer can be larger than the Recipients count on the finished send, sometimes by a lot. Neither number is wrong: the first is who is in the group, the second is who was eligible when it went out. Opted-out contacts stay in the group — they are removed at send time, not at match time — which is deliberate, so a group’s definition does not quietly change as people leave.

Seeing opt state

There is no unsubscribe screen. Opt state shows up in three places, all of them read-only:

  • The legacy All Contacts table has an Opt In column showing a green In or a red Out badge per contact.
  • Engage contact rows carry an Opted out pill, and so does each send in a contact’s Emails panel where that send is the one they opted out from. Contacts covers the record.
  • The Unsubscribes column in Sent Emails counts, per send, the recipients who opted out from it.

There is no bulk unsubscribe screen, no way to import a suppression list, and no way to opt someone out on their behalf from the interface.

Replacing the unsubscribe pages

Both pages are Leed defaults you can take ownership of. This is the ordinary template-override mechanism described in overriding Leed templates — the file existing in your site repository is what switches the override on.

What you can override

PageEject nameWritten toPlan requiredWhat you must preserve
The unsubscribe form pageunsubscribesrc/_includes/unsubscribe.hbsany, including Freethe form: its id, its submit handler, the reason radios and the do-not-contact checkbox
The confirmation pageunsubscribedsrc/_includes/unsubscribed.hbsany, including Freenothing — but give the reader a way back to your site

Both are full-page replacements: your file supplies the whole document, not a fragment slotted into Leed’s.

How

# See everything this site's plan lets it customize
leed site eject

# Take ownership of the two unsubscribe pages
leed site eject unsubscribe
leed site eject unsubscribed

Each command copies a starting file into src/_includes/ and stops there — it does not edit, commit or deploy anything. Pass -f to overwrite a file you have already ejected, which discards your version. The next site build picks the files up on its own; there is no flag to set. leed site eject lists every ejectable template and the plan each one needs.

The contract you must keep

The submit handler is Leed’s, and it reads the form by element name. Your markup can look like anything; these five things have to survive:

  • the form’s id is unsubscribeForm;
  • its onsubmit calls submitLeadUnsubscribe() and returns the result;
  • the reason radios share name="reason" and carry exactly the five stored values;
  • the do-not-contact checkbox is name="donotcontact" with value="true";
  • the button submits the form.

The safest override is a layout of your own that includes the leed/unsubscribe-form partial and restyles everything around it — then the contract is Leed’s problem rather than yours, and a future change to the form arrives with your next build. The pages inherit your site’s CSS either way, so restyling them is ordinary theming; how styling works applies unchanged.

The default form markup, for reference
<form id="unsubscribeForm" class="leed-form" onsubmit="return submitLeadUnsubscribe();">
  <div class="leed-form-block explanation">
    Select Reason
  </div>
  <div class="leed-form-block">
    <input type="radio" id="too_many_emails" name="reason" value="too_many" required="true">
    <label for="too_many_emails">Too many emails</label>
  </div>
  <div class="leed-form-block">
    <input type="radio" id="irrelevant_content" name="reason" value="irrelevant">
    <label for="irrelevant_content">Content does not meet my needs or interests</label>
  </div>
  <div class="leed-form-block">
    <input type="radio" id="unmet_expectations" name="reason" value="mismatch">
    <label for="unmet_expectations">Content was not what I expected</label>
  </div>
  <div class="leed-form-block">
    <input type="radio" id="not_me" name="reason" value="not_me">
    <label for="not_me">I never signed up for these emails</label>
  </div>
  <div class="leed-form-block">
    <input type="radio" id="other" name="reason" value="other">
    <label for="other">Other reason</label>
  </div>
  <div id="donotcontact-group" class="leed-form-block">
    <input type="checkbox" id="donotcontact" name="donotcontact" value="true">
    <label for="donotcontact">Remove me from all future communications</label>
  </div>
  <button id="unsubscribe-button" type="submit">Unsubscribe</button>
</form>

The link that starts all of this is the one your email layout is required to contain — a send that renders without it fails for that recipient rather than going out unsubscribable.

ESC