# 25-May-2026 — consent and unsubscribe suppression controls for composer recipients [#279](https://github.com/nbarrett/ngx-ramblers/issues/279)

## [build 654](https://github.com/nbarrett/ngx-ramblers/actions/runs/26392751272) — [commit 8c5e2b9](https://github.com/nbarrett/ngx-ramblers/commit/8c5e2b913e28ae4e208a2993d4d5de0c548edc6e)

_____

## Email composer recipient suppression controls

Gives admins control over who can be emailed from the composer, covering Head Office marketing consent and unsubscribe/block state. Members who should not be emailed are shown clearly in the recipient picker rather than silently disappearing, and the same validation plus an authoritative server-side guard stop them being sent to on every path - picker, manual, and the direct single-member flow.

### Mail settings (Brevo, Global Settings)

- **Respect Head Office marketing consent** (default on): members without `emailMarketingConsent` appear in the picker as a disabled "members without Head Office consent" group and cannot be selected. Turn it off to allow selecting them.
- **Block sending to unsubscribed or blocked members** (default off, overridable): when on, unsubscribed/blocked members are disabled in the picker and skipped at send. Off by default because a local unsubscribe is only pushed to Brevo when you run Update Brevo Mailing Lists, so enforcement is opt-in.
- Both settings are defaulted to a concrete value via config migration so the checkbox state always matches the behaviour.

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/beb21959-a590-4b85-9a61-af58df96dd76.jpeg)

_Above: Screenshot of Global Mail Settings_

### Composer recipient picker

- No-consent, unsubscribed and blocked members are grouped and labelled instead of dropped, so it is obvious they exist and why they cannot be selected.
- Unsubscribed/blocked entries show the date, for example "(unsubscribed 23 May 2026)" or "(blocked 23 May 2026)".
- Auto-fill pre-filters skip disabled members, and any previously saved selection of a now-disabled member is removed.
- Typed search text clears after each member is added.
#### Recipient selection when "Respect Head Office marketing consent" is true
- See below for a screenshot of when individual members within a mailing list are displayed within the email composer. 
- Only the members that have given Head Office consent are selectable. Members without Head Office consent remain disabled and non-selectable.


![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/78355952-bab6-4f23-9e94-59d918d9f2f2.jpeg)

#### Recipient selection when "Respect Head Office marketing consent" is false
- See below for a screenshot of when individual members within a mailing list are displayed within the email composer. 
- Even though members are grouped according to whether they have given Head Office consent,  they can still be included in the member selection.

