Most modern SaaS products have the same challenge:
“When X happens, we need to notify a lot of things.”
For example, when a lead is created:
- create/update a contact in HubSpot for sales
- fire a webhook to your backend
- add the lead to a Mailchimp list
- add the lead to Intercom for support
- log an event in your analytics stack
- notify someone in Slack
That’s a classic fan-out problem: one event, many destinations.
The naive approach (and why it hurts)
The straightforward way is to put all the calls in your app:
async function onLeadCreated(lead) {
await Promise.all([
// send the lead to all the needed services
syncToHubSpotForSales(lead),
sendInternalWebhook(lead),
addToMailchimpForEmails(lead),
addToIntercomForSupport(lead),
trackInAnalytics(lead),
]);
}
This works until:
- one destination is slow (your handler is now slow)
- one destination is flaky (you need retries)
- each integration wants different payload shapes
- you need per-tenant configuration (“ACME wants this, Contoso wants that”)
You quickly end up with:
- per-integration branching (
if tenant === 'acme' then ...) - mixed concerns (business logic + integration plumbing)
- tangled retries and error handling
Pattern: central event + external fan-out
A more robust pattern:
- Your core app emits a single, clean event.
- A dedicated integration layer receives the event.
- The integration layer:
- evaluates rules
- decides which destinations should receive it
- maps the payload
- handles retries, backoff, and dead letters
Your app code stays focused on business events, not integration details.
A concrete example
Your app just does this:
import fetch from 'node-fetch';
import { createMeshesMachineToken } from './meshes-auth';
async function emitLeadCreated(workspaceId: string, lead: any) {
const token = await createMeshesMachineToken();
await fetch('https://api.meshes.io/api/v1/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
workspace: workspaceId,
event: 'lead.created',
payload: lead,
}),
});
}
Everything else (CRMs, webhooks, Slack, etc.) is configured as rules in the integration layer.
