Architecture

Communication Infrastructure Domain Architecture

Last updated: 2026-02-01 | Architecture

Communication Infrastructure Domain Architecture

Key Takeaways

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Mandrill library is deprecated — the email service uses lutung 0.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
email 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)

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

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

In-Flight State

External Integrations

Integration Service Purpose Migration Impact
Mandrill (Mailchimp) Email 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
Email Mandrill REST Send templated emails
SMS Twilio REST Send SMS messages
Chat Stream Chat REST (SDK) Channel/user management
Email Keycloak Admin API User email lookup/verification
SMS Keycloak Admin API User phone number lookup

Asynchronous (RabbitMQ)

Publisher Message Consumer Purpose
Notifications SendEmail Email Route notification to email delivery
Notifications SendEmailDigest Email Send weekly digest email
Notifications SendSms SMS Route notification to SMS delivery
Notifications SendSseMessageToUser SSE Route notification to SSE push
Email EmailSent Notifications Delivery confirmation
Email 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.
Email 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

  1. 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 SendNotification events
    • Manage user subscriptions and preferences
    • Deliver via Mandrill (email), Twilio (SMS), or SSE push
    • Track delivery status
  2. Chat stays separate: It’s a thin Stream Chat wrapper with its own domain (channels, membership, terms of use). No benefit from merging.

  3. Message-Board stays separate: Forum functionality with Redis SSE is distinct from notification delivery. Keep as standalone.

  4. 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

Migration Risks

  1. Mandrill webhook URLs: Mandrill callbacks point to current service URL. Must update in Mandrill dashboard during migration or maintain redirect.
  2. Twilio webhook URLs: Same as Mandrill — callback URLs must be updated.
  3. Stream Chat API keys: Per-environment keys stored in config. Must migrate to new service config.
  4. SSE connection interruption: Brief gap during service restart. EventSource auto-reconnect handles this, but users may miss notifications during the window.
  5. 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