• Blog
  • Documentation
  • Pricing
  • FAQ
  • Contact
Sign InSign Up

A single, reliable layer for all your product's integrations - rules, routing, retries, and fan-out included.

© Copyright 2025 Meshes. All Rights Reserved.

About
  • Blog
  • Contact
Product
  • Documentation
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy

From One Event to Many Integrations: A Practical Guide to Fan-Out Architecture

Your product emits one event, but it needs to drive CRMs, webhooks, analytics, and internal systems. This post explains fan-out patterns and how to avoid a tangle of ad-hoc integrations.

Cover Image for From One Event to Many Integrations: A Practical Guide to Fan-Out Architecture

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:

  1. Your core app emits a single, clean event.
  2. A dedicated integration layer receives the event.
  3. 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.

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.

2. Per-tenant customization explodes combinatorics

Imagine:

  • 1,000 customers
  • each with their own preference of:
    • which CRM to use
    • which fields to map
    • which events they care about

Baking that into your app produces a mess of:

if (tenant === 'acme') { ... }
else if (tenant === 'contoso') { ... }

Versus:

  • one event schema per event type
  • per-workspace rules + mappings in the integration layer

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

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.

Your architecture becomes:

  1. App: emit lead.created.
  2. Meshes:
    • match rules on lead.created
    • fan-out to HubSpot, Mailchimp, webhooks, etc.
    • manage retries and failures

You keep one clean pipeline:

Domain events in → integrations out.

When to adopt fan-out architecture

You’ll get the most value when:

  • you’re integrating with more than a couple of third-party services
  • customers are asking for per-tenant integration behavior
  • you’re spending time debugging “where did this webhook go?”

If you’re already there, it might be time to centralize event fan-out instead of bolting on another webhook handler in your core app.

That’s exactly the job Meshes is trying to do for you.