{"id":"6a64c51d88ea8efa12fc7b35","title":"2026 07 25 Issue 328","path":"how-to/committee/release-notes/2026-07-25-issue-328","contentMarkdown":"# 25-Jul-2026 — adopt Ramblers Team Emails API ([ref #328](https://github.com/nbarrett/ngx-ramblers/issues/328), [#329](https://github.com/nbarrett/ngx-ramblers/issues/329), [#330](https://github.com/nbarrett/ngx-ramblers/issues/330), [#331](https://github.com/nbarrett/ngx-ramblers/issues/331), [#332](https://github.com/nbarrett/ngx-ramblers/issues/332), [#333](https://github.com/nbarrett/ngx-ramblers/issues/333))\n\n## [build 792](https://github.com/nbarrett/ngx-ramblers/actions/runs/30160946191) — [commit 1e1610a](https://github.com/nbarrett/ngx-ramblers/commit/1e1610a8f36c15f438cd6b469df9b840d3fc73b8)\n\n_____\n\n## What's new\n\nNGX now implements the consumer side of Ramblers Team Emails 1.0.0 using the same shared contract as the live Salesforce mock. This replaces the superseded [#209](https://github.com/nbarrett/ngx-ramblers/issues/209) interface with the published supporter snapshot, unsubscribe and bounced-email operations, including their team-scoped `api_key` and `team_code` credentials.\n\nSupporter synchronisation treats the API response as a complete snapshot. It matches members using stable Ramblers identities, supports members and non-members without assuming that a membership number exists, preserves locally held data that Ramblers does not return, and records new, changed, unchanged and disappeared counts for review. A supporter who disappears from a later snapshot is counted only: Phase One does not define disappearance as deletion, expiry or transfer, so those members are not prepared for apply.\n\nThe published permissions now protect member data and email audiences. When Ramblers Team Emails is enabled, NGX requires the signed-in supporter to have permission to view member data before exposing protected addresses, and separately enforces permission to email members, volunteers and Wellbeing Walkers. Local-only recipients continue to use the existing NGX permissions.\n\nHard and soft Brevo bounces can now be reported to Ramblers using the supporter reference and email address. NGX records whether the writeback was delivered, skipped or failed, retries transient network, rate-limit and server errors, and avoids repeating a writeback that has already succeeded. Unsubscribe requests are contract-valid and use the published supporter identity, but remain deliberately disabled until Head Office confirms which preference they change and whether that change is team-specific or organisation-wide.\n\nAdmin UI now uses the published product name **Ramblers Team Emails** end to end (settings, bulk load, upload history, match labels). Internal host paths, config keys and Mongo field names stay as they are. Member bulk load and bulk delete show visual progress bars with percentage complete, clearer success and error wording, and safer matching that only updates members that already have an id. Bulk delete and Brevo contact create or update are faster under rate limits.\n\nUpload history can be managed from Member Bulk Load → Manage History: filter by date range, select sessions with checkboxes, and delete the selection (with member action rows). Diagnostics still supports list and clear-all for admins. Upload History filters sit inside the panel, sub-tabs and members populate on first open, and large tables scroll within the browser window. Mail Settings → List Settings shows live progress when syncing with Brevo.\n\n## Supporter fields (Team Emails 1.0.0)\n\nWhat NGX does with each published supporter field in this release, and known follow-ups. Local member data that Ramblers does not return is preserved.\n\n| Supporter field | NGX use in this release | Future / not yet |\n| --- | --- | --- |\n| `memberRef` | Primary stable identity for match, bounce writeback and unsubscribe preparation; mapped to `salesforceMemberRef` on the load snapshot | Keep as the stable identity for non-members; confirm any Head Office identifier semantics still open |\n| `contactId` | Mapped to `salesforceId`; used for match; written onto the member record on apply | — |\n| `membershipNo` | Mapped to `membershipNumber`; match key; applied when changed | Nullable members without a number are supported |\n| `email` | Mapped and applied (when blank locally, or when policy requires); bounce/unsubscribe identity with `memberRef` | — |\n| `title` | Mapped; applied when blank locally | — |\n| `firstName` | Mapped (falls back to `friendlyName`); applied when blank locally | — |\n| `lastName` | Mapped; applied when blank locally | — |\n| `friendlyName` | Used only as `firstName` fallback when first name is empty (not the same as local `displayName`) | Store as its own member field later; do not overwrite `displayName`; optional seed of display name only when creating or when blank |\n| `mobile` | Mapped to `mobileNumber`; applied when blank locally | — |\n| `landline` | Mapped to `landlineTelephone`; applied when blank locally | — |\n| `membershipStatus` | Mapped to `memberStatus`; applied when changed | Confirm status vocabulary with Head Office if labels change |\n| `memberType` | Mapped to `type` on the load snapshot | Persist/apply path if admin UI needs it beyond snapshot review |\n| `membershipExpiry` | Mapped to `membershipExpiryDate`; applied when changed (including clear rules for lapsed/resigned) | — |\n| `membershipJoinDate` | Received and validated; not mapped into NGX | Map if needed for AGM/join reporting without Insight Hub |\n| `membershipEndDate` | Received and validated; not mapped into NGX | Map if end-of-membership reporting is required |\n| `teamStatus` | Mapped to `salesforceTeamStatus` on the snapshot; drives audience checks (Member / Affiliated / Volunteer / Wellbeing Walker) when present on the member | Ensure apply always writes team status so audience checks stay accurate after every sync |\n| `teamRelationshipFrom` | Mapped to `salesforceTeamRelationshipFrom` on the snapshot | Persist/apply if history of team relationship is needed in admin |\n| `wellbeingWalker` | Mapped on the snapshot | Persist/apply for filters and reporting |\n| `walkLeader` | Mapped on the snapshot only (not yet applied to the member record). NGX “walk leader” filters and pickers still use who has led walks, event assignment and Walks Manager contact names | Persist the flag and use it as an extra label or soft signal (e.g. member admin badge). Do not replace activity-based leader lists, rematch or walk edit permissions with this boolean alone |\n| `volunteerRoles[]` | Received and validated; not mapped into NGX | Phase Two: roles, start dates, walk-leader status and Wellbeing Walks role |\n| `emailConsent` + `doNotEmail` | Combined into `emailMarketingConsent` (`true` only if consent and not do-not-email); `doNotEmail` kept on the snapshot | Granular marketing consent remains Insight Hub / Phase Two |\n| `emailConsentLastUpdated` | Mapped to `emailPermissionLastUpdated`; applied when changed | — |\n| `postConsent` | Mapped on the snapshot | Persist/apply; `postConsentLastUpdated` not mapped yet |\n| `postConsentLastUpdated` | Received and validated; not mapped | Map with post consent if post marketing is used |\n| `phoneConsent` | Mapped on the snapshot | Persist/apply; `phoneConsentLastUpdated` not mapped yet |\n| `phoneConsentLastUpdated` | Received and validated; not mapped | Map with phone consent if used |\n| `emailConsentWellbeingWalks` | Mapped on the snapshot | Persist/apply for Wellbeing Walks audiences |\n| `noWalkProgram` | Mapped on the snapshot | Persist/apply for programme suppression rules |\n| `noCampaigning` | Mapped on the snapshot | Persist/apply for campaign suppression |\n| `noSurveys` | Mapped on the snapshot | Persist/apply for survey suppression |\n| `canViewMemberData` | Mapped; enforced when sending protected member data / addresses (sender must have it) | Ensure every sync writes the flag onto the matched member record |\n| `canEmailMembers` | Mapped; enforced for Member / Affiliated audiences | Same persist/apply as above |\n| `canEmailVolunteers` | Mapped; enforced for Volunteer audiences | Same persist/apply as above |\n| `canEmailWellbeingWalkers` | Mapped; enforced for Wellbeing Walker audiences | Same persist/apply as above |\n| `canViewMemberDate` | Received and validated (contract field); not used by NGX | Confirm with Head Office whether this is intentional vs `canViewMemberData`; ignore or map once clarified |\n| Bounce payload (`memberRef`, email, hard/soft) | Hard/soft Brevo bounces written back with audit and retries | — |\n| Unsubscribe payload (`memberRef`, email) | Prepared against the contract; **disabled by default** until Head Office confirms scope | Enable when preference and team vs organisation-wide behaviour are confirmed |\n| Disappeared supporters | Counted only; not deleted, expired or prepared for apply | Removal semantics only if Head Office defines them |\n| Not on Team Emails 1.0.0 (e.g. postcode, joint membership, group/area/other marketing consent) | Not returned by the API; local values kept; Insight Hub import still used | Phase Two fields needed to retire Insight Hub exports |\n\n## At a glance\n\n- [#328](https://github.com/nbarrett/ngx-ramblers/issues/328) replaces the legacy client with `GET /get_supporters`, `/unsubscribe` and `/bounced_email` and validates every request and response against `@ramblers/sf-contract`.\n- [#329](https://github.com/nbarrett/ngx-ramblers/issues/329) reconciles complete supporter snapshots while preserving local fields, counting unexplained disappearances without preparing them for apply, and supporting affiliated members, volunteers and Wellbeing Walkers without membership numbers.\n- [#330](https://github.com/nbarrett/ngx-ramblers/issues/330) makes `canViewMemberData`, `canEmailMembers`, `canEmailVolunteers` and `canEmailWellbeingWalkers` effective safeguards for protected member data and audiences.\n- [#331](https://github.com/nbarrett/ngx-ramblers/issues/331) sends hard and soft bounce reports to Ramblers with idempotent audit records and bounded retries for temporary failures.\n- [#332](https://github.com/nbarrett/ngx-ramblers/issues/332) prepares Ramblers unsubscribe writeback using `memberRef` and email but keeps it behind an explicit disabled-by-default switch pending scope confirmation.\n- [#333](https://github.com/nbarrett/ngx-ramblers/issues/333) adds contract, mapping, snapshot, permission and writeback tests together with a rehearsal, production cutover and rollback runbook.\n- Insight Hub import remains available throughout transition and remains necessary for Phase Two fields that are absent from Team Emails 1.0.0.\n- System Settings and Member Bulk Load label the integration **Ramblers Team Emails**, with URL slug `ramblers-team-emails` and automatic rewrite from legacy `salesforce` / `head-office-supporter-import` query values.\n- Bulk load and bulk delete show striped progress bars; bulk wording uses **Processed** / **Finished processing** rather than per-member success noise.\n- Bulk-load matching requires an existing member id, so incomplete rows without ids are not treated as updates.\n- Save failures and unique-index races show a clear **Save failed** review message instead of a misleading **Updated** success.\n- Manage History lets file or member admins filter sessions by date, tick individual sessions and delete the selection; member action rows for those sessions are removed too.\n- Upload History filters live inside the tab panel; Members uploaded and Member actions headings and member rows load on first open.\n- Bulk-load tables scroll within the viewport so the panel stays on screen.\n- Member bulk-load audit sessions can also be listed and cleared under System Settings → Diagnostics.\n- Mail Settings → List Settings shows alert and progress feedback while syncing contacts with Brevo.\n- Brevo contact delete, create and batch update run with documented 10 RPS, 429 cooldown, and concurrent delete without remote event-history snapshots for large bulk selections.","contentHtml":"<h1>25-Jul-2026 — adopt Ramblers Team Emails API (<a href=\"https://github.com/nbarrett/ngx-ramblers/issues/328\">ref #328</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/329\">#329</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/330\">#330</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/331\">#331</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/332\">#332</a>, <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/333\">#333</a>)</h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/30160946191\">build 792</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/1e1610a8f36c15f438cd6b469df9b840d3fc73b8\">commit 1e1610a</a></h2>\n<hr>\n<h2>What&#39;s new</h2>\n<p>NGX now implements the consumer side of Ramblers Team Emails 1.0.0 using the same shared contract as the live Salesforce mock. This replaces the superseded <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/209\">#209</a> interface with the published supporter snapshot, unsubscribe and bounced-email operations, including their team-scoped <code>api_key</code> and <code>team_code</code> credentials.</p>\n<p>Supporter synchronisation treats the API response as a complete snapshot. It matches members using stable Ramblers identities, supports members and non-members without assuming that a membership number exists, preserves locally held data that Ramblers does not return, and records new, changed, unchanged and disappeared counts for review. A supporter who disappears from a later snapshot is counted only: Phase One does not define disappearance as deletion, expiry or transfer, so those members are not prepared for apply.</p>\n<p>The published permissions now protect member data and email audiences. When Ramblers Team Emails is enabled, NGX requires the signed-in supporter to have permission to view member data before exposing protected addresses, and separately enforces permission to email members, volunteers and Wellbeing Walkers. Local-only recipients continue to use the existing NGX permissions.</p>\n<p>Hard and soft Brevo bounces can now be reported to Ramblers using the supporter reference and email address. NGX records whether the writeback was delivered, skipped or failed, retries transient network, rate-limit and server errors, and avoids repeating a writeback that has already succeeded. Unsubscribe requests are contract-valid and use the published supporter identity, but remain deliberately disabled until Head Office confirms which preference they change and whether that change is team-specific or organisation-wide.</p>\n<p>Admin UI now uses the published product name <strong>Ramblers Team Emails</strong> end to end (settings, bulk load, upload history, match labels). Internal host paths, config keys and Mongo field names stay as they are. Member bulk load and bulk delete show visual progress bars with percentage complete, clearer success and error wording, and safer matching that only updates members that already have an id. Bulk delete and Brevo contact create or update are faster under rate limits.</p>\n<p>Upload history can be managed from Member Bulk Load → Manage History: filter by date range, select sessions with checkboxes, and delete the selection (with member action rows). Diagnostics still supports list and clear-all for admins. Upload History filters sit inside the panel, sub-tabs and members populate on first open, and large tables scroll within the browser window. Mail Settings → List Settings shows live progress when syncing with Brevo.</p>\n<h2>Supporter fields (Team Emails 1.0.0)</h2>\n<p>What NGX does with each published supporter field in this release, and known follow-ups. Local member data that Ramblers does not return is preserved.</p>\n<table>\n<thead>\n<tr>\n<th>Supporter field</th>\n<th>NGX use in this release</th>\n<th>Future / not yet</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><code>memberRef</code></td>\n<td>Primary stable identity for match, bounce writeback and unsubscribe preparation; mapped to <code>salesforceMemberRef</code> on the load snapshot</td>\n<td>Keep as the stable identity for non-members; confirm any Head Office identifier semantics still open</td>\n</tr>\n<tr>\n<td><code>contactId</code></td>\n<td>Mapped to <code>salesforceId</code>; used for match; written onto the member record on apply</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>membershipNo</code></td>\n<td>Mapped to <code>membershipNumber</code>; match key; applied when changed</td>\n<td>Nullable members without a number are supported</td>\n</tr>\n<tr>\n<td><code>email</code></td>\n<td>Mapped and applied (when blank locally, or when policy requires); bounce/unsubscribe identity with <code>memberRef</code></td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>title</code></td>\n<td>Mapped; applied when blank locally</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>firstName</code></td>\n<td>Mapped (falls back to <code>friendlyName</code>); applied when blank locally</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>lastName</code></td>\n<td>Mapped; applied when blank locally</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>friendlyName</code></td>\n<td>Used only as <code>firstName</code> fallback when first name is empty (not the same as local <code>displayName</code>)</td>\n<td>Store as its own member field later; do not overwrite <code>displayName</code>; optional seed of display name only when creating or when blank</td>\n</tr>\n<tr>\n<td><code>mobile</code></td>\n<td>Mapped to <code>mobileNumber</code>; applied when blank locally</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>landline</code></td>\n<td>Mapped to <code>landlineTelephone</code>; applied when blank locally</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>membershipStatus</code></td>\n<td>Mapped to <code>memberStatus</code>; applied when changed</td>\n<td>Confirm status vocabulary with Head Office if labels change</td>\n</tr>\n<tr>\n<td><code>memberType</code></td>\n<td>Mapped to <code>type</code> on the load snapshot</td>\n<td>Persist/apply path if admin UI needs it beyond snapshot review</td>\n</tr>\n<tr>\n<td><code>membershipExpiry</code></td>\n<td>Mapped to <code>membershipExpiryDate</code>; applied when changed (including clear rules for lapsed/resigned)</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>membershipJoinDate</code></td>\n<td>Received and validated; not mapped into NGX</td>\n<td>Map if needed for AGM/join reporting without Insight Hub</td>\n</tr>\n<tr>\n<td><code>membershipEndDate</code></td>\n<td>Received and validated; not mapped into NGX</td>\n<td>Map if end-of-membership reporting is required</td>\n</tr>\n<tr>\n<td><code>teamStatus</code></td>\n<td>Mapped to <code>salesforceTeamStatus</code> on the snapshot; drives audience checks (Member / Affiliated / Volunteer / Wellbeing Walker) when present on the member</td>\n<td>Ensure apply always writes team status so audience checks stay accurate after every sync</td>\n</tr>\n<tr>\n<td><code>teamRelationshipFrom</code></td>\n<td>Mapped to <code>salesforceTeamRelationshipFrom</code> on the snapshot</td>\n<td>Persist/apply if history of team relationship is needed in admin</td>\n</tr>\n<tr>\n<td><code>wellbeingWalker</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply for filters and reporting</td>\n</tr>\n<tr>\n<td><code>walkLeader</code></td>\n<td>Mapped on the snapshot only (not yet applied to the member record). NGX “walk leader” filters and pickers still use who has led walks, event assignment and Walks Manager contact names</td>\n<td>Persist the flag and use it as an extra label or soft signal (e.g. member admin badge). Do not replace activity-based leader lists, rematch or walk edit permissions with this boolean alone</td>\n</tr>\n<tr>\n<td><code>volunteerRoles[]</code></td>\n<td>Received and validated; not mapped into NGX</td>\n<td>Phase Two: roles, start dates, walk-leader status and Wellbeing Walks role</td>\n</tr>\n<tr>\n<td><code>emailConsent</code> + <code>doNotEmail</code></td>\n<td>Combined into <code>emailMarketingConsent</code> (<code>true</code> only if consent and not do-not-email); <code>doNotEmail</code> kept on the snapshot</td>\n<td>Granular marketing consent remains Insight Hub / Phase Two</td>\n</tr>\n<tr>\n<td><code>emailConsentLastUpdated</code></td>\n<td>Mapped to <code>emailPermissionLastUpdated</code>; applied when changed</td>\n<td>—</td>\n</tr>\n<tr>\n<td><code>postConsent</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply; <code>postConsentLastUpdated</code> not mapped yet</td>\n</tr>\n<tr>\n<td><code>postConsentLastUpdated</code></td>\n<td>Received and validated; not mapped</td>\n<td>Map with post consent if post marketing is used</td>\n</tr>\n<tr>\n<td><code>phoneConsent</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply; <code>phoneConsentLastUpdated</code> not mapped yet</td>\n</tr>\n<tr>\n<td><code>phoneConsentLastUpdated</code></td>\n<td>Received and validated; not mapped</td>\n<td>Map with phone consent if used</td>\n</tr>\n<tr>\n<td><code>emailConsentWellbeingWalks</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply for Wellbeing Walks audiences</td>\n</tr>\n<tr>\n<td><code>noWalkProgram</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply for programme suppression rules</td>\n</tr>\n<tr>\n<td><code>noCampaigning</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply for campaign suppression</td>\n</tr>\n<tr>\n<td><code>noSurveys</code></td>\n<td>Mapped on the snapshot</td>\n<td>Persist/apply for survey suppression</td>\n</tr>\n<tr>\n<td><code>canViewMemberData</code></td>\n<td>Mapped; enforced when sending protected member data / addresses (sender must have it)</td>\n<td>Ensure every sync writes the flag onto the matched member record</td>\n</tr>\n<tr>\n<td><code>canEmailMembers</code></td>\n<td>Mapped; enforced for Member / Affiliated audiences</td>\n<td>Same persist/apply as above</td>\n</tr>\n<tr>\n<td><code>canEmailVolunteers</code></td>\n<td>Mapped; enforced for Volunteer audiences</td>\n<td>Same persist/apply as above</td>\n</tr>\n<tr>\n<td><code>canEmailWellbeingWalkers</code></td>\n<td>Mapped; enforced for Wellbeing Walker audiences</td>\n<td>Same persist/apply as above</td>\n</tr>\n<tr>\n<td><code>canViewMemberDate</code></td>\n<td>Received and validated (contract field); not used by NGX</td>\n<td>Confirm with Head Office whether this is intentional vs <code>canViewMemberData</code>; ignore or map once clarified</td>\n</tr>\n<tr>\n<td>Bounce payload (<code>memberRef</code>, email, hard/soft)</td>\n<td>Hard/soft Brevo bounces written back with audit and retries</td>\n<td>—</td>\n</tr>\n<tr>\n<td>Unsubscribe payload (<code>memberRef</code>, email)</td>\n<td>Prepared against the contract; <strong>disabled by default</strong> until Head Office confirms scope</td>\n<td>Enable when preference and team vs organisation-wide behaviour are confirmed</td>\n</tr>\n<tr>\n<td>Disappeared supporters</td>\n<td>Counted only; not deleted, expired or prepared for apply</td>\n<td>Removal semantics only if Head Office defines them</td>\n</tr>\n<tr>\n<td>Not on Team Emails 1.0.0 (e.g. postcode, joint membership, group/area/other marketing consent)</td>\n<td>Not returned by the API; local values kept; Insight Hub import still used</td>\n<td>Phase Two fields needed to retire Insight Hub exports</td>\n</tr>\n</tbody></table>\n<h2>At a glance</h2>\n<ul>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/328\">#328</a> replaces the legacy client with <code>GET /get_supporters</code>, <code>/unsubscribe</code> and <code>/bounced_email</code> and validates every request and response against <code>@ramblers/sf-contract</code>.</li>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/329\">#329</a> reconciles complete supporter snapshots while preserving local fields, counting unexplained disappearances without preparing them for apply, and supporting affiliated members, volunteers and Wellbeing Walkers without membership numbers.</li>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/330\">#330</a> makes <code>canViewMemberData</code>, <code>canEmailMembers</code>, <code>canEmailVolunteers</code> and <code>canEmailWellbeingWalkers</code> effective safeguards for protected member data and audiences.</li>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/331\">#331</a> sends hard and soft bounce reports to Ramblers with idempotent audit records and bounded retries for temporary failures.</li>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/332\">#332</a> prepares Ramblers unsubscribe writeback using <code>memberRef</code> and email but keeps it behind an explicit disabled-by-default switch pending scope confirmation.</li>\n<li><a href=\"https://github.com/nbarrett/ngx-ramblers/issues/333\">#333</a> adds contract, mapping, snapshot, permission and writeback tests together with a rehearsal, production cutover and rollback runbook.</li>\n<li>Insight Hub import remains available throughout transition and remains necessary for Phase Two fields that are absent from Team Emails 1.0.0.</li>\n<li>System Settings and Member Bulk Load label the integration <strong>Ramblers Team Emails</strong>, with URL slug <code>ramblers-team-emails</code> and automatic rewrite from legacy <code>salesforce</code> / <code>head-office-supporter-import</code> query values.</li>\n<li>Bulk load and bulk delete show striped progress bars; bulk wording uses <strong>Processed</strong> / <strong>Finished processing</strong> rather than per-member success noise.</li>\n<li>Bulk-load matching requires an existing member id, so incomplete rows without ids are not treated as updates.</li>\n<li>Save failures and unique-index races show a clear <strong>Save failed</strong> review message instead of a misleading <strong>Updated</strong> success.</li>\n<li>Manage History lets file or member admins filter sessions by date, tick individual sessions and delete the selection; member action rows for those sessions are removed too.</li>\n<li>Upload History filters live inside the tab panel; Members uploaded and Member actions headings and member rows load on first open.</li>\n<li>Bulk-load tables scroll within the viewport so the panel stays on screen.</li>\n<li>Member bulk-load audit sessions can also be listed and cleared under System Settings → Diagnostics.</li>\n<li>Mail Settings → List Settings shows alert and progress feedback while syncing contacts with Brevo.</li>\n<li>Brevo contact delete, create and batch update run with documented 10 RPS, 429 cooldown, and concurrent delete without remote event-history snapshots for large bulk selections.</li>\n</ul>\n"}