![](https://ngx-ramblers.org.uk/api/aws/s3/site-content/b0d52da3-24d7-463d-8775-29cb9a53139d.jpeg)


### Reliable unsubscribe detection

- A bare `subscribed: false` cannot distinguish "actively unsubscribed" from "never subscribed", so an `unsubscribedAt` timestamp records genuine unsubscribe events only.
- `unsubscribedAt` is stamped on every true-to-false transition: branded unsubscribe page (per-list and legacy all-lists), Brevo events webhook, Brevo unsubscribes sync, and admin manual un-tick in the member modal (cleared on re-subscribe). Persisted in the Mongo member schema.
- Detection requires `subscribed: false` with `unsubscribedAt`, so never-subscribed entries no longer produce false positives.
- The reference list is the narrowed list when one is chosen, the email type's default list in "Any member" mode, otherwise fully unsubscribed (off every list).

### Unsubscribe link targets the right list

- The branded unsubscribe link previously used the email's configured default list (e.g. All Members) even for a member only ever on another list, so the unsubscribe was a no-op on their real subscription. It now resolves to the list the member is actually on when they are not subscribed to the requested list.

### Validation and server-side guard on every path

- Sent-status (already-received) dedup is driven by the email type's configured member selection: it applies only to the targeted cohorts (Expired, Recently Added, Missing From Bulk Load) and never to Mailing List or None, so a newsletter goes to the whole list each time.
- The direct single-member flow now uses the same recipients-step validation as the list flow: a chosen member who is blocked, unsubscribed or without consent produces a validation error and disables Next, rather than slipping through.
- `batch-transactional-send` skips, at send time, any member who is blocked or unsubscribed (when the block toggle is on) or without Head Office consent (when the consent toggle is on), with a `Skipped` status and reason
- an authoritative backstop regardless of how the member was chosen. The progress bar shows a "N skipped" badge and per-recipient detail.

### Historical data backfill

- Migration `20260524000000-backfill-subscription-unsubscribed-at` populates `unsubscribedAt` for pre-existing branded unsubscribes from the mail-list-audit history (per-list and legacy), so past unsubscribes are respected too. Runs automatically in the background on server start.

### Member modal

- A titled caveat ("Subscriptions do not guarantee delivery") under the subscription checkboxes notes that a ticked list does not guarantee delivery when the member is blocked or has not given Head Office consent.
- The Brevo "View contact details" button sits on the same line as the Subscription audit / Brevo activity toggle, right-aligned; vertical spacing equalised.

### Shareable deep links

- Opening a member's edit modal sets a `member-id` query param (membership number, or member id when there is no membership number) and the active modal tab on open, alongside the existing Brevo sub-tab param, so the URL recreates the exact view and is shareable. Closing clears them.

### Other composer refinements

- Recipient sections (Send to an email address, Recipients for this send, Pick from previously-saved addresses, include group members) use a consistent collapsible framed layout; Global Settings checkboxes padded.
- Contact action dropdown tooltips placed to the right so they do not overlap the member name.

### Also bundled (separate work)

- Brevo contact activity timeline refresh: colour-coded event markers, inline subject, tidier layout and preview toggle.
- Removal of unused Angular imports in a few components.

## Member modal refinements (#279 follow-up)

### Tab restructure

- Combined the Membership and Ramblers tabs into one Ramblers Membership tab, with framed Membership, Email marketing consent and Walks Manager sections.
- Moved Email marketing consent onto Ramblers Membership, since it is Ramblers/Head Office provided data rather than email-provider state.
- Renamed the provider tab (Brevo or Mailchimp) to "Email & subscriptions" and wired it to the modal-tab query param so it deep-links and reopens like the other tabs.
- Sections across the Email & subscriptions and Ramblers Membership tabs use the shared thumbnail-heading-frame for one consistent framed style.

### Consistent, DRY tables

- The Audit tab's member-update-audit table now uses the same shared sortable-table component as the Subscription audit table, with identical styling and sort behaviour. Removed the bespoke table markup and the duplicate client-side sort logic.

### Deep-link reliability

- Clicking Edit now reliably sets member-id in the URL: the modal-open navigation is awaited before the modal renders, and the tab navigation only fires on a real tab change, so the two no longer race and strip member-id.

### Brevo activity timeline

- Centred the timeline rail so status dots sit on the line rather than being clipped to its left.

### Window fit

- Constrained the member modal to the window height so the title bar and the Save / Delete / Copy / Cancel buttons stay pinned and the body scrolls within, rather than the footer dropping below the viewport on tall tabs like Email & subscriptions. The Brevo Recent history keeps its own scroll area.

### Jump to consent from the subscription warning

- When a member cannot be emailed because Head Office marketing consent has not been given, the "Subscriptions do not guarantee delivery" note now shows a link that opens the Ramblers Membership tab, where the consent detail now lives.

### Tab row, frames and Preferences

- The member modal's tab row no longer wraps to a second line.
- Section frames grow to wrap their own content rather than being held to a fixed height (the earlier fit-to-window flex experiment was dropped); the body scrolls as a whole with the title and footer pinned.
- The standalone Preferences tab is removed
- its profile-settings-confirmed and hide-surname-from-non-logged-in-members settings now sit in a framed section at the bottom of the Contact tab, leaving one fewer tab.