Skip to content

Notifications

Revel has a multi-channel notification system that delivers messages through in-app notifications, email, and Telegram. The system is designed to be extensible, user-configurable, and respectful of user preferences.

Architecture

flowchart LR
    Trigger["Event Trigger<br/>(service layer)"] --> Dispatcher["Notification Dispatcher"]
    Dispatcher --> InApp["In-App Channel"]
    Dispatcher --> Email["Email Channel"]
    Dispatcher --> Telegram["Telegram Channel"]

    InApp --> DB[(Database)]
    Email --> Celery["Celery Worker"]
    Telegram --> Celery

    Celery --> SMTP["SMTP Server"]
    Celery --> TGBot["Telegram Bot API"]

    Prefs["User Preferences"] -.->|"filter channels"| Dispatcher

Channels

Channel Delivery Templates Notes
In-App Immediate (database) notifications/templates/notifications/in_app/ Stored as model instances, queryable via API
Email Async (Celery) Django email templates Supports HTML and plain text
Telegram Async (Celery) notifications/templates/notifications/telegram/ Markdown-formatted messages via bot

User preferences control everything

Users can configure which channels receive which notification types. The dispatcher respects these preferences before sending. Notifications also support digest mode for batching.

Notification Types

The following notification types are supported across all channels:

Event Notifications

Type Description
event_cancelled An event the user RSVP'd to has been cancelled
event_open An event is now open for registration
event_reminder Reminder before an upcoming event
event_updated Event details have changed

Invitation & Membership

Type Description
invitation_received User received an event invitation
invitation_request_created Someone requested an invitation (staff notification)
membership_promoted User's membership role was upgraded
membership_removed User was removed from an organization
membership_request_approved Membership request was approved
membership_request_rejected Membership request was rejected
membership_request_created Someone requested membership (staff notification)
membership_granted User was granted membership directly

Tickets & Payments

Type Description
ticket_created Ticket was successfully created
ticket_cancelled Ticket was cancelled
ticket_refunded Ticket payment was refunded
ticket_updated Ticket details were updated
payment_confirmation Payment was processed successfully
rsvp_confirmation RSVP was confirmed
rsvp_updated RSVP details were updated
rsvp_cancelled RSVP was cancelled

ticket_checked_in is no longer dispatched (1.64.0)

The ticket_checked_in enum value (and its template/context scaffolding) still exists, but no notification is sent on check-in — the holder is already at the event, so the message was pure noise.

Transactional types (bypass the digest)

The transactional types (TRANSACTIONAL_TYPES in notifications/enums.py) are the payment, ticket and subscription lifecycle types: payment_confirmation, ticket_created, ticket_cancelled, ticket_refunded, and the five subscription_* receipts and alerts.

These are time- and money-sensitive, so they always deliver immediately and bypass the digest cadence. Together with account_banned and system_announcement they form MANDATORY_TYPES, which users can't opt out of. See Email Sending & Opt-Out Policy. Every other type honours the user's digest setting (digest users get the in-app notification now and the email in the next digest sweep).

The three staff_ticket_* alert types and the two org_custom_domain_* owner types (White-Label) are also in TRANSACTIONAL_TYPES, so they skip the digest, but they are not in MANDATORY_TYPES: the recipient can opt out of them.

Waitlist & Availability

Type Description
waitlist_spot_available A reserved offer was issued to the user. Carries expires_at, is_cutoff_batch, and event context. See Advanced Waitlist.

Questionnaires

Type Description
questionnaire_submitted A questionnaire submission was received (staff notification)
questionnaire_evaluation_result Evaluation result sent to the user

Potluck

Type Description
potluck_item_created A new potluck item was added
potluck_item_created_and_claimed A potluck item was added and claimed in one action
potluck_item_claimed Someone claimed a potluck item
potluck_item_unclaimed A potluck claim was removed
potluck_item_updated A potluck item was updated
potluck_item_deleted A potluck item was deleted

Whitelist Requests

Type Description
whitelist_request_created User requested whitelist access (staff notification)
whitelist_request_approved Whitelist request was approved
whitelist_request_rejected Whitelist request was rejected

Account

Type Description
account_banned User's account has been banned from the platform

Organization setup

Type Description
org_setup_nudge Capped reminder to the owner of a stalled organization (forgotten draft, private profile, no events, check-in, gone quiet). Opt-out-able. See Org setup nudges
org_custom_domain_active The organization's white-label custom domain went live (PENDING to ACTIVE). To the owner only; opt-out-able and transactional (skips the digest). See White-Label
org_custom_domain_failed The custom domain failed (PENDING or ACTIVE to FAILED), with Cloudflare's messages and DNS hints. To the owner only; opt-out-able and transactional. The settings link always points to the platform

Announcements

Type Description
org_announcement Organization-wide announcement
system_announcement Platform-wide notice (Terms of Service, privacy policy, operational). Can't be opted out of, so never promotional

Announcements can be sent now or scheduled

Org announcements can be sent immediately, scheduled for a future absolute or event-relative time, and optionally re-sent to attendees who sign up after the first send — all delivered as org_announcement notifications through this dispatcher. See Scheduled Announcements.

Following

Type Description
new_event_from_followed_org A followed organization published a new event
new_event_from_followed_series A new event was added to a followed series
organization_followed Someone followed an organization (org staff notification)
event_series_followed Someone followed an event series (org staff notification)

Template Structure

Templates are organized by channel under src/notifications/templates/notifications/:

notifications/
  templates/
    notifications/
      in_app/
        event_cancelled.md
        event_open.md
        ...
      email/
        event_cancelled.html
        event_cancelled.txt
        ...
      emails/
        digest.html
        digest.txt
      telegram/
        event_cancelled.md
        event_open.md
        ...

Template formats

Both in-app and Telegram templates use Markdown (.md files). Email templates use Django's standard template system with .html and .txt variants.

User Preferences

Users configure their notification preferences per channel and per type. The preference model supports:

  • Per-channel toggles: Enable/disable each channel independently
  • Per-type toggles: Fine-grained control over which notifications to receive
  • Digest mode: Batch notifications into periodic summaries instead of sending individually (does not apply to the transactional types, which always deliver immediately)
  • Per-organization announcement mute: Stop one organization's announcements without touching anything else

Defaults

New users receive most notification types on all available channels by default. Exceptions: potluck notifications are restricted to in-app only (no email or Telegram) and guest users have potluck notifications disabled entirely. Telegram notifications always require the user to have linked their Telegram account.

Implementation Details

The notification system is implemented in notifications/service/, which includes:

  • Dispatcher: Routes notifications to the appropriate channels based on user preferences
  • Eligibility checks: Determines which users should receive a given notification (e.g., only attendees of an event)
  • Reminder scheduling: Schedules event reminders via Celery Beat periodic tasks
  • Digest batching: Aggregates notifications into periodic digest emails
  • Unsubscribe handling: Manages per-type and per-channel opt-outs, one-click List-Unsubscribe, the per-organization announcement mute and the email suppression list (see Email Sending & Opt-Out Policy)

Notifications are triggered from the service layer via Django signals and direct dispatcher calls. Delivery for email and Telegram channels is always asynchronous via Celery tasks.

Digest email (overhauled in 1.64.0)

The digest groups its notifications by human-readable type label (e.g. "Event Reminder"). Each item carries its body, a timestamp, and a "View details" link derived from the notification's context, and the whole email is restyled to match the transactional email templates. See NotificationDigest._build_notification_groups in notifications/service/digest.py.

Event times render in the event's timezone (1.64.0)

Event times in notifications and emails are always rendered in the event's own timezone (via format_event_datetime), not the recipient's or the server's.

FEATURE_TELEGRAM strips the Telegram channel platform-wide

When FEATURE_TELEGRAM is off (self-host without a bot), the dispatcher strips the Telegram channel from every notification's delivery list — no Telegram delivery is ever enqueued, regardless of a user's per-channel preference. See Telegram Bot → Configuration.

Admin Pings (Pushover & Discord)

Separate from the main user-facing notification system, Revel dispatches admin-only pings when operationally interesting events happen on the platform. These never reach end users and do not create Notification records; they go directly to Pushover and/or a Discord webhook.

Event Pushover content Discord content
New user signs up User email, referrer email (if any), running user count Generic "A new user joined" + running user count (no PII)
New organization created Org name, owner email, running org count Org name, owner email, running org count

Each channel self-skips when its config is missing:

  • Pushover requires PUSHOVER_USER_KEY + PUSHOVER_APP_TOKEN.
  • Discord requires DISCORD_ADMIN_WEBHOOK_URL.

Dispatch is gated by SiteSettings flags:

  • notify_user_joined → new-user pings (both channels)
  • notify_organization_created → new-organization pings (both channels)

Tasks live in accounts/tasks.py (user) and events/tasks.py (organization); signals in the corresponding signals.py dispatch them via transaction.on_commit so any related rows (e.g., Referral) are visible when the task runs. All tasks retry with exponential backoff (½/4 min, max 3 attempts).