← Back to Projects
Business Services

Notification & CRM Service

Announcements, in-app inbox and a call-centre CRM back end

Rebuilt notifications · built CRM integration from scratch · largest contributor · Oct 2024 — Jan 2026
Go go-zero gRPC MySQL/GORM Kafka REST
8
Tables
15
RPC Endpoints

Background

Call-centre staff work through lists of customers sorted by when they were last contacted. Aggregating call history for every list view does not scale, and the customer data itself lives in the identity service.

Architecture

Back office → gateway (outreach API + provider client) → outreach gRPC service → MySQL. Identity-service events (registered, phone changed, status changed) → Kafka consumer → transactional update of the latest-contact row. Announcements: publish → Kafka → WebSocket service → connected clients.

Key Highlights

01

Rebuilt announcements and inbox: multi-language content, group targeting, per-user read state and unread counts, real-time delivery through Kafka and WebSocket

02

Customer-outreach back end from scratch: 15 gRPC endpoints over 8 tables (seats, call and SMS records, settings)

03

23 admin endpoints and a signed REST client for a cloud contact-centre and SMS provider, with automatic seat-group provisioning

04

A "latest contact per customer" read model kept in sync from Kafka user events, with retry and dead-letter topics and transactional writes

05

Online backfill of the read model: temporary index → INSERT … SELECT … ON DUPLICATE KEY UPDATE → drop index

Key Implementations

1

Event-driven read model

One row per customer with their latest call and SMS, created on registration and reset when the phone number or status changes, updated in the same transaction as each call record.

Why: List screens read one row per customer instead of aggregating history, and the data follows the identity service without synchronous calls.

2

Provider integration

A generic signed REST client for the provider, provider error codes mapped to localised platform errors, and seat groups created automatically when the current one is full.

Why: Staff shouldn't see raw vendor errors, and onboarding new staff shouldn't need manual provider setup.

Technical Decisions

Technical Decisions Chosen Alternative Reason
Latest-contact lists Denormalised read model maintained from events Aggregate call history at query time Reads are far more frequent than calls, and the list needs filters that span identity data.