Communication Infrastructure Domain Architecture
Communication Infrastructure Domain Architecture
Key Takeaways
- Six Gen 2 services handle communication — email (Mandrill), sms (Twilio), notifications (orchestration), chat (Stream Chat), message-board (Redis SSE), and sse (real-time push). Email, SMS, and notifications share a single PostgreSQL database and form a natural consolidation target.
- Stream Chat is the chat backbone — the chat service is a thin wrapper around Stream Chat SDK (v1.26.2). All chat functionality is delegated to Stream’s hosted platform. This is already effectively a SaaS replacement.
- SSE is the platform-wide real-time layer — 8 inbound RabbitMQ handlers, Redis-backed group membership, purchase-triggered access control. Multiple services publish events that SSE pushes to browsers.
- Gen 1 real-time services are fully replaceable — peeq-websocket (Node.js/Socket.IO, single EC2, Jitsi watchers) and peeq-conference-sse (role-based meet-and-greet) are tied to deprecated meet-and-greet/Jitsi features.
- Mandrill library is deprecated — the email service
uses
lutung0.0.8 (unmaintained). Migration to a maintained email API client is needed regardless of other modernization decisions.
Migration Decision Question
Can communication services be consolidated, and what delivery channels are required?
Migration Verdict
Verdict: Consolidate Complexity: L Key Constraint: Email/SMS/notifications share a database and form a delivery pipeline — consolidate into a single notification service. Chat is already SaaS (Stream). SSE is infrastructure-level. Mandrill library must be replaced. Dependencies: All services depend on Keycloak JWT. SSE is consumed by 7+ publishing services. Notifications routes to email, SMS, and SSE.
Per-Service Verdicts
| Service | Verdict | Rationale |
|---|---|---|
| Email + SMS + Notifications | Consolidate | Shared DB, pipeline relationship (notifications → email/sms). Merge into single notification/delivery service |
| Chat | Upgrade | Thin Stream Chat wrapper — upgrade Spring Boot, keep SaaS delegation |
| Message-Board | Upgrade | 5 migrations, 3 entities, Redis SSE — simple service worth keeping separate |
| SSE | Upgrade | Core real-time infrastructure — upgrade in place |
| peeq-conference-sse | Retire | Gen 1, meet-and-greet feature deprecated |
| peeq-websocket | Retire | Gen 1 Node.js, single EC2 SPOF, Jitsi/Puppeteer dependencies |
| peeq-sse | Retire | Gen 1, replaced by Gen 2 SSE |
Services Inventory
| Service | Gen | Deployed? | Stack | Purpose |
|---|---|---|---|---|
| 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Mandrill email delivery | |
| sms | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Twilio SMS delivery |
| notifications | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Notification orchestration + subscriptions |
| chat | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Stream Chat integration |
| message-board | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Forum/message board with Redis SSE |
| sse | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Server-Sent Events real-time push |
Not in Production
| Service | Status | Notes |
|---|---|---|
| peeq-sse (Gen 1) | Replaced | Java 11/SB 2.6.8, replaced by Gen 2 SSE |
| peeq-conference-sse (Gen 1) | Inactive | Java 11/SB 2.4.2, meet-and-greet only |
| peeq-websocket (Gen 1) | Inactive | Node.js/Socket.IO, single EC2, Jitsi watchers |
| peeq-notification-service (Gen 1) | Replaced | Replaced by Gen 2 notifications |
API Surface
Email Service (GraphQL — deprecated + REST webhook)
GraphQL (all DEPRECATED — migrating to Keycloak): -
checkStatus — email verification status -
confirmCode(code, email) — verify email with code -
resendCode(application, email) — resend verification code -
countUsersWithVerifiedEmail — count verified users
REST: -
POST /Api/Email/Mandrill/Callback/{route} — Mandrill
delivery status webhook (HMAC signature validation)
SMS Service (GraphQL + REST webhook)
Mutations: confirmCode, resendCode, setTemplate, getTemplates Queries: checkStatus, queryUserPhoneNumbers
REST: - POST /twilio/callback/** —
Twilio delivery status webhook (signature validation)
Notifications Service (GraphQL — 8 operations)
Mutations: createNotificationSubscriptions, updateNotificationSubscriptions, markNotificationsAsRead, triggerNotificationDigest, seedTestNotificationDigestMessage Queries: queryMyNotificationSubscriptions, getUnreadSSENotificationsForMe, getNotificationSubscriptionsForMe (deprecated)
Chat Service (GraphQL — 9 operations)
Auth: createToken (generates Stream Chat JWT) Channel Management: createChannel (admin), updateChannel (admin), queryChannels (admin), addUsersToChannel (admin), removeUsersFromChannel (admin), normalizeChannelName, queryUsersInChannel Terms: upsertTermsOfUseAcceptance, queryTermsOfUseAcceptance (admin)
Message-Board Service (GraphQL — 7 operations)
Queries: getMessages (paginated), getMessageBoards, getBroadcastDonationsPerFan (leaderboard) Mutations: addMessage, removeUserFromSseGroup, muteMember, unmuteMember
SSE Service (REST — minimal)
GET /api/sse/subscribe— SSE connection endpoint (Keycloak JWT auth)GET /tech/db/update— admin: fix DB relationshipsGET /tech/db/redis/migrate— admin: migrate PostgreSQL groups to Redis
Who Calls This Domain? Who Does It Call?
graph LR
subgraph "Communication Domain"
EM[Email]
SM[SMS]
NOT[Notifications]
CH[Chat]
MB[Message-Board]
SSE_SVC[SSE]
end
subgraph "Callers (RabbitMQ Publishers)"
CEL[Celebrity]
FAN[Fan]
INV[Inventory]
SH[Shoutout]
CON[Content]
WEB[Webinar]
STR[Stripe]
PBP[Purchase-Request-BPM]
end
subgraph "External APIs"
MAN[Mandrill]
TWI[Twilio]
STREAM[Stream Chat]
RED[Redis]
end
subgraph "Frontend"
FE1[peeq-mono]
FE2[frontends]
end
NOT -->|SendEmail| EM
NOT -->|SendSms| SM
NOT -->|SendSseMessageToUser| SSE_SVC
EM -->|API| MAN
SM -->|API| TWI
CH -->|SDK| STREAM
MB -->|Pub/Sub| RED
CEL & FAN -.->|ProfileCreated| CH & SSE_SVC
INV -.->|PurchaseStateUpdated| SSE_SVC & CH & MB
SH & CON & WEB & STR & PBP -.->|SendNotification| NOT
FE1 & FE2 -->|GraphQL| CH & MB & NOT
FE1 & FE2 -->|SSE| SSE_SVC
API Backward Compatibility Constraints
- Email verification GraphQL is deprecated — already migrating to Keycloak. Can be removed in Gen 3 without breaking changes.
- Notifications subscriptions have a deprecated
getNotificationSubscriptionsForMereplaced byqueryMyNotificationSubscriptions. Must maintain both during migration. - Chat
createTokenis the critical API — frontends need Stream Chat JWT tokens. Changing token format would break all chat clients. - SSE
/subscribeendpoint — both frontends connect to this. URL or auth changes require coordinated deployment. - Message-board
addMessage— real-time message flow depends on Redis SSE topic. Message format changes would break live boards.
Data Model
Email + SMS Shared Schema (9 Flyway migrations)
erDiagram
user_emails {
uuid id PK
uuid user_id
string email
instant assigned_on
instant verified_on
string verification_code
instant created_on
}
sent_messages_email {
uuid id PK
uuid user_id
uuid subscription_id
instant sent_on
boolean ignored
string variables
string from_addr
string to_addr
string email_id
string status
string template
string mandrill_response_status
}
callbacks {
uuid id PK
uuid message_id
instant received_on
string from_addr
string to_addr
string json
string type
}
user_phone_numbers {
uuid id PK
uuid user_id
string number
instant assigned_on
instant verified_on
string verification_code
instant created_on
}
sent_messages_sms {
uuid id PK
uuid user_id
instant sent_on
boolean ignored
string content
string from_addr
string to_addr
string sid
string status
string type
uuid subscription_id
}
message_templates {
uuid id PK
string name UK
string template
uuid created_by_id
instant created_on
instant deleted_on
}
user_emails ||--o{ sent_messages_email : "sends"
user_phone_numbers ||--o{ sent_messages_sms : "sends"
sent_messages_email ||--o{ callbacks : "receives"
Note: user_emails table is DEPRECATED —
email verification migrating to Keycloak.
Notifications Schema (10 Flyway migrations)
erDiagram
subscriptions {
uuid id PK
uuid user_id
uuid foreign_key_id
string foreign_key
string channel_pattern
string delivery_methods_json
string metadata
date created_on
date unsubscribed_on
}
notification_logs {
uuid id PK
uuid user_id
uuid subscription_id FK
string channel
string template
string metadata
string delivery_methods_json
string delivery_method_statuses_json
date created_on
date last_updated_on
boolean marked_as_read
}
notification_digest_messages {
uuid id PK
uuid recipient_user_id
uuid item_id
string item_type
string item_name
string item_description
string thumbnail_url
boolean message_sent
instant message_sent_on
instant created_on
uuid created_by_id
integer event_duration_minutes
instant event_start_time
uuid featured_talent_id
}
subscriptions ||--o{ notification_logs : "triggers"
Chat Schema (0 service-level Flyway migrations)
erDiagram
channels {
uuid id PK
uuid created_by_id
string channel_name
string channel_topic
string channel_description
string stream_io_channel_id
string image_location
boolean enabled
string channel_type
date created_on
date last_modified_on
}
channel_membership {
uuid id PK
uuid user_id
uuid channel_record_id FK
boolean active
uuid associated_subscription_id
date created_on
date last_updated_on
}
terms_of_use_acceptance {
uuid id PK
uuid user_id UK
boolean terms_accepted
date created_on
date last_modified_on
}
channels ||--o{ channel_membership : "has members"
Note: Chat channels map 1:1 to Stream Chat channels
via stream_io_channel_id.
associated_subscription_id links channels to subscription
purchases — purchase triggers channel access.
Message-Board Schema (5 Flyway migrations)
erDiagram
message_boards {
uuid id PK
string id_str
string id_type
uuid created_by_id
string name
string description
string metadata
date created_on
date starts_on
date stops_on
}
members {
uuid id PK
uuid message_board_id FK
uuid user_id
string name
string metadata
boolean muted
date added_on
date removed_on
date muted_on
date unmuted_on
}
messages {
uuid id PK
uuid message_board_id FK
uuid member_id FK
string message_content
string message_content_type
string metadata
date created_on
}
message_boards ||--o{ members : "has"
message_boards ||--o{ messages : "contains"
members ||--o{ messages : "writes"
SSE Schema (2 Flyway migrations)
erDiagram
groups {
uuid id PK
string id_str
string id_type
uuid created_by_id
string metadata
instant created_on
}
members {
uuid id PK
uuid group_id FK
uuid user_id
string metadata
instant added_on
}
groups ||--o{ members : "contains"
Note: SSE groups mirror message-board structure.
Groups are linked to inventory items via
associated_item_ids. Redis caches group membership for fast
lookup during SSE push.
Data Migration Specifics
- Schema transformation: Minimal for all services —
additive schemas. Email
user_emailstable can be dropped (deprecated). - Foreign key dependencies: All entities FK to
Keycloak UUIDs. Chat channels link to Stream Chat via
stream_io_channel_id. Notifications link to inventory items viaforeign_key. - Data volume: Unknown. Notification logs and sent messages tables grow unboundedly — likely the largest tables in this domain.
- Shared database: Email, SMS, and notifications share a PostgreSQL instance (peeq-notification-service-db). Chat, message-board, and SSE have separate databases.
In-Flight State
- Active SSE connections: Browser SSE connections will drop during service restart. Clients auto-reconnect (EventSource spec). Brief notification gap during migration.
- Pending Mandrill callbacks: Email delivery webhooks may arrive after migration. Callback URL must remain active during transition.
- Pending Twilio callbacks: Same as Mandrill — delivery status webhooks in flight.
- Notification digest schedule: Weekly digest runs Wednesdays at noon ET. Migration should not span this window.
- Stream Chat state: Chat history and channels are stored in Stream’s infrastructure (SaaS). No migration needed for chat data.
- Redis SSE groups: In-memory group membership must be rebuilt from PostgreSQL if Redis is flushed during migration.
External Integrations
| Integration | Service | Purpose | Migration Impact |
|---|---|---|---|
| Mandrill (Mailchimp) | Transactional email delivery | API key migration. Library (lutung) must be replaced —
unmaintained. |
|
| Twilio | SMS | SMS delivery + verification | API key migration. SDK is current (v10.4.1). |
| Stream Chat | Chat | Hosted chat platform | API key/secret migration. SDK current (v1.26.2). |
| Redis | SSE, Message-Board | Group membership cache, SSE pub/sub | Redis instance migration or reconnection. |
| Keycloak | All services | JWT authentication | Same as identity domain — issuer URL config. |
Inter-Service Communication
Synchronous (REST/GraphQL)
| From | To | Protocol | Purpose |
|---|---|---|---|
| Mandrill | REST | Send templated emails | |
| SMS | Twilio | REST | Send SMS messages |
| Chat | Stream Chat | REST (SDK) | Channel/user management |
| Keycloak | Admin API | User email lookup/verification | |
| SMS | Keycloak | Admin API | User phone number lookup |
Asynchronous (RabbitMQ)
| Publisher | Message | Consumer | Purpose |
|---|---|---|---|
| Notifications | SendEmail | Route notification to email delivery | |
| Notifications | SendEmailDigest | Send weekly digest email | |
| Notifications | SendSms | SMS | Route notification to SMS delivery |
| Notifications | SendSseMessageToUser | SSE | Route notification to SSE push |
| EmailSent | Notifications | Delivery confirmation | |
| EmailVerified | Keycloak? | Email verification complete | |
| SMS | SmsSent | Notifications | Delivery confirmation |
| SMS | PhoneNumberVerified | Users? | Phone verification complete |
| Any service | SendNotification | Notifications | Trigger notification pipeline |
| Celebrity | CelebrityProfileCreated | Chat, SSE | Create chat account + SSE group |
| Fan | FanProfileCreated | Chat, SSE | Create chat account + add to free channels |
| Inventory | PurchaseInventoryItemRequest | Chat | Add user to paid chat channels |
| Inventory | PurchaseStateUpdated | SSE, Message-Board | Add/remove user from groups based on purchase |
| Message-Board | MessageAddedToMessageBoard | (Redis SSE) | Real-time message fanout |
| Any service | CreateSseGroup | SSE | Create SSE broadcast group |
| Any service | AddUserToSseGroup | SSE | Add user to SSE group |
Notification Delivery Pipeline
sequenceDiagram
participant Service as Any Service
participant NOT as Notifications
participant EM as Email (Mandrill)
participant SM as SMS (Twilio)
participant SSE as SSE Service
participant Browser as User Browser
Service->>NOT: SendNotification (channel, metadata)
NOT->>NOT: Match subscriptions by channelPattern
NOT->>NOT: Filter by user delivery preferences
alt EMAIL delivery
NOT->>EM: SendEmail (template, metadata)
EM->>EM: Flatten metadata, sanitize HTML
EM-->>EM: Send via Mandrill API
EM->>NOT: EmailSent (status)
end
alt SMS delivery
NOT->>SM: SendSms (template, metadata)
SM->>SM: Resolve template, normalize phone
SM-->>SM: Send via Twilio API
SM->>NOT: SmsSent (status)
end
alt SSE delivery
NOT->>SSE: SendSseMessageToUser (userId, data)
SSE->>Browser: SSE push event
end
NOT->>NOT: Update deliveryMethodStatuses
Gen 1 → Gen 2 Comparison
| Aspect | Gen 1 | Gen 2 | Gap |
|---|---|---|---|
| SSE | peeq-sse (Java 11/SB 2.6.8) | sse (Java 21/SB 3.5.4) | Gen 2 is full replacement. Same architecture with Redis + PostgreSQL. |
| Conference SSE | peeq-conference-sse (Java 11/SB 2.4.2) | None | Feature deprecated (meet-and-greet uses Zoom webinar now). Role-based rooms not needed. |
| WebSocket | peeq-websocket (Node.js/Socket.IO) | None | Feature deprecated (Jitsi watchers, meet-and-greet coordination). Single EC2 SPOF. |
| Notifications | peeq-notification-service | notifications (Gen 2) | Gen 2 adds digest emails, subscription topics, delivery method preferences. |
| Chat Framework | Stream Chat (same) | Stream Chat (same) | SDK version bump. No functional change in delegation model. |
| Mandrill (same library) | Mandrill (same library) | lutung library deprecated in both — needs replacement
regardless. |
|
| Test Coverage | ~0 | Very low (2-3 test files per service) | Both generations lack testing. |
Modernization Implications
Consolidation Opportunities
Email + SMS + Notifications → single Delivery Service: These three services form a pipeline (notifications routes to email/sms). They share a database. Consolidation eliminates 2 services and simplifies the delivery pipeline. The unified service would:
- Accept
SendNotificationevents - Manage user subscriptions and preferences
- Deliver via Mandrill (email), Twilio (SMS), or SSE push
- Track delivery status
- Accept
Chat stays separate: It’s a thin Stream Chat wrapper with its own domain (channels, membership, terms of use). No benefit from merging.
Message-Board stays separate: Forum functionality with Redis SSE is distinct from notification delivery. Keep as standalone.
SSE stays as infrastructure: SSE is consumed by 7+ services as real-time infrastructure. Keep as standalone service.
Mandrill Library Replacement
The lutung library (v0.0.8) is unmaintained. Options: -
Mailchimp Transactional API (Mandrill’s current SDK) —
direct replacement - SendGrid — popular alternative
with better Java SDK support - Amazon SES —
cost-effective, already on GCP but AWS SDK present in codebase
SaaS Replacement Candidates
| Current | SaaS Alternative | Impact |
|---|---|---|
| Notifications (custom) | OneSignal, Customer.io | Would replace subscription management + multi-channel routing |
| Email (Mandrill) | SendGrid, Postmark, SES | Email delivery only — keep notification orchestration |
| SMS (Twilio) | Already SaaS | Keep — just upgrade SDK |
| Chat (Stream Chat) | Already SaaS | Keep — already delegated |
Dead Code Removal
user_emailstable — deprecated, migrating to Keycloak for email verification- Email verification GraphQL — deprecated
(
checkStatus,confirmCode,resendCode) getNotificationSubscriptionsForMe— deprecated in favor ofqueryMyNotificationSubscriptions- peeq-websocket — entire service (Node.js, 58 Socket.IO events, Puppeteer/Jitsi)
- peeq-conference-sse — entire service (meet-and-greet SSE)
- peeq-sse — entire service (replaced by Gen 2)
Migration Risks
- Mandrill webhook URLs: Mandrill callbacks point to current service URL. Must update in Mandrill dashboard during migration or maintain redirect.
- Twilio webhook URLs: Same as Mandrill — callback URLs must be updated.
- Stream Chat API keys: Per-environment keys stored in config. Must migrate to new service config.
- SSE connection interruption: Brief gap during service restart. EventSource auto-reconnect handles this, but users may miss notifications during the window.
- Redis state: SSE group membership cached in Redis. Flush/restart requires PostgreSQL rebuild.
Last updated: 2026-01-30 — Session 7 Review by: 2026-07-30 Staleness risk: Medium — Mandrill library deprecation is urgent; Stream Chat SDK may evolve