Introduction
The pandemic‑era shift to remote and distributed teams has turned people platforms from niche HR tools into the central nervous system of modern enterprises. Companies now need a single pane of glass that can hire, onboard, pay, and manage compliance for workers spread across dozens of jurisdictions.
One of the most visible manifestations of this new reality is the activity feed—the stream of notifications, alerts, and status updates that keep every stakeholder informed in real time. Deel’s public “Notification Hub” (the activity feed you see after logging into their platform) is a compelling example of how a well‑engineered feed can become a productivity multiplier for a global workforce.
In this article we will:
- Dissect the technical and UX challenges behind a worldwide people platform.
- Use Deel’s activity feed as a case study to illustrate best‑in‑class design patterns.
- Connect those patterns to broader concepts such as event sourcing, CQRS, and real‑time streaming.
- Provide a step‑by‑step mini‑project that demonstrates how to build a scalable notification hub from scratch.
- Explore future directions—AI‑driven insights, predictive compliance, and the next generation of “forever people” platforms.
Whether you’re a senior engineer, a product manager, or a CTO evaluating a new HR tech stack, this deep dive will give you a roadmap for constructing a robust, secure, and delightful activity feed that can serve as the backbone of a truly global workforce.
The Rise of “Forever People” Platforms
From Payroll to People‑Centric Ecosystems
Traditional payroll or HRIS systems were built for static, on‑premises workforces. They focused on:
- Monthly payroll runs.
- Local benefits administration.
- Basic employee record keeping.
In contrast, a “forever people” platform (a term popularized by Deel) aims to be a continuous, lifecycle‑spanning service that handles every interaction a worker has with a company—from the first contract offer to the final off‑boarding invoice—across any country. This ambition forces a re‑evaluation of several core software concepts:
| Traditional HR System | Forever People Platform |
|---|---|
| Batch‑oriented processing | Real‑time event streams |
| Single‑jurisdiction focus | Multi‑jurisdiction compliance engine |
| Manual data entry | Automated data enrichment (e.g., tax IDs, bank verification) |
| Static UI dashboards | Dynamic activity feeds & notifications |
Why an Activity Feed Matters
A global workforce platform must inform, guide, and empower its users instantly:
- Employees need to know when a contract is signed, a payment is processed, or a compliance document is missing.
- Managers must see approvals pending, budget overruns, or legal alerts.
- Finance & Ops teams require visibility into currency conversions, tax filings, and invoice statuses.
An activity feed aggregates these signals into a chronologically ordered, context‑rich stream, reducing the need for users to constantly poll multiple dashboards or inboxes. In essence, the feed becomes the single source of truth for “what’s happening now.”
Core Challenges in Global Workforce Management
Before we dive into Deel’s implementation, it’s worth outlining the technical obstacles any “forever people” platform must overcome:
Regulatory Diversity
Over 200 countries have distinct labor, tax, and data‑privacy laws. The platform must enforce these rules in real time while still delivering a uniform UI experience.Data Consistency at Scale
A single worker’s record may be referenced by payroll, compliance, benefits, and legal modules. Maintaining strong consistency across micro‑services is non‑trivial.Real‑Time Visibility
Delays of even a few minutes can cause missed deadlines (e.g., tax filing cut‑offs). The system must push updates instantly to all relevant parties.Internationalization (i18n) & Localization (l10n)
Notifications must appear in the user’s preferred language, respecting date, time, and currency formats.Security & Privacy
Sensitive personal data (SSNs, bank details) travels across services and must be protected under GDPR, CCPA, and other regimes.Scalability & Fault Tolerance
A platform serving thousands of companies can generate millions of events per day. The architecture must handle spikes without losing messages.
Deel’s activity feed tackles these challenges head‑on, and its design choices serve as a blueprint for anyone building a comparable system.
Deel’s Activity Feed: A Case Study in Real‑Time Notifications
Architecture Overview
At a high level, Deel’s feed follows a micro‑service, event‑driven architecture:
[User Action] → API Gateway → Domain Service (e.g., Contracts) → Event Store
↘
Event Bus (Kafka)
↘
Notification Service → Feed Builder → Real‑time API (WebSocket/Server‑Sent Events)
- Domain Services (contracts, payments, compliance) are responsible for business logic and emit domain events (e.g.,
ContractSigned,InvoicePaid). - Events are persisted in an event store (often a log‑structured storage like Apache Kafka or a dedicated event‑sourcing DB).
- A central Notification Service consumes these events, enriches them (adds user‑friendly text, links, localization), and stores them in a feed database (e.g., DynamoDB, PostgreSQL with a JSONB column).
- The real‑time API pushes new feed items to connected clients via WebSockets or Server‑Sent Events (SSE), ensuring instant delivery.
This separation of concerns gives the platform flexibility (new feed types can be added without touching the UI) and reliability (the feed can be rebuilt from the event log if needed).
Event Sourcing & CQRS
Deel leverages Event Sourcing to guarantee that every state change is captured as an immutable event. Coupled with Command Query Responsibility Segregation (CQRS):
- Command side – writes go through domain services that validate and emit events.
- Query side – the feed is a read model built from those events, optimized for fast look‑ups and pagination.
Benefits include:
- Auditable history – you can reconstruct any worker’s timeline.
- Scalable reads – the feed database can be sharded or cached without impacting write throughput.
- Easy integration – third‑party systems can subscribe to the same event stream for their own notifications.
Front‑End Rendering Strategies
Deel’s UI must render a potentially infinite scroll of heterogeneous items (contract updates, payment alerts, compliance reminders). Key techniques:
Component‑Based Rendering – each event type maps to a React component (
<ContractSignedCard/>,<PaymentReceivedCard/>). This enables type‑safe rendering and isolated styling.Virtualized Lists – libraries like
react‑virtualizedorreact‑windowrender only visible rows, dramatically reducing DOM size.Optimistic UI Updates – when a user performs an action (e.g., approves a contract), the UI immediately inserts a provisional feed item, later reconciled with the server event.
Internationalization – the UI pulls the pre‑localized message from the feed item (e.g.,
"Your contract with {{company}} was signed"), allowing the front‑end to stay language‑agnostic.
Handling Internationalization & Localization
Deel’s feed items contain template strings with placeholders (e.g., {{company}}, {{date}}). The Notification Service resolves these templates using the user’s locale:
def render_message(event, locale):
template = LOCALE_TEMPLATES[locale][event.type]
return template.format(**event.payload)
- Date & Time – rendered via
Intl.DateTimeFormaton the client, respecting the user’s timezone. - Currency – formatted using
Intl.NumberFormatwith the appropriate locale and currency code. - Right‑to‑Left (RTL) Support – CSS
direction: rtl;applied conditionally based on locale.
This approach ensures single‑source localization, avoiding duplication across services.
Designing Scalable Notification Hubs
If you’re building a notification system from scratch, consider the following building blocks, each inspired by Deel’s production‑grade implementation.
Message Queues: Kafka, Pulsar, or Managed Alternatives
- Apache Kafka – the de‑facto standard for durable, high‑throughput event streaming. Its log‑compaction feature is perfect for retaining the latest state of each entity (e.g., the most recent contract status).
- Apache Pulsar – offers built‑in multi‑tenant isolation and geo‑replication, useful for SaaS platforms serving multiple regions.
- Managed Services – AWS Kinesis, Google Pub/Sub, or Azure Event Hubs reduce operational overhead.
Key configuration tips:
| Setting | Reason |
|---|---|
replication.factor >= 3 | Guarantees durability across broker failures. |
segment.bytes tuned to 1 GB | Balances disk usage and compaction latency. |
enable.idempotence=true (Kafka) | Prevents duplicate writes when retries occur. |
Pub/Sub Patterns
The publish‑subscribe model decouples producers (domain services) from consumers (notification builder). Common patterns:
- Topic per Domain –
contracts,payments,compliance. Consumers subscribe only to topics they care about. - Event Type Filtering – Using Kafka’s message headers to tag events (
event_type=ContractSigned). Consumers can filter without reading the entire payload. - Dead‑Letter Queues (DLQ) – Unprocessable messages are redirected to a DLQ for later inspection, preventing pipeline stalls.
Data Consistency and Idempotency
When multiple services generate events for the same entity, you must ensure idempotent processing:
public void handle(Event e) {
if (processedIds.contains(e.id())) {
return; // already handled
}
// process event
processedIds.add(e.id());
}
- Store processed IDs in a fast key‑value store (Redis or DynamoDB) with a TTL (e.g., 48 hours) to bound memory usage.
- Use event versioning (
e.version) to detect out‑of‑order delivery; discard or reorder accordingly.
Security, Privacy, and Compliance Considerations
A people platform handles personally identifiable information (PII) and financial data, making security a top priority.
End‑to‑End Encryption
- Transport Layer – Enforce TLS 1.3 for all API and WebSocket connections.
- At‑Rest Encryption – Use envelope encryption (AWS KMS) for databases storing feed items. Even if the feed is not “sensitive” per se, linking it to contracts can expose PII.
Access Control
- Fine‑grained RBAC – Permissions scoped to
company,region, androle. A contractor in Brazil should not see finance alerts for a US subsidiary. - Token‑Based Authorization – JWTs with claims like
scope:feed:readthat are validated by the real‑time API before establishing a WebSocket connection.
Auditing & GDPR
- Event Log Retention – Keep raw events for 7 years (or as required by local law) in an immutable store (e.g., AWS S3 with Object Lock).
- Right‑to‑Be‑Forgotten – Implement a tombstone event that masks or removes a user’s data from the feed while preserving the event chronology for audit purposes.
User Experience: Making the Feed Meaningful
Technical brilliance means nothing if the feed is ignored. Deel’s UI excels because it prioritizes relevance and reduces cognitive load.
Prioritization and Filtering
- Signal Ranking – Assign each event a priority score based on business impact (e.g.,
PaymentFailed>ContractViewed). The front‑end can surface high‑priority items at the top. - User‑Defined Filters – Allow users to toggle categories (
Compliance,Payments,Team Updates). Store preferences in a fast cache (Redis) for instant retrieval.
Accessibility
- ARIA Labels – Each feed card includes
aria-labeldescribing the notification, enabling screen readers. - Keyboard Navigation – Feed items are focusable (
tabindex="0"), and arrow keys move between entries. - Contrast & Font Size – Follow WCAG AA guidelines for color contrast and scalable text.
Mobile Optimization
- Progressive Loading – Load the first 10 items, then fetch more as the user scrolls (
fetchMore()). - Push Notifications – Use native push (APNs/FCM) for critical alerts (e.g., “Invoice overdue”) while keeping the in‑app feed for context.
- Offline Cache – Store the latest 50 items in IndexedDB so the feed remains usable without connectivity.
Integration with Existing HRIS and Payroll Systems
Most enterprises already have legacy HRIS (Workday, SAP SuccessFactors) and payroll (ADP, Paychex). A modern people platform must synchronize rather than replace.
- Bidirectional Sync – Use webhooks or API connectors that translate Deel events to HRIS actions (
ContractSigned → EmployeeRecordCreate). - Mapping Layer – A transformation service maps Deel’s data model to the target system’s schema, handling field mismatches and custom attributes.
- Conflict Resolution – Adopt a source‑of‑truth hierarchy (e.g., Deel > HRIS) and log any overrides for audit.
By exposing a standardized event schema (e.g., CloudEvents), Deel makes it trivial for third‑party systems to subscribe and react.
Future Trends: AI‑Powered Insights and Predictive Alerts
The next generation of people platforms will go beyond reactive notifications to proactive guidance.
Predictive Compliance
- Machine Learning models can flag contracts that are likely to violate upcoming labor law changes, prompting pre‑emptive action.
- Example: A model trained on historical compliance failures predicts a 30% risk for contracts with a certain clause in a new jurisdiction.
Smart Summaries
- Natural Language Generation (NLG) can turn a week’s worth of feed items into a concise email digest:
“This week you received 3 contract signatures, 2 payments, and 1 compliance request.”
Sentiment‑Aware Alerts
- By analyzing worker‑submitted messages, the platform can detect stress signals (e.g., repeated late‑payment complaints) and surface an alert to HR.
Implementing these features requires data pipelines (Spark/Flink) and model serving (TensorFlow Serving, TorchServe) that consume the same event stream powering the feed.
Practical Walkthrough: Building a Mini Notification Hub
Below is a complete, runnable example that demonstrates the core pieces of a notification hub similar to Deel’s. We’ll use:
- Node.js (Express) for the API & event producer.
- Kafka (via
kafkajs) as the event bus. - PostgreSQL for the feed read model.
- React with WebSocket for the front‑end.
1. Set Up the Event Producer (Domain Service)
// server/events.js
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ clientId: 'deel-demo', brokers: ['localhost:9092'] });
const producer = kafka.producer();
async function initProducer() {
await producer.connect();
}
initProducer();
async function emitEvent(type, payload) {
const event = {
id: `${type}-${Date.now()}`,
type,
timestamp: new Date().toISOString(),
payload,
};
await producer.send({
topic: 'domain-events',
messages: [{ key: type, value: JSON.stringify(event) }],
});
}
// Example endpoint that creates a contract
const express = require('express');
const app = express();
app.use(express.json());
app.post('/contracts', async (req, res) => {
const { contractor, company } = req.body;
// ...persist contract in DB (omitted)
await emitEvent('ContractSigned', { contractor, company });
res.status(201).json({ status: 'contract_created' });
});
app.listen(3000, () => console.log('API listening on :3000'));
2. Consume Events and Build the Feed
// server/notification-service.js
const { Kafka } = require('kafkajs');
const { Pool } = require('pg');
const kafka = new Kafka({ clientId: 'deel-demo', brokers: ['localhost:9092'] });
const consumer = kafka.consumer({ groupId: 'notification-service' });
const pgPool = new Pool({ connectionString: process.env.DATABASE_URL });
async function start() {
await consumer.connect();
await consumer.subscribe({ topic: 'domain-events', fromBeginning: true });
await consumer.run({
eachMessage: async ({ message }) => {
const event = JSON.parse(message.value.toString());
const feedItem = await transformEvent(event);
await pgPool.query(
`INSERT INTO feed (event_id, user_id, message, created_at) VALUES ($1,$2,$3,$4)`,
[event.id, feedItem.userId, feedItem.message, event.timestamp]
);
},
});
}
/**
* Simple transformer – in production you’d handle i18n, templating, etc.
*/
async function transformEvent(event) {
let message;
let userId;
switch (event.type) {
case 'ContractSigned':
message = `${event.payload.contractor} signed a contract with ${event.payload.company}`;
userId = event.payload.contractor; // assume userId = contractor name for demo
break;
// add more cases as needed
default:
message = `Unknown event ${event.type}`;
userId = 'system';
}
return { userId, message };
}
start().catch(console.error);
SQL schema (run once):
CREATE TABLE feed (
id SERIAL PRIMARY KEY,
event_id TEXT UNIQUE,
user_id TEXT,
message TEXT,
created_at TIMESTAMP WITH TIME ZONE
);
3. Real‑Time API (WebSocket)
// server/ws.js
const WebSocket = require('ws');
const { Pool } = require('pg');
const pgPool = new Pool({ connectionString: process.env.DATABASE_URL });
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', async (ws, req) => {
// Simple auth – in reality verify JWT from query string
const userId = req.url.split('?user=')[1];
if (!userId) {
ws.close(4001, 'Missing user');
return;
}
// Send latest 20 items on connect
const { rows } = await pgPool.query(
`SELECT message, created_at FROM feed WHERE user_id=$1 ORDER BY created_at DESC LIMIT 20`,
[userId]
);
ws.send(JSON.stringify({ type: 'init', items: rows }));
// Subscribe to PostgreSQL NOTIFY for new items (requires pg trigger)
const client = await pgPool.connect();
await client.query('LISTEN new_feed_item');
client.on('notification', async (msg) => {
const payload = JSON.parse(msg.payload);
if (payload.user_id === userId) {
ws.send(JSON.stringify({ type: 'new', item: payload }));
}
});
});
Add a PostgreSQL trigger to fire a NOTIFY on insert:
CREATE OR REPLACE FUNCTION notify_new_feed() RETURNS trigger AS $$
BEGIN
PERFORM pg_notify(
'new_feed_item',
json_build_object(
'user_id', NEW.user_id,
'message', NEW.message,
'created_at', NEW.created_at
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER feed_notify AFTER INSERT ON feed
FOR EACH ROW EXECUTE FUNCTION notify_new_feed();
4. Front‑End React Component
// src/Feed.tsx
import React, { useEffect, useState } from 'react';
import { FixedSizeList as List } from 'react-window';
type FeedItem = {
message: string;
created_at: string;
};
export default function Feed({ userId }: { userId: string }) {
const [items, setItems] = useState<FeedItem[]>([]);
useEffect(() => {
const ws = new WebSocket(`ws://localhost:8080?user=${userId}`);
ws.onmessage = (ev) => {
const data = JSON.parse(ev.data);
if (data.type === 'init') {
setItems(data.items);
} else if (data.type === 'new') {
setItems((prev) => [data.item, ...prev]);
}
};
return () => ws.close();
}, [userId]);
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style} className="feed-item">
<p>{items[index].message}</p>
<small>{new Date(items[index].created_at).toLocaleString()}</small>
</div>
);
return (
<List
height={500}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
}
What this demo shows:
- Event‑driven write path – domain service emits to Kafka.
- Decoupled consumer – Notification Service builds a read‑optimized feed.
- Real‑time push – PostgreSQL NOTIFY + WebSocket delivers new items instantly.
- Scalable UI –
react-windowvirtualizes the list, keeping the DOM tiny even with thousands of rows.
In production you would replace the simple PostgreSQL NOTIFY with a dedicated message broker (Kafka consumer groups) for better scalability, and you’d add i18n, security, and retry logic.
Conclusion
Deel’s activity feed exemplifies how a people‑first platform can turn a chaotic stream of global workforce events into a coherent, actionable experience. By embracing:
- Event sourcing & CQRS for auditability and scalability,
- Robust message‑bus architecture (Kafka, Pulsar) for decoupled processing,
- Security‑by‑design (encryption, fine‑grained RBAC, GDPR compliance),
- Thoughtful UX (prioritization, accessibility, mobile friendliness),
the platform not only solves today’s remote‑work challenges but also lays a foundation for AI‑driven predictive insights that will define the next generation of HR tech.
The mini‑project presented here gives you a concrete starting point. Expand it with richer event types, multi‑locale support, and advanced analytics, and you’ll be well on your way to building a “forever people” hub that can scale with your organization’s global ambitions.
Resources
- Event Sourcing Basics – Martin Fowler’s classic article: Event Sourcing
- Apache Kafka Documentation – official guide for building resilient streaming pipelines: Kafka Docs
- WCAG 2.1 Accessibility Standards – essential for inclusive UI design: WCAG Overview
- Server‑Sent Events vs WebSockets – a practical comparison for real‑time feeds: MDN SSE vs WebSocket
- CloudEvents Specification – standardized event format for interoperability: CloudEvents
These resources will help you deepen your understanding of the concepts covered and guide you as you scale your own notification hub. Happy building!