Fan-Out Messaging with URL Groups - QueueSaaS Blog
Relay
QueueSaaS Team

Fan-Out Messaging with URL Groups

Learn how to broadcast messages to multiple endpoints simultaneously using QueueSaaS URL Groups. Perfect for microservices architectures and event-driven systems.

url-groups fan-out microservices architecture

Fan-Out Messaging with URL Groups

URL Groups are one of QueueSaaS’s most powerful features for microservices architectures. They allow you to publish a single message that gets delivered to multiple endpoints simultaneously, perfect for event-driven systems, webhook broadcasting, and decoupled service communication.

What Are URL Groups?

URL Groups let you define a collection of endpoints and publish messages to all of them with a single API call. Each endpoint receives the message independently, with its own retry logic and delivery tracking.

Use cases:

  • Broadcasting events to multiple microservices
  • Sending webhooks to multiple third-party services
  • Notifying multiple systems of state changes
  • Implementing pub/sub patterns

Creating a URL Group

Basic Setup

import { Client } from '@anlyonhq/scheduler';

const client = new Client({
  apiKey: process.env.ANLYON_API_KEY!,
});

// Create a URL group with multiple endpoints
const group = await client.urlGroups.create({
  name: 'Production Webhooks',
  endpoints: [
    { 
      url: 'https://service1.example.com/webhook',
      name: 'User Service',
    },
    { 
      url: 'https://service2.example.com/webhook',
      name: 'Notification Service',
    },
    { 
      url: 'https://service3.example.com/webhook',
      name: 'Analytics Service',
    },
  ],
});

console.log(`Created group: ${group.data.id}`);

Custom Retry Configuration

Each endpoint can have its own retry configuration:

const group = await client.urlGroups.create({
  name: 'Critical Services',
  endpoints: [
    {
      url: 'https://critical-service.com/webhook',
      name: 'Critical Service',
      retryCount: 10, // More retries for critical services
    },
    {
      url: 'https://optional-service.com/webhook',
      name: 'Optional Service',
      retryCount: 3, // Fewer retries for optional services
    },
  ],
});

Publishing to a Group

Basic Broadcast

// Publish to all endpoints in the group
const result = await client.urlGroups.publish(group.data.id, {
  method: 'POST',
  body: {
    event: 'user.created',
    userId: '123',
    timestamp: new Date().toISOString(),
  },
});

console.log(`Published to ${result.data.total} endpoints`);
console.log(`Successful: ${result.data.successful}`);
console.log(`Failed: ${result.data.failed}`);

With Delay

You can also add a delay before broadcasting:

await client.urlGroups.publish(group.data.id, {
  method: 'POST',
  body: { event: 'order.processed', orderId: '456' },
  delay: 60, // Broadcast 60 seconds from now
});

Managing Groups

List All Groups

const groups = await client.urlGroups.list();
groups.data.forEach(group => {
  console.log(`${group.name}: ${group.id}`);
});

Get Group Details

const group = await client.urlGroups.get('group-id');
console.log(`Group: ${group.data.name}`);
console.log(`Endpoints: ${group.data.endpoints.length}`);
group.data.endpoints.forEach(endpoint => {
  console.log(`  - ${endpoint.name}: ${endpoint.url}`);
});

Add Endpoints

Dynamically add endpoints to an existing group:

await client.urlGroups.addEndpoint('group-id', {
  url: 'https://new-service.com/webhook',
  name: 'New Service',
  retryCount: 5,
});

Remove Endpoints

await client.urlGroups.removeEndpoint('group-id', 'endpoint-id');

Update Group Name

await client.urlGroups.update('group-id', {
  name: 'Updated Group Name',
});

Delete a Group

await client.urlGroups.delete('group-id');

Real-World Examples

Event-Driven Architecture

In a microservices architecture, URL Groups make it easy to broadcast events:

// When a user signs up, notify multiple services
const userCreatedGroup = await client.urlGroups.get('user-events-group');

await client.urlGroups.publish(userCreatedGroup.data.id, {
  method: 'POST',
  body: {
    event: 'user.created',
    userId: newUser.id,
    email: newUser.email,
    timestamp: new Date().toISOString(),
  },
});

// This automatically notifies:
// - Email service (send welcome email)
// - Analytics service (track signup)
// - Notification service (send push notification)
// - CRM service (create contact)

Third-Party Webhook Broadcasting

Broadcast webhooks to multiple third-party services:

// Create a group for payment webhooks
const paymentGroup = await client.urlGroups.create({
  name: 'Payment Webhooks',
  endpoints: [
    { url: 'https://stripe.com/webhook', name: 'Stripe' },
    { url: 'https://paypal.com/webhook', name: 'PayPal' },
    { url: 'https://internal-api.com/payments', name: 'Internal API' },
  ],
});

// Broadcast payment event
await client.urlGroups.publish(paymentGroup.data.id, {
  method: 'POST',
  body: {
    event: 'payment.completed',
    amount: 99.99,
    currency: 'USD',
    transactionId: 'txn_123',
  },
});

Multi-Region Deployment

Notify services across multiple regions:

const multiRegionGroup = await client.urlGroups.create({
  name: 'Multi-Region Services',
  endpoints: [
    { url: 'https://us-east.api.example.com/webhook', name: 'US East' },
    { url: 'https://eu-west.api.example.com/webhook', name: 'EU West' },
    { url: 'https://asia-pac.api.example.com/webhook', name: 'Asia Pacific' },
  ],
});

// Broadcast configuration update
await client.urlGroups.publish(multiRegionGroup.data.id, {
  method: 'POST',
  body: {
    event: 'config.updated',
    config: { featureFlag: true },
  },
});

Best Practices

  1. Group by purpose: Create groups based on event types or service categories
  2. Use descriptive names: Make it easy to identify groups in your dashboard
  3. Set appropriate retries: Critical services need more retries than optional ones
  4. Monitor delivery: Track success rates for each endpoint
  5. Handle failures gracefully: Some endpoints may fail while others succeed
  6. Limit group size: Keep groups under 50 endpoints for optimal performance
  7. Use idempotent endpoints: Ensure your webhooks can handle duplicate deliveries

Error Handling

When publishing to a group, some endpoints may succeed while others fail:

const result = await client.urlGroups.publish('group-id', {
  body: { event: 'test' },
});

if (result.data.failed > 0) {
  console.warn(`${result.data.failed} endpoints failed to receive the message`);
  // Check individual endpoint status
  result.data.results.forEach(endpointResult => {
    if (!endpointResult.success) {
      console.error(`Failed: ${endpointResult.endpointUrl} - ${endpointResult.error}`);
    }
  });
}

Performance Considerations

  • Parallel delivery: All endpoints receive messages in parallel
  • Independent retries: Each endpoint retries independently
  • Non-blocking: Failed endpoints don’t block successful ones
  • Scalable: Groups can handle high message volumes

Comparison: URL Groups vs Individual Messages

Without URL Groups (Manual)

// Publish to each endpoint individually
const endpoints = [
  'https://service1.com/webhook',
  'https://service2.com/webhook',
  'https://service3.com/webhook',
];

for (const url of endpoints) {
  await client.messages.publish({
    url,
    body: { event: 'test' },
  });
}
// Problems:
// - More API calls
// - Harder to manage
// - No aggregate tracking
// Single API call broadcasts to all
await client.urlGroups.publish('group-id', {
  body: { event: 'test' },
});
// Benefits:
// - Single API call
// - Centralized management
// - Aggregate tracking
// - Independent retries

Next Steps

Start broadcasting with URL Groups today! 📡