From One Event to Many Integrations: A Practical Guide to Fan-Out Architecture
Your product emits one event, but it needs to fan out to CRMs, webhooks, analytics, and internal systems. This post explains fan-out event routing patterns, per-tenant customization, and how to avoid a tangle of ad-hoc integrations.
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)
Everything else (CRMs, webhooks, Slack, etc.) is configured as rules in the integration layer.
Why event fan-out belongs outside your core app
1. Different destinations fail differently
CRMs might rate-limit or return 429s.
Webhooks might timeout or be misconfigured.
Analytics endpoints might be slow but not critical.
The customer UI needs to return fast, so it can't wait for these things to happen.
You don't want your core app to decide, per destination:
what's retryable
how long to backoff
when to give up
how to store dead letters
An integration layer can centralize that logic and treat each destination appropriately. When delivery does fail permanently, you need a dead letter queue to catch and replay events—not a silent data loss.
if (tenant === 'acme') { ... }else if (tenant === 'contoso') { ... }
Versus:
one event schema per event type
per-workspace rules + mappings in the integration layer
This is the multi-tenant integration pattern: workspaces isolate each customer's credentials, rules, and delivery state so one tenant's configuration never affects another.
3. Observability becomes manageable
Instead of:
grepping app logs for "hubspot failed"
trying to correlate job IDs with webhook IDs
You get:
one place to see an event and all downstream attempts
per-destination status and error messages
retry and dead-letter counts
The build-vs-buy reality
You can build a fan-out system yourself. An event bus, a rule evaluator, per-destination workers, retry tables, dead letter storage, a dashboard—it's all well-understood infrastructure. Most teams estimate a few weeks.
What they underestimate is the ongoing cost: every new destination introduces new auth patterns, new payload shapes, new rate limit behaviors, and new edge cases in error handling. After the third or fourth integration, you're not building product features anymore—you're maintaining a one-off integration platform.
The question isn't "can we build this?" It's "is this where our engineering hours create the most value?"
Implementing fan-out with Meshes
Meshes is designed to be that integration layer:
Events – you POST events into the API.
Rules – you configure routing, conditions, and destinations.
Connections – you securely store OAuth/API keys per workspace.
Delivery engine – you get fan-out, retries, and dead letters out of the box.