Notification service

Introduction#

NotificationService fans out across Telegram and Web Push. Routing, batches, preference filters, retries, and when a notice gets marked sent.

Project structure#

The notification system is organized around a service-oriented architecture :

  • NotificationService acts as the orchestrator and router for multiple channels.
  • Channel services implement a simple interface contract (channel_name property and broadcast/send methods).
  • DatabaseService manages persistent state for notices and users.
  • Clients encapsulate low-level interactions with external APIs (Telegram Bot API).
  • Runners and servers coordinate execution and expose endpoints for triggering notifications.

Core components#

  • NotificationService: Central coordinator that routes notifications to enabled channels and performs batch processing of pending notices.
  • TelegramService: Implements channel-specific logic for Telegram, including message formatting, chunking, and broadcasting to users.
  • WebPushService: Implements channel-specific logic for web push notifications, including VAPID authentication and subscription management.
  • DatabaseService: Provides access to notices and users collections, including retrieval of unsent notices and marking them as sent.
  • TelegramClient: Low-level client for Telegram Bot API with retry and rate-limit handling.
  • NotificationRunner: CLI runner that wires dependencies and triggers sending of unsent notices.
  • WebhookServer: Exposes REST endpoints for programmatic notification dispatch and subscription management.

Architecture overview#

The notification workflow begins with pending notices stored in MongoDB. The NotificationService retrieves unsent notices and broadcasts them to enabled channels. TelegramService formats and sends messages to users, while WebPushService delivers push notifications to subscribed browsers. Results are recorded back to the database to mark notices as sent.

Detailed component analysis#

NotificationService#

  • Responsibilities:
  • Aggregates multiple channels and routes notifications accordingly.
  • Performs batch processing of unsent notices.
  • Broadcasts messages to specified channels and records delivery outcomes.
  • Key methods:
  • add_channel: Adds a channel implementation.
  • send_to_channel: Sends a message to a specific channel by name.
  • broadcast: Broadcasts to specified channels or all if none specified.
  • send_unsent_notices: Retrieves unsent notices and sends them to target channels, marking as sent upon success.
  • send_new_posts_to_all_users: Alternative entry point for scheduled jobs.

TelegramService#

  • Responsibilities:
  • Implements channel_name property for routing.
  • Formats messages for Telegram (MarkdownV2 and HTML).
  • Splits long messages into chunks respecting Telegram limits.
  • Broadcasts to all active users with rate limiting.
  • Retries without formatting on failures.
  • Key methods:
  • send_message: Sends a message to default chat with optional parse_mode.
  • send_to_user: Sends a message to a specific user.
  • broadcast_to_all_users: Iterates active users and sends messages with throttling.
  • Message formatting helpers: convert_markdown_to_telegram, convert_markdown_to_html, escape_markdown_v2, split_long_message.

WebPushService#

  • Responsibilities:
  • Implements channel_name property for routing.
  • Checks availability of pywebpush and VAPID keys.
  • Broadcasts to all users with push subscriptions.
  • Handles subscription removal for expired endpoints.
  • Manages subscription persistence (save/remove/get_public_key).
  • Key methods:
  • send_message: Broadcasts to all subscriptions.
  • send_to_user: Sends to a user’s subscriptions.
  • broadcast_to_all_users: Iterates users and subscriptions.
  • _send_push: Sends a single push with VAPID claims and handles WebPushException.

DatabaseService and user management#

  • Responsibilities:
  • Provides get_unsent_notices and mark_as_sent for queued notifications.
  • Supplies get_active_users for broadcasting.
  • Manages user registration and deactivation.
  • Integration with NotificationService:
  • NotificationService relies on DatabaseService to fetch unsent notices and mark them as sent after successful delivery.

NotificationRunner and CLI integration#

  • Responsibilities:
  • Creates and wires dependencies (TelegramService, WebPushService, DatabaseService).
  • Initializes NotificationService with selected channels.
  • Executes send_unsent_notices and returns results.
  • CLI integration:
  • main.py subcommand “send” invokes send_updates with telegram/web flags.

WebhookServer integration#

  • Responsibilities:
  • Exposes endpoints to trigger notifications programmatically.
  • Provides subscription management for web push.
  • Integrates NotificationService and WebPushService via dependency injection.
  • Endpoints:
  • POST /api/notify: Broadcast to specified channels.
  • POST /api/notify/telegram: Telegram-only.
  • POST /api/notify/web-push: Web push-only.
  • POST /api/push/subscribe and /api/push/unsubscribe: Manage subscriptions.
  • GET /api/push/vapid-key: Retrieve VAPID public key.

Dependency analysis#

  • Coupling:
  • NotificationService depends on channel implementations via channel_name and broadcast/send contracts.
  • Channel services depend on DatabaseService for user and subscription data.
  • TelegramService depends on TelegramClient for API calls.
  • Cohesion:
  • Each service has a single responsibility: routing (NotificationService), channel-specific delivery (TelegramService/WebPushService), and persistence (DatabaseService).
  • External dependencies:
  • Telegram Bot API (requests).
  • Optional web push library (pywebpush) with graceful degradation.
  • MongoDB via PyMongo.

Performance considerations#

  • Rate limiting:
  • TelegramService applies throttling between user sends to avoid rate limits.
  • TelegramClient handles 429 responses with Retry-After header.
  • Message chunking:
  • TelegramService splits long messages into chunks respecting Telegram’s character limits.
  • Graceful degradation:
  • WebPushService disables itself if pywebpush is unavailable or VAPID keys are missing.
  • Batch processing:
  • NotificationService processes unsent notices in batches and marks as sent upon success.
  • Caching and reuse:
  • Settings are cached to reduce repeated environment parsing.
  • Indexing:
  • MongoDB collections are indexed for frequent queries (e.g., notices by sent flags, users by user_id).

Troubleshooting guide#

  • Telegram configuration issues:
  • Missing bot token or chat ID leads to early return in TelegramService and TelegramClient.
  • Rate limiting: TelegramClient retries with exponential backoff and respects Retry-After.
  • Web push configuration issues:
  • Missing pywebpush or VAPID keys disables web push functionality.
  • Expired subscriptions (404/410) are handled by removing them.
  • Database connectivity:
  • DBClient requires MONGO_CONNECTION_STR; connection failures are logged and raised.
  • DatabaseService wraps operations with try/catch and logs errors.
  • Notification delivery failures:
  • NotificationService logs errors per channel and continues processing remaining posts.
  • WebhookServer returns HTTP 500 on exceptions with error details.

Conclusion#

NotificationService routes to channel services, degrades when one channel is down, and marks sent only after a real success. Keep channel logic inside TelegramService / WebPushService.