Changelog
What changed, and why.
Every release across the CRM, Gala, and the retreat and event apps, grouped by product.
September 2026
LatestRetreat
A retired event in the retreat marketing site's Past Events list no longer opens an "Event Not Found" page — past events now have their own detail page — alongside a refresh of the site's upcoming-event content.
Platform, Integrations & Reliability
Mailchimp webhook deliveries no longer fail on list-scoped events. The endpoint required a subscriber address on every payload, so campaign notifications and address-change (upemail) events were rejected as malformed — producing a sustained webhook error rate during a routine campaign send, and discarding the evidence needed to tell a harmless ping from a lost unsubscribe. Address changes now resolve against the address the CRM already holds, and a change event missing that address fails loudly instead of being acknowledged and silently dropped. Segmentation that feeds dynamic tags and report presets is accurate again.
Next.js was upgraded across all three apps to clear two critical security advisories, including an unauthenticated remote-code-execution issue in the image optimization API.
August 2026
[release date — to confirm]The AI Assistant became a full conversational reporting surface and the report catalog was rebuilt around one-click presets; staff gained their first notification and digest emails; a broad usability-audit remediation reshaped navigation, overlays, onboarding, and first-run defaults; and every giving calendar boundary now resolves in the organization's own timezone.
CRM
A docked conversational assistant now answers questions about supporters, giving, and events in plain language, with every number sourced from the same deterministic engine behind the rest of the CRM.
The assistant runs a tool loop over the semantic layer rather than generating figures itself, so answers carry provenance receipts showing which definitions and filters produced them, and result cards link through to the full filtered list.
A person lookup answers "what has Jane given?" — resolving a name, disambiguating when several people match, and showing that person's giving summary and recorded connections, each displayed per person and never aggregated.
The assistant also answers event registration questions ("how many people are signed up for the pool party?") using the same figures the event page shows, with an explicit caveat where the underlying data can't support the precision the question implies.
Supporter names, emails, and amounts are rendered in the browser only and never sent to the model; access is re-checked on every tool call, so a revoked role or removed membership stops answers immediately.
The reports surface was rebuilt as a launcher — searchable, with reports grouped by the staff job each one serves, plus per-user Recent and renameable Saved reports.
The curated catalog grew from four contribution presets to eleven, covering overview, action-needed, fundraising performance, and people reports; each is a real report specification run through the same validation and compute path as a hand-built one.
One definition of "new supporter" now applies everywhere, so a supporter whose giving is admission or sponsorship no longer appears under "No contributions recorded" beside their own non-zero dollars.
Report detail gained a print-quality layout, interactive parameters that can be adjusted without rebuilding the report, supporting records on otherwise thin results, and a fundraising snapshot trend.
The older natural-language "Ask AI" report interpreter was retired; the docked assistant now owns open-ended questions and the reports page is deterministic throughout.
Each staff member can now choose which notifications they receive, per organization, from a live settings section on their account page.
Activity alerts cover new contributions (optionally only above a threshold), confirmed event and retreat orders, cancellations, and form submissions; operations alerts cover Stripe webhook failures, refunds needing attention, and import problems, and are limited to owners and admins.
A daily digest summarizes yesterday's activity and what still needs attention, rendered per recipient so one colleague's snoozed item stays open for another.
A weekly Monday report covers the previous week with week-over-week comparisons.
Both are scoped to the organization's own timezone, send exactly one message per window, and skip a quiet window entirely rather than sending an empty one.
A person's email addresses are now first-class records. Every valid address has exactly one owner within an organization, staff can add addresses, promote a secondary to primary, and remove one explicitly, and a conflict names the record that already holds the address and links to it. Automated intake — webhooks, registration, CSV import — never fails on a conflict: it files the record for duplicate review instead of dropping a payment.
Staff can now run duplicate detection themselves from the duplicates queue, with live progress and a last-scan timestamp, plus a per-person "Check for duplicates" action. Merges across every path now preview which record is kept and which is deleted, confirm before merging, and report both names afterward.
People can be filtered by "Not subscribed" and added to the connected Mailchimp audience in bulk, with a per-person summary of what happened.
Navigation and labels were reworked — dead links from global search, the contributions table, and the email-health banner now resolve to real records; search is debounced with proper searching and no-results states; primary actions moved into page headers; and the left nav and search quick links share one definition so they cannot drift.
Every form-bearing overlay now warns before discarding unsaved changes on any dismissal path and refuses to close mid-save, native browser confirmation prompts were replaced throughout with confirmations that name the actual facts — the amount, who receives it, which rows disappear — and person-detail tabs are now addressable by URL.
Currency and date display moved to one shared implementation, and user-facing copy follows one vocabulary — people who give are "supporters," registered people are "attendees," and headings, buttons, and toasts use consistent sentence case.
A design-system pass removed the remaining hard-coded colors across the CRM and both public apps so surfaces flip correctly between light and dark, added accessible names to icon-only controls, and made card-style list items properly reachable by keyboard.
Contributions can be filtered by amount, and people by Mailchimp status and email opt-out — all three usable in saved views and smart tags.
Giving calendar boundaries now resolve in the organization's timezone. "This year" resolved to January 1 at UTC midnight, so a contribution recorded December 31 at 6pm Pacific counted in the following fiscal year — and because the same calculation was reimplemented in several places, the dashboard, campaigns, contributions, and reports were all wrong in the same way. Year, month, and week boundaries now move together, report ranges display in the organization's zone, and imported calendar dates were migrated so they stay in the year the file stated.
Users without CRM access — retreat staff in particular — no longer hit a blank "This page couldn't load" screen on every route. A visibility check in the root layout was throwing instead of answering, and a separate issue left super admins without a membership row stuck on a permanently blank page.
Opted-out supporters can no longer be emailed from an acknowledgements or thank-you batch send. The pending thank-you list, the dashboard signals behind it, and the batch send itself each now honor the send-suppression flag.
Supporters first created by a payment or registration are now indexed for duplicate detection. They had never been, so every supporter who first arrived through checkout was permanently invisible as a merge candidate and a later hand-entered record for the same person produced no matches.
The global search box no longer loses focus and drops characters while typing.
Contribution records created by every path — manual entry, import, Stripe, Gala, Gravity Forms, retreat, and event orders — now consistently produce their search entry, activity entry, and giving totals, closing gaps where some paths silently skipped one.
Retreat
First-run defaults were corrected across retreat setup — creating a retreat now requires real start and end dates instead of inventing them, early-bird pricing defaults off and cannot advertise a $0 saving, the publish control is a labelled switch with an unsaved-change hint, and cabin management moved from Settings into the Inventory tab.
The public cabin chooser no longer shows blank space above a disabled button when no cabins are available — it distinguishes "registration opens soon" from inventory that is access-code gated and points at the code field.
Returning from Stripe with the browser Back button no longer lands on an empty registration wizard with every registrant's details lost; the saved draft is now cleared only once the order is confirmed.
Events
Attendees can manage their own registration from their ticket, and staff can re-send confirmations and tickets on demand, with per-field visibility and server-side gates aligned so the manage page shows only what the event actually collects.
A person's event history now shows which Gala table they were seated at, with the table name and tier, resolved for both table owners and assigned guests.
Issuing an event refund now confirms first, naming the amount, the purchaser who receives it, the order it came from, and the tickets being removed from the roster. Free tickets get the same gate with cancellation wording.
Events now carry a publish signal — Published, Public (ticketing off), or Not published — on both the detail header and the index cards, reading the same liveness rule the public portal enforces, so staff can no longer see "Published" while visitors see a ticketing-off page.
Pending registrations now explain themselves on the roster and the outstanding-balance alert: abandoned checkouts, holding no inventory, expiring automatically.
An event record no longer becomes unreachable as soon as it has one registration. The roster was minting a signed ticket link for every row, so a missing signing secret took down the entire event — attendees, money, every tab. Links are now minted on demand for the one attendee being viewed, and a signing failure degrades to no link instead of an unusable page.
Completing a registration no longer leaves the wizard restorable at the confirmation step with a live "Confirm Registration" button; the saved draft clears once the order is confirmed, and only for the registration that order actually completed.
Platform, Integrations & Reliability
Every public event and retreat page now carries a consistent footer with the organization's support contact, tax ID and disclaimer, refund policy, and privacy and terms links, editable in CRM settings. All three apps gained branded error pages that name a support contact and a reference code instead of showing a raw error screen, and unknown retreat links now return a branded 404 instead of an unbranded dead end. Payment failures at checkout now explain what went wrong and name the organization's own support address.
Access changes (invitations, role changes including the previous role, removals), money reversals (refunds, cancellations, retroactive coupons, post-checkout changes), and configuration changes (email settings, organization settings, Mailchimp connection, host bindings) are now recorded for incident investigation. Settings entries record which fields changed, never their values.
Tenant credentials were removed from the database entirely. The nine credential columns were dropped from the organization record, so the table can no longer hold a credential and no projection can leak one, and automated checks now refuse any attempt to reintroduce one.
The authorization boundary was rebuilt on an explicit capability model. Every public backend function now carries either a capability gate or a reviewed, machine-checked public contract declaring its identity, tenant binding, expiry, replay, rate-limit, and test coverage — and the legacy role-comparison helpers were deleted so the pattern cannot come back.
Closed a path by which content submitted through public checkout could run script inside the CRM and reach an administrator's session. Message HTML is now sanitized and then interpolated with escaping, in that order; every email preview renders inside a fully sandboxed frame; and the CRM enforces a content security policy. Automated checks now fail the build on any new unsafe rendering path, and full-history secret scanning runs on every change.
Stripe webhook handling was rebuilt around a single dispatch record per event. Four defects went with it, the most serious being that enabling the platform's test-mode flag on production made every live Stripe event fail admission and return success — so contributions, retreat confirmations, and ticket purchases in that window were lost permanently with no retry. Events that never reach a final state are now recoverable, failed events retain their payload for diagnosis, and a failure is filed against the system that actually raised it.
Delivery status no longer sticks permanently on "failed." A transient send error outranked Resend's own later confirmation, so a delivered email stayed marked failed in the email log and on the person's record. Confirmed delivery events now correct an app-side guess, and an hourly reconciliation sweeps any row that slips through.
Three public event portal queries were returning the entire organization record to the browser, including identifiers naming the organization's credential profile. They now project an explicit field list, and a regression check pins that list so a newly added column cannot ride out to the browser.
Super-admin platform and organization statistics no longer fail outright once an organization passes the backend's query-size limit; the counters are now maintained at write time and rebuilt nightly from source records.
Mailchimp state now matches on any of a supporter's addresses, not just their primary one, so a supporter subscribed under a secondary address is linked instead of showing as unmatched — with a guard so a secondary-address match cannot overwrite person-level state belonging to a different audience member.