Champagne

Member notifications

Member inbox, per-category channel preferences, manual staff messages, reminders, and session cancellation.

Members open My account → Notifications for their inbox and Preferences for channel settings. The inbox shows read and unread entries, links to the relevant account page, and supports marking one or all entries read, or one entry unread. All / Unread filters the complete inbox before pagination; changing the filter returns to page 1. Page navigation returns focus and scroll to the inbox heading. Pages contain 20 items, newest first (creation time, then ID descending).

query Notifications($clubId: ID!, $page: Int = 1, $unreadOnly: Boolean = false) {
  myNotifications(clubId: $clubId, page: $page, unreadOnly: $unreadOnly) {
    items {
      id
      category
      title
      detail
      startsAt
      timeZone
      body
      href
      createdAt
      readAt
    }
    unreadCount
    hasMore
  }
  myNotificationPreferences(clubId: $clubId) {
    category
    inApp
    email
  }
}
mutation Preferences($clubId: ID!, $input: NotificationPreferenceInput!) {
  setMyNotificationPreference(clubId: $clubId, input: $input) {
    category
    inApp
    email
  }
}
mutation Read($clubId: ID!, $id: ID) {
  markMyNotificationsRead(clubId: $clubId, id: $id)
}
mutation Unread($clubId: ID!, $id: ID!) {
  markMyNotificationUnread(clubId: $clubId, id: $id)
}

All operations require member authentication in the requested club. The recipient comes from the session; the API accepts no recipient ID. Staff cannot use these member operations. IDs must be UUIDs; page defaults to 1 and accepts 1–10,000. unreadOnly defaults to false (including explicit null); true returns only entries with no readAt. Filtering happens before the 20-item page limit and offset. Alternative sorting is not exposed. readAt is nullable; timestamps are ISO 8601 UTC strings. detail contains a nullable snapshot of the member, reservation or plan context. A reservation's start is not written into detail: startsAt (ISO 8601 UTC, nullable) carries it with the club's IANA timeZone, so the member app formats it in the interface language ("Le Rooftop · 20 oct. 2026, 19:00"); both are null for other notifications. href is a relative consumer route. unreadCount covers the complete visible inbox, not just the current page. Omitting id marks all currently unread entries read. Both read-state mutations return whether any row changed. markMyNotificationUnread requires a notification UUID and resets only its readAt to null; it returns false for an already-unread, missing, hidden, or other member’s notification. Neither mutation generates notifications or resends email. markMyCustomerChatRead also marks read the member's unread chat notifications of that conversation (href /account/chats/<chatId>), so a club message counts once in the inbox badge.

NotificationPreferenceInput requires category, inApp, and email. Categories are:

CategoryEvents
reservationReservation creation, status and detail changes
proposalA new or changed staff date proposal
sponsorshipA sponsored person receives a membership (excluding add-ons)
guestThe member's guest accepts an invitation
reminderA confirmed reservation starts within 24 hours
waitlistA place becomes available, or a waiting member books the session
renewalA Stripe subscription's paid period ends within 7 days, or its price changes after one month's notice
sessionA booked or waitlisted session occurrence is canceled
messageA professional sends a reservation chat message
chatA professional starts or answers a customer chat
clubA club administrator sends a manual message from the back office
creditsA credit debit drops the balance below the club's low balance alert

Both channels default to enabled. Saving a category updates only that category. In-app settings apply to future notifications; existing inbox history remains. Email preferences are checked again before a queued email is claimed. Re-enabling a category does not backfill suppressed events. The existing reservation confirmation mail journal respects the reservation.email preference; the notification worker does not send another confirmation email. Notification emails write startsAt like the reservation confirmation email ("Tuesday 27 October 2026 at 10:00", followed by "Times shown in Europe/Paris."), so a member promoted from the waitlist receives both emails with the same date format.

Manual staff messages

Club administrators open a member's profile in the back office and choose Send notification. The dialog offers three categories: Received package and Lost object prefill an editable message, and Other requires a custom one. The member receives the message in the club category through the usual in-app and email channels.

mutation Notify($input: SendPersonNotificationInput!) {
  sendPersonNotification(input: $input) {
    id
    category
    title
    body
    href
    createdAt
  }
}

SendPersonNotificationInput requires personId, clubId, and kind (received_package, lost_object, or other); body is optional. The caller must be an authenticated administrator of clubId, and the person must belong to that club. Both IDs must be UUIDs. The mutation returns the created MemberNotification, or null when the person is not found in the club.

KindTitleDefault body
received_packagePackage receivedA package addressed to you has arrived at the club. Please collect it at the reception.
lost_objectLost object foundAn item that may belong to you was found at the club. Please contact the reception to recover it.
otherMessage from your clubNone; body is required

body is trimmed and replaces the default text when present. It is limited to 2,000 characters and must not contain null characters; an empty or missing body with kind: other returns BAD_USER_INPUT. Titles and default bodies are stored in English and translated by the member app; the back office sends body: null when the prefilled text is left unchanged so that translation still applies. href is always /account/notifications. Each send creates a new entry: there is no deduplication of repeated manual messages. The club in-app and email preferences apply, and the email is delivered by the notification worker like any other category.

Scheduling and delivery

Notification records commit in the business transaction independently of club webhook settings. The existing outbound webhook transport remains available and retains its own selection settings. The API's scoped worker scans each minute, at most 100 due records per event category, and processes up to 100 notification emails. Reminder windows are currently fixed at 24 hours and 7 days. Reservations booked inside the reminder window are eligible on the next scan. Renewal reminders use paidPeriodEnd, not an inferred anniversary; manual memberships and add-ons are excluded. An approaching period end is not proof of a successful future charge.

Unique event keys prevent repeated reminder creation, including when channels are disabled. Rescheduling a reservation or advancing a paid period creates a new reminder key. Before email send, canceled/rescheduled reservation reminders and outdated membership reminders are skipped. Expired reminder emails are also skipped. Proposal emails are sent only while the notified proposal version is still current, awaiting acceptance, and its start time is in the future. Replaced, accepted, canceled, or expired proposals are skipped, including on retry. Inbox entries remain historical records.

When a place opens, the waitlist worker books eligible waiting members through the normal reservation checks. Successful booking removes the waitlist entry and emits waitlist.joined; the notification links to the resulting reservation and asks the member to check its status and any payment due. A member booking directly while on the waitlist receives the same notification.

Notification emails are subject <club name> · <title> and greet the member by first name, followed by the body, the context and a link to the page in the member's club account. Reservation confirmation, security deposit and reservation invitation emails use the same <club name> · … subject. A reservation's context names the session title when the reservation belongs to a titled session, otherwise the category.

"Reservation update" notifications describe the new status in the member app's words: pending is "Your request was sent to the club.", booked is "Your reservation is booked, payment is due.", and confirmed and completed say so. A cancellation adds what happened to the price, read from the reservation when the notification is created: "The price was not refunded." when it was kept past the cancellation notice, "Your payment is being refunded." after a card refund, or "The price returned to your credit balance." after a credit refund; otherwise "Your reservation is canceled.". Email requires the configured mail provider and CONSUMER_ORIGIN. The worker commits a sending claim before contacting the provider. Definite failures retry after one hour; uncertain outcomes and abandoned sending claims become unknown and are not automatically resent. Provider acceptance is not inbox-delivery confirmation. These are in-app and email channels; browser/mobile push and SMS are not configured.

Session occurrence cancellation

mutation CancelSession($clubId: ID!, $categoryId: ID!, $occurrenceId: String!) {
  cancelReservationSession(clubId: $clubId, categoryId: $categoryId, occurrenceId: $occurrenceId)
}

Club admins and staff assigned to the reservation category can cancel a future occurrence. IDs follow reservationAvailability: a custom session ID, or weekly definition ID plus :YYYY-MM-DD in club time. A malformed occurrence ID (not one of those two shapes) is rejected with BAD_USER_INPUT. Invalid scope or a past occurrence returns false; an already-canceled occurrence returns true. The back-office session roster exposes Cancel session with a confirmation step.

Cancellation closes that occurrence to bookings/waitlisting, cancels pending/booked/confirmed reservations and notifies their owners, then removes and notifies its waitlist. Other occurrences of a recurring definition remain available. Reservation lifecycle webhooks still fire, but members receive the session-category notification rather than a second reservation-category cancellation. Paid reservations are refunded by a scoped worker using the existing idempotent Stripe refund operation; failures remain discoverable and retry each minute. Session cancellation is effective while refunds are pending. Deleting a session definition is rejected while active reservations, waitlist entries, or unrefunded payments remain; cancel the affected occurrences first.

Undo marking all notifications read

mutation ReadAll($clubId: ID!) {
  markAllMyNotificationsRead(clubId: $clubId) {
    ids
    readAt
  }
}
mutation UndoReadAll($clubId: ID!, $receipt: NotificationReadReceiptInput!) {
  undoMyNotificationsRead(clubId: $clubId, receipt: $receipt)
}

markAllMyNotificationsRead atomically marks all currently unread in-app notifications for the authenticated member and club, across all pages. It returns the exact changed UUIDs and their UTC ISO timestamp (readAt); ids is empty when nothing changed. Pass that receipt to undoMyNotificationsRead to restore those notifications to unread. Only records still carrying the receipt's timestamp are restored, preserving later read-state changes. The mutation returns the restored UUIDs and is safe to repeat. IDs must be valid UUIDs and readAt must be a UTC ISO timestamp with milliseconds; otherwise the mutation fails with BAD_USER_INPUT. Both operations require member authentication for the selected club and leave previously read entries, other members, notification delivery and email unchanged.

On this page