Notifications Service
The Notifications service delivers push notifications (APNs for iOS, FCM for Android), transactional emails (via Resend), and processes events from Redis Streams. Notifications are never sent synchronously — all delivery is triggered via the async job queue.
Quick Reference
| Property | Value |
|---|---|
| Port | 8500 |
| Language | Go |
| Framework | Chi v5 |
| Module path | github.com/gospelib/main/services/notifications |
| Entry point | cmd/server/main.go |
| Push providers | APNs (Apple), FCM (Google) |
| Email provider | Resend |
| Queue | Redis Streams (gl:events:notifications) |
Responsibilities
- Push notifications — Deliver to iOS (APNs) and Android (FCM) devices
- Transactional email — Account events, subscription changes, study reminders via Resend
- Redis Streams consumer — Process notification events from the
gl:events:notificationsstream - Device registration — Track user devices and push tokens (via
gl_user_devicestable) - Preference management — Respect user notification preferences
Running Locally
cd services/notifications
go run ./cmd/server
The service starts on port 8500 and requires Redis to be running.
Environment Variables
| Variable | Default | Description |
|---|---|---|
GOSPELIB_NOTIFICATIONS_PORT | 8500 | HTTP listen port |
GOSPELIB_NOTIFICATIONS_REDIS_URL | redis://localhost:6380 | Redis for Streams consumer + state |
GOSPELIB_NOTIFICATIONS_APNS_KEY | — | APNs authentication key |
GOSPELIB_NOTIFICATIONS_APNS_KEY_ID | — | APNs key ID |
GOSPELIB_NOTIFICATIONS_APNS_TEAM_ID | — | Apple Developer Team ID |
GOSPELIB_NOTIFICATIONS_FCM_CREDENTIALS | — | FCM service account JSON |
GOSPELIB_NOTIFICATIONS_RESEND_API_KEY | — | Resend API key for transactional email |
GOSPELIB_NOTIFICATIONS_DATABASE_URL | postgres://localhost:5432/gospelib | PostgreSQL for device records |
Health Check
curl http://localhost:8500/health
# {"status": "ok"}
Job Queue Pattern
The service runs a Redis Streams consumer group that processes notification events asynchronously:
// services/notifications/internal/queue/consumer.go
func (c *Consumer) Run(ctx context.Context) error {
for {
msgs, err := c.redis.XReadGroup(ctx, &redis.XReadGroupArgs{
Group: "notifications-workers",
Consumer: c.workerID,
Streams: []string{"gl:events:notifications", ">"},
Count: 10,
Block: 5 * time.Second,
})
if err != nil && err != redis.Nil {
c.log.Error("queue read error", "err", err)
time.Sleep(1 * time.Second)
continue
}
for _, msg := range msgs[0].Messages {
go c.process(ctx, msg)
}
}
}
Consumer Group
- Group name:
notifications-workers - Stream:
gl:events:notifications - Batch size: 10 messages per read
- Block timeout: 5 seconds
- Processing: One goroutine per message for concurrent delivery
Event Types
| Event | Source | Action |
|---|---|---|
notification.push | Any service | Send push notification to user's devices |
notification.email | Any service | Send transactional email via Resend |
user.created | Auth service | Send welcome email |
subscription.changed | Billing service | Send plan change confirmation |
Entry Point Pattern
Standard Go service entry point at cmd/server/main.go:
- Configure Zerolog (JSON structured logging)
- Load config from environment variables
- Initialize Redis Streams consumer
- Initialize push providers (APNs, FCM) and email provider (Resend)
- Create Chi router with middleware
- Register routes — health, notification preferences
- Start HTTP server + consumer in goroutines
- Wait for
SIGINT/SIGTERM - Graceful shutdown — drain consumer, stop HTTP server
Docker
FROM golang:1.25-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/server
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
EXPOSE 8500
ENTRYPOINT ["/app"]
Related Pages
- Services Overview — all services at a glance
- Architecture > Data > Redis — Redis Streams patterns
- Auth Service — publishes user lifecycle events
- Billing Service — publishes subscription events