Prompt
What's a good architecture for sending SaaS notifications?
Latest observation
A good SaaS notification architecture is usually event-driven, channel-agnostic, and preference-aware. The main goal is to let your product emit “something happened” events, then route those events through a notification system that decides who to notify, by which channel, when, and with what template.
Recommended high-level architecture
1) Source systems emit events
Your app services publish domain events such as:
invoice.paidtrial.expiringuser.invitedreport.readysecurity.login_from_new_device
These should be emitted to a queue/stream or event bus, not sent directly to email/SMS/etc.
Why: decouples product logic from notification delivery.
2) Notification service consumes events
A dedicated notification service subscribes to those events and:
- validates the event payload
- resolves recipients
- checks user/org preferences
- determines channels
- chooses templates
- enqueues delivery jobs
This service is the brain of the system.
3) Notification policy engine
This is where you define rules like:
- if event =
trial.expiring, send email + in-app - if user opted out of marketing, skip promotional emails
- if severity = high, send push/SMS
- if workspace timezone is set, schedule for local morning
You can implement this as:
- config-driven rules in DB
- code-based rules
- a hybrid
For SaaS, config-driven tends to scale better.
4) Template/rendering layer
Use a templating system that supports:
- localization
- variables/merge fields
- channel-specific formatting
Example:
- email: HTML + text
- in-app: short rich text
- SMS: concise plain text
- push: title/body/deeplink
Templates should be versioned.
5) Delivery workers per channel
Have separate workers for each channel:
- Email worker → SES/SendGrid/Mailgun
- SMS worker → Twilio
- Push worker → FCM/APNs
- In-app worker → write to DB / websocket / pubsub
- Webhook worker → external callbacks
Each worker should handle:
- retries
- rate limits
- idempotency
- provider failover
- dead-letter queue
6) Notification store
Persist notification records for:
- audit/history
- unread state
- retries/status tracking
- user inbox
- analytics
Typical fields:
- event type
- recipient
- channel
- template version
- status: queued/sent/failed/read
- provider message ID
- timestamps
7) User preference and identity resolution
You need a service/table to map:
- user ID → email, phone, device tokens
- org membership / role
- preferred language
- notification preferences
- quiet hours / timezone
This is critical for correct delivery.
A practical flow
- Billing service emits
invoice.paid - Event lands on Kafka/SQS/PubSub
- Notification service consumes event
- It looks up recipients and preferences
- It decides: send email to billing admin + in-app to account owners
- It renders templates
- It enqueues channel jobs
- Email worker sends through provider
- Status is recorded
- User opens app and sees the same notification in inbox
Important design principles
Idempotency
Events may be delivered more than once. Prevent duplicate notifications using:
- event IDs
- dedupe keys
- unique constraints per recipient/channel/template/event
Fan-out control
One event may produce many notifications. Use async fan-out and batch processing where possible.
Priority levels
Support priorities:
- critical
- transactional
- informational
- marketing
This helps with routing, retries, and throttling.
Rate limiting
Protect users and providers:
- per-user caps
- per-tenant caps
- provider quotas
- bulk send throttling
Separate transactional from marketing
Keep these paths distinct:
- transactional: product-generated, user-expected
- marketing: campaign/engagement driven
This helps with compliance and deliverability.
Observability
Track:
- emitted events
- delivery success/failure
- provider latency
- open/click rates
- opt-outs
- retry counts
Logs + metrics + tracing are important here.
Suggested stack options
Simple/early stage
- App emits events to SQS/RabbitMQ
- Notification service in your backend
- PostgreSQL for notification records
- SendGrid/SES for email
- Twilio for SMS
- Firebase for push
Larger scale
- Kafka/PubSub for event streaming
- Dedicated notification service
- Redis for rate limiting/deduping
- PostgreSQL or DynamoDB for storage
- Worker pools per channel
- Feature-flagged template/routing rules
A good minimum viable design
If you want something robust but not overbuilt:
- Domain events from app services
- Queue for async processing
- Notification service
- Postgres for notification state/preferences
- Email/SMS/push adapters
- Template engine
- Retry + DLQ
- In-app inbox
That’s usually enough for most SaaS products.
If you want, I can also provide:
- a reference architecture diagram
- a database schema
- a microservices vs monolith tradeoff
- a sample event model and API design