Summary
Add a multi-channel notification system so users are alerted when something requires their attention — even when they're not actively looking at the app. Notifications escalate through three channels based on user presence: in-app toast (tab visible), browser push notification (tab hidden), and email via SES (app closed). Triggers are @mentions in discussions and agent questions requiring human input.
Motivation
Today, @mention notifications are ephemeral WebSocket toasts. If the user's tab isn't focused or the app is closed, the notification is silently lost. Users have no way to know something needs their attention unless they're actively watching the screen. This is a problem for a collaborative tool where discussions and agent questions can happen asynchronously. The DynamoDB notifications table already exists in infrastructure but is completely unused: there's no persistent inbox, no browser push, and no email fallback
Detailed design
Triggers (when to notify)
| Event |
Recipients |
| @mention in a discussion |
The mentioned user(s) |
| Agent question requiring human input |
Project owner (+ optionally members, based on preference) |
Channel escalation logic
Event occurs
→ Identify recipient(s) (exclude the author)
→ For each recipient:
├─ Active WS connection + tab visible → In-app toast (existing behavior)
├─ Active WS connection + tab hidden → Browser Notification (Notification API)
└─ No active WS connection → Email via SES
Presence detection
- Server-side (email decision): Query
connections DynamoDB table by UserIdIndex. If user has at least one connection with a valid (non-expired) token → online (use WebSocket). Otherwise → offline (send email).
- Client-side (browser notification decision): The frontend WebSocket handler checks
document.visibilityState. If 'hidden' → fire new Notification(...). If 'visible' → show toast only.
Backend changes
-
Activate the notifications table — add TTL attribute (30-day expiry), define item schema:
PK: userId (S), SK: timestamp (N)
Attributes: id, type ('mention'|'question'), payload (map), read (bool), deliveredVia ('ws'|'email'|'both')
-
Notification dispatcher Lambda — receives events (from discussion Lambda inline call or via EventBridge), persists to notifications table, checks user presence, routes to WebSocket (broadcastToUser) or SES.
-
REST endpoints — GET /notifications (list, paginated), PATCH /notifications/{id} (mark read), GET /notifications/unread-count, PUT /notifications/preferences.
-
SES integration — Terraform module for verified domain, email template (subject: [ProjectName] {byName} mentioned you, body: excerpt + deep link + unsubscribe).
-
User preferences — stored on User vertex or in a new DynamoDB item:
emailNotifications: bool (default: true)
notifyOnMention: always on
notifyOnAgentQuestion: bool (default: true for owners, false for members)
Frontend changes
-
Browser Notification API — request permission on login, fire new Notification() when WebSocket event arrives and document.visibilityState === 'hidden'.
-
Notification inbox — bell icon in header with unread badge, dropdown listing recent notifications with "mark as read" and "jump to thread" actions.
-
Preferences UI — settings page/modal for toggling email and per-event-type notifications.
Architecture diagram
┌────────────────────────────────────────────────────────┐
│ Event Source (discussions Lambda / agent Lambda) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Notification Dispatcher Lambda │
│ 1. Persist to DynamoDB notifications table │
│ 2. Query connections table (user online?) │
│ ├─ Online → broadcastToUser (WebSocket) │
│ └─ Offline → SES SendEmail │
└──────────────────────────────────────────────────────┘
│ │
┌────────┘ └────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ WebSocket │ │ Amazon SES │
│ (existing) │ │ (new) │
└────────┬─────────┘ └──────────────────┘
│
▼ (frontend)
┌──────────────────────────────────────┐
│ document.visibilityState check │
│ ├─ 'visible' → in-app toast │
│ └─ 'hidden' → Notification API │
└──────────────────────────────────────┘
Alternatives considered
-
SNS for push delivery — SNS supports email/SMS subscriptions natively but adds topic management overhead, doesn't support rich HTML emails well, and we'd still need SES for formatting. Using SES directly is simpler for email-only delivery.
-
Service Worker + Web Push API — enables true push notifications even when the browser is fully closed (no tab at all). Rejected because it requires a push subscription server, VAPID keys, and adds significant complexity. The Notification API (requires at least one tab open) covers the most common case. Can be added later.
-
Polling-based notification inbox without push — simpler but defeats the purpose. Users would only see notifications when they manually check. The whole point is proactive alerting.
-
Notify all project members on every discussion message — too noisy. Scoping to @mentions and agent questions keeps signal-to-noise ratio high.
Breaking changes
None. This is purely additive:
- The
notifications DynamoDB table already exists (unused) — activating it doesn't affect existing data.
- The WebSocket
broadcastToUser function already exists — extending its usage doesn't change existing behavior.
- Browser Notification API requires explicit user permission grant — no automatic behavior change for existing users.
- Email notifications are opt-out (enabled by default) but can be disabled immediately via preferences.
Open questions
-
Email batching — if a user is offline and receives 5 mentions in 10 minutes, should we send 5 separate emails or batch into a digest? Proposal: send immediately for the first, then batch subsequent ones in a 5-minute window.
-
Notification TTL — 30 days? 90 days? Should it be configurable per project?
-
SES production access — new AWS accounts start in SES sandbox (can only send to verified emails). Need to plan for production access request during deployment. Should we document this or automate it?
-
Service Worker (v2) — is true offline push (app fully closed) important enough to prioritize for v2, or is the Notification API (requires one tab open) sufficient for foreseeable use cases?
-
Agent questions targeting — should "agent question" notifications go to all members or just the project owner? Should this be configurable per project?
-
Slack/Teams integration — should we support a third-party chat channel (Slack, Teams, Discord) as an alternative to email for offline notifications? The simplest approach would be a project-level Incoming Webhook URL (owner configures it in project settings, all notifications for the project go to that channel). Per-user Slack DMs would require a full OAuth app and user mapping — significantly more effort.
Summary
Add a multi-channel notification system so users are alerted when something requires their attention — even when they're not actively looking at the app. Notifications escalate through three channels based on user presence: in-app toast (tab visible), browser push notification (tab hidden), and email via SES (app closed). Triggers are @mentions in discussions and agent questions requiring human input.
Motivation
Today,
@mentionnotifications are ephemeral WebSocket toasts. If the user's tab isn't focused or the app is closed, the notification is silently lost. Users have no way to know something needs their attention unless they're actively watching the screen. This is a problem for a collaborative tool where discussions and agent questions can happen asynchronously. The DynamoDB notifications table already exists in infrastructure but is completely unused: there's no persistent inbox, no browser push, and no email fallbackDetailed design
Triggers (when to notify)
Channel escalation logic
Presence detection
connectionsDynamoDB table byUserIdIndex. If user has at least one connection with a valid (non-expired) token → online (use WebSocket). Otherwise → offline (send email).document.visibilityState. If'hidden'→ firenew Notification(...). If'visible'→ show toast only.Backend changes
Activate the
notificationstable — add TTL attribute (30-day expiry), define item schema:Notification dispatcher Lambda — receives events (from discussion Lambda inline call or via EventBridge), persists to notifications table, checks user presence, routes to WebSocket (
broadcastToUser) or SES.REST endpoints —
GET /notifications(list, paginated),PATCH /notifications/{id}(mark read),GET /notifications/unread-count,PUT /notifications/preferences.SES integration — Terraform module for verified domain, email template (subject:
[ProjectName] {byName} mentioned you, body: excerpt + deep link + unsubscribe).User preferences — stored on User vertex or in a new DynamoDB item:
emailNotifications: bool (default: true)notifyOnMention: always onnotifyOnAgentQuestion: bool (default: true for owners, false for members)Frontend changes
Browser Notification API — request permission on login, fire
new Notification()when WebSocket event arrives anddocument.visibilityState === 'hidden'.Notification inbox — bell icon in header with unread badge, dropdown listing recent notifications with "mark as read" and "jump to thread" actions.
Preferences UI — settings page/modal for toggling email and per-event-type notifications.
Architecture diagram
Alternatives considered
SNS for push delivery — SNS supports email/SMS subscriptions natively but adds topic management overhead, doesn't support rich HTML emails well, and we'd still need SES for formatting. Using SES directly is simpler for email-only delivery.
Service Worker + Web Push API — enables true push notifications even when the browser is fully closed (no tab at all). Rejected because it requires a push subscription server, VAPID keys, and adds significant complexity. The Notification API (requires at least one tab open) covers the most common case. Can be added later.
Polling-based notification inbox without push — simpler but defeats the purpose. Users would only see notifications when they manually check. The whole point is proactive alerting.
Notify all project members on every discussion message — too noisy. Scoping to
@mentionsand agent questions keeps signal-to-noise ratio high.Breaking changes
None. This is purely additive:
notificationsDynamoDB table already exists (unused) — activating it doesn't affect existing data.broadcastToUserfunction already exists — extending its usage doesn't change existing behavior.Open questions
Email batching — if a user is offline and receives 5 mentions in 10 minutes, should we send 5 separate emails or batch into a digest? Proposal: send immediately for the first, then batch subsequent ones in a 5-minute window.
Notification TTL — 30 days? 90 days? Should it be configurable per project?
SES production access — new AWS accounts start in SES sandbox (can only send to verified emails). Need to plan for production access request during deployment. Should we document this or automate it?
Service Worker (v2) — is true offline push (app fully closed) important enough to prioritize for v2, or is the Notification API (requires one tab open) sufficient for foreseeable use cases?
Agent questions targeting — should "agent question" notifications go to all members or just the project owner? Should this be configurable per project?
Slack/Teams integration — should we support a third-party chat channel (Slack, Teams, Discord) as an alternative to email for offline notifications? The simplest approach would be a project-level Incoming Webhook URL (owner configures it in project settings, all notifications for the project go to that channel). Per-user Slack DMs would require a full OAuth app and user mapping — significantly more effort.