{"id":"6a4e5c91ae048adb2cc26564","title":"2026 07 08 Issue 306","path":"how-to/committee/release-notes/2026-07-08-issue-306","contentMarkdown":"# 08-Jul-2026 — Forward emails with attachments, plus inbox and contact-sync improvements [#306](https://github.com/nbarrett/ngx-ramblers/issues/306)\n\n## [build 754](https://github.com/nbarrett/ngx-ramblers/actions/runs/28948820807) — [commit b5d5502](https://github.com/nbarrett/ngx-ramblers/commit/b5d55026b29548b138ccc71c8df687cbf6732ee7)\n\n_____\n\n## What's new\n\nOpening a previously sent email from the Show → Sent list could show no recipients at all, even though the email went out correctly. This happened whenever the message was addressed to external (non-member) recipients, because the saved copy of the message never included them. The same gap affected CC and BCC addresses, file attachments and multi-column layouts - all of which would silently disappear when a draft or sent message was reloaded. Every part of a composed message is now saved and restored faithfully.\n\nInbox messages can now be forwarded. A new Forward button on each inbox message opens the email composer with the subject prefixed Fwd:, a conventional forwarded-message header block quoting the original, and the original attachments pre-attached - you choose the recipients. And emails sent from the composer to external addresses now appear in the inbox as conversations, not just ones started with an inbox Reply: each send is recorded as an outbound thread under the sending committee role, so when the recipient replies, their reply joins the same conversation.\n\nChanging a subscribed member's email address in member admin now updates their Brevo contact instead of leaving it on the old address. The contact is renamed in place - keeping its history, statistics and list memberships - and all other contact details (name, membership number, expiry, username) travel with every update. Contact updates can no longer accidentally create duplicate contacts in Brevo, and any stray duplicate already occupying the new address is snapshotted and removed automatically. The same rules apply to bulk loads from Insight Hub and Salesforce.\n\nLogging in is no longer case-sensitive - a username or email address entered with any capitalisation now works. The member admin modal lowercases email addresses and usernames as they are typed (without the cursor jumping), and dates can be typed or pasted in natural forms like \"Wednesday, 24 February 2027\", with all date pickers now displaying dates in the site's standard long format. On NGX-Lite sites, navbar items added in system settings now appear instead of being silently filtered out. The \"configure it in Mail Settings\" messages in the email composer are now clickable links that open Mail Settings in a new tab.\n\nComposer attachments can now be previewed the same way inbox attachments can - click an attachment to open a draggable, resizable preview of images, PDFs, CSVs and text. The email composer also reads better on a phone: the rich-text formatting toolbar collapses behind a \"Formatting tools\" toggle, attachment names stay on a single line instead of stretching the row, transient notices and validation share one consistent alert box, and the Exit composer button lines up with the rest of the page. The View or Edit Settings / Banner buttons show just their pencil icon, with the full label on hover.\n\n## At a glance\n\n- Sent messages re-opened from the Sent list show the external To, CC and BCC recipients they were sent to; drafts and sent messages keep attachments and multi-column layouts\n- New Forward button on inbox messages\n- opens the composer with the original message quoted and its attachments included\n- Emails sent from the composer to external addresses show in the inbox as sent conversations under the sending role\n- Changing a member's email address in member admin renames their Brevo contact in place; names, membership numbers, expiry dates and usernames also propagate\n- Brevo contact updates can never create duplicate contacts; stray duplicates blocking an email change are cleaned up automatically\n- Login username and email are case-insensitive; member admin lowercases email and username as typed\n- Dates can be typed or pasted naturally (e.g. Wednesday, 24 February 2027) and display in the site's standard format\n- NGX-Lite sites show every navbar item configured in system settings; social events items still depend on local social events being present\n- Composer validation messages linking to Mail Settings open it in a new tab\n- Composer attachments open a draggable, resizable preview (images, PDFs, CSVs, text)\n- the same viewer the inbox uses\n- On mobile the composer's formatting toolbar collapses behind a toggle, attachment names truncate to one line, alerts combine into one box, and the Exit composer button aligns with the page\n- View or Edit Settings / Banner buttons are icon-only with the label as a tooltip\n- Messages saved before this change were stored without recipient information, so their recipients cannot be recovered\n- the recipient count on the Sent list remains accurate\n\n## Technical changes\n\n- serialiseStateForStorage persists externalRecipients, ccRecipients and bccRecipients, the attachments array, groupEventsFilter eventIds/sortBy, and fragmentOrder recursively via serialiseFragment so MULTI_COLUMN fragments keep columns and columnGapPx\n- compose-reply endpoint accepts forward: true, returning a Fwd:-prefixed subject, buildQuotedForwardHtml header block and the message's attachments; inbox Reply/Reply All/Forward consolidated into prepareOutboundCompose; forwarded attachments reuse inbox-attachments S3 URLs (public GET route)\n- no copying\n- performInboxReplyWriteback generalised to performInboxWriteback: without a reply context it resolves the alias from the sender's committee role and records via new recordOutboundMessage, matching an existing thread by references or counterparty+subject or creating one marked outbound and read; findExistingThread/createThread parameterised with counterparty and lastDirection; buildRfc822 lists all recipients and omits empty threading headers\n- contacts-batch-update rewritten around updateBatchContacts (update-only): contacts identified by Brevo id (CreateContactRequest.id added, client sends member.mail.id), attributes.EMAIL performs in-place renames, per-contact listIds replace the importContacts path which silently created contacts for unmatched emails\n- contact-update endpoint updates by contact_id with the full stripped attribute payload and resolves duplicates first: a different contact holding the target email is snapshot-deleted when it has no ext_id or the same ext_id, and rejected with a clear error when it belongs to another member\n- MailListUpdaterService: processContactEmailChanges renames contacts by id before update requests are built (member admin quick sync and updateMailLists reconciliation), sets member.mail.email after create and rename, audits renames and failures, and excludes failed renames from signature stamping so they retry; toCreateContactRequestWithObjectAttributes falls back to member.email when mail.email was never recorded\n- login and reset-password share userNameOrEmailCriteria\n- an escaped, anchored, case-insensitive regex over userName and email\n- LowercaseInputDirective lowercases input in place preserving caret position; member-admin modal applies it to email and username and trims+lowercases both on save\n- Page navigator drops the NGX_LITE_ALLOWED_PATHS allowlist; lite filtering keeps only the hasLocalSocialEvents condition for social paths\n- Date pickers display dddd, D MMMM YYYY; DateUtilsService.parseTypedDate tries TYPED_DATE_INPUT_FORMATS (all UIDateFormat enum members); DatePicker intercepts the change event in the capture phase on the input group, stopping propagation when the flexible parse succeeds and setting bsValue so ngx-bootstrap's stricter parser cannot blank the field\n- Email composer imports RouterLink, activating the previously inert routerLink bindings on Mail Settings validation links\n- New AttachmentPreviewComponent extracted from the inbox preview modal (image/pdf/csv/text, cdkDrag + resize); inbox and composer both drive it via an AttachmentPreview {filename, url, contentType?} shape, removing the duplicated preview code from the inbox; transition:none on the draggable dialog stops Bootstrap's transform transition lagging the drag\n- TiptapMarkdownEditor toolbar collapses under a Formatting tools toggle below 768px via toolbarExpanded; composer attachment pills truncate the filename (white-space:nowrap, min-width:0 down the flex chain) with the full name as a tooltip; workspace notify alert reuses the email-composer-validation-summary container and merges into the same box as the recipients validation\n- composer-workspace-actions buttons reset the global .btn + .btn margin on mobile so the Exit button aligns with the title and stepper; ButtonWrapper gains an iconOnly input that drops the label entirely and serves the title as the tooltip","contentHtml":"<h1>08-Jul-2026 — Forward emails with attachments, plus inbox and contact-sync improvements <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/306\">#306</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/28948820807\">build 754</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/b5d55026b29548b138ccc71c8df687cbf6732ee7\">commit b5d5502</a></h2>\n<hr>\n<h2>What&#39;s new</h2>\n<p>Opening a previously sent email from the Show → Sent list could show no recipients at all, even though the email went out correctly. This happened whenever the message was addressed to external (non-member) recipients, because the saved copy of the message never included them. The same gap affected CC and BCC addresses, file attachments and multi-column layouts - all of which would silently disappear when a draft or sent message was reloaded. Every part of a composed message is now saved and restored faithfully.</p>\n<p>Inbox messages can now be forwarded. A new Forward button on each inbox message opens the email composer with the subject prefixed Fwd:, a conventional forwarded-message header block quoting the original, and the original attachments pre-attached - you choose the recipients. And emails sent from the composer to external addresses now appear in the inbox as conversations, not just ones started with an inbox Reply: each send is recorded as an outbound thread under the sending committee role, so when the recipient replies, their reply joins the same conversation.</p>\n<p>Changing a subscribed member&#39;s email address in member admin now updates their Brevo contact instead of leaving it on the old address. The contact is renamed in place - keeping its history, statistics and list memberships - and all other contact details (name, membership number, expiry, username) travel with every update. Contact updates can no longer accidentally create duplicate contacts in Brevo, and any stray duplicate already occupying the new address is snapshotted and removed automatically. The same rules apply to bulk loads from Insight Hub and Salesforce.</p>\n<p>Logging in is no longer case-sensitive - a username or email address entered with any capitalisation now works. The member admin modal lowercases email addresses and usernames as they are typed (without the cursor jumping), and dates can be typed or pasted in natural forms like &quot;Wednesday, 24 February 2027&quot;, with all date pickers now displaying dates in the site&#39;s standard long format. On NGX-Lite sites, navbar items added in system settings now appear instead of being silently filtered out. The &quot;configure it in Mail Settings&quot; messages in the email composer are now clickable links that open Mail Settings in a new tab.</p>\n<p>Composer attachments can now be previewed the same way inbox attachments can - click an attachment to open a draggable, resizable preview of images, PDFs, CSVs and text. The email composer also reads better on a phone: the rich-text formatting toolbar collapses behind a &quot;Formatting tools&quot; toggle, attachment names stay on a single line instead of stretching the row, transient notices and validation share one consistent alert box, and the Exit composer button lines up with the rest of the page. The View or Edit Settings / Banner buttons show just their pencil icon, with the full label on hover.</p>\n<h2>At a glance</h2>\n<ul>\n<li>Sent messages re-opened from the Sent list show the external To, CC and BCC recipients they were sent to; drafts and sent messages keep attachments and multi-column layouts</li>\n<li>New Forward button on inbox messages</li>\n<li>opens the composer with the original message quoted and its attachments included</li>\n<li>Emails sent from the composer to external addresses show in the inbox as sent conversations under the sending role</li>\n<li>Changing a member&#39;s email address in member admin renames their Brevo contact in place; names, membership numbers, expiry dates and usernames also propagate</li>\n<li>Brevo contact updates can never create duplicate contacts; stray duplicates blocking an email change are cleaned up automatically</li>\n<li>Login username and email are case-insensitive; member admin lowercases email and username as typed</li>\n<li>Dates can be typed or pasted naturally (e.g. Wednesday, 24 February 2027) and display in the site&#39;s standard format</li>\n<li>NGX-Lite sites show every navbar item configured in system settings; social events items still depend on local social events being present</li>\n<li>Composer validation messages linking to Mail Settings open it in a new tab</li>\n<li>Composer attachments open a draggable, resizable preview (images, PDFs, CSVs, text)</li>\n<li>the same viewer the inbox uses</li>\n<li>On mobile the composer&#39;s formatting toolbar collapses behind a toggle, attachment names truncate to one line, alerts combine into one box, and the Exit composer button aligns with the page</li>\n<li>View or Edit Settings / Banner buttons are icon-only with the label as a tooltip</li>\n<li>Messages saved before this change were stored without recipient information, so their recipients cannot be recovered</li>\n<li>the recipient count on the Sent list remains accurate</li>\n</ul>\n<h2>Technical changes</h2>\n<ul>\n<li>serialiseStateForStorage persists externalRecipients, ccRecipients and bccRecipients, the attachments array, groupEventsFilter eventIds/sortBy, and fragmentOrder recursively via serialiseFragment so MULTI_COLUMN fragments keep columns and columnGapPx</li>\n<li>compose-reply endpoint accepts forward: true, returning a Fwd:-prefixed subject, buildQuotedForwardHtml header block and the message&#39;s attachments; inbox Reply/Reply All/Forward consolidated into prepareOutboundCompose; forwarded attachments reuse inbox-attachments S3 URLs (public GET route)</li>\n<li>no copying</li>\n<li>performInboxReplyWriteback generalised to performInboxWriteback: without a reply context it resolves the alias from the sender&#39;s committee role and records via new recordOutboundMessage, matching an existing thread by references or counterparty+subject or creating one marked outbound and read; findExistingThread/createThread parameterised with counterparty and lastDirection; buildRfc822 lists all recipients and omits empty threading headers</li>\n<li>contacts-batch-update rewritten around updateBatchContacts (update-only): contacts identified by Brevo id (CreateContactRequest.id added, client sends member.mail.id), attributes.EMAIL performs in-place renames, per-contact listIds replace the importContacts path which silently created contacts for unmatched emails</li>\n<li>contact-update endpoint updates by contact_id with the full stripped attribute payload and resolves duplicates first: a different contact holding the target email is snapshot-deleted when it has no ext_id or the same ext_id, and rejected with a clear error when it belongs to another member</li>\n<li>MailListUpdaterService: processContactEmailChanges renames contacts by id before update requests are built (member admin quick sync and updateMailLists reconciliation), sets member.mail.email after create and rename, audits renames and failures, and excludes failed renames from signature stamping so they retry; toCreateContactRequestWithObjectAttributes falls back to member.email when mail.email was never recorded</li>\n<li>login and reset-password share userNameOrEmailCriteria</li>\n<li>an escaped, anchored, case-insensitive regex over userName and email</li>\n<li>LowercaseInputDirective lowercases input in place preserving caret position; member-admin modal applies it to email and username and trims+lowercases both on save</li>\n<li>Page navigator drops the NGX_LITE_ALLOWED_PATHS allowlist; lite filtering keeps only the hasLocalSocialEvents condition for social paths</li>\n<li>Date pickers display dddd, D MMMM YYYY; DateUtilsService.parseTypedDate tries TYPED_DATE_INPUT_FORMATS (all UIDateFormat enum members); DatePicker intercepts the change event in the capture phase on the input group, stopping propagation when the flexible parse succeeds and setting bsValue so ngx-bootstrap&#39;s stricter parser cannot blank the field</li>\n<li>Email composer imports RouterLink, activating the previously inert routerLink bindings on Mail Settings validation links</li>\n<li>New AttachmentPreviewComponent extracted from the inbox preview modal (image/pdf/csv/text, cdkDrag + resize); inbox and composer both drive it via an AttachmentPreview {filename, url, contentType?} shape, removing the duplicated preview code from the inbox; transition:none on the draggable dialog stops Bootstrap&#39;s transform transition lagging the drag</li>\n<li>TiptapMarkdownEditor toolbar collapses under a Formatting tools toggle below 768px via toolbarExpanded; composer attachment pills truncate the filename (white-space:nowrap, min-width:0 down the flex chain) with the full name as a tooltip; workspace notify alert reuses the email-composer-validation-summary container and merges into the same box as the recipients validation</li>\n<li>composer-workspace-actions buttons reset the global .btn + .btn margin on mobile so the Exit button aligns with the title and stepper; ButtonWrapper gains an iconOnly input that drops the label entirely and serves the title as the tooltip</li>\n</ul>\n"}