MESSAGING GUIDE · UPDATED 11 AUG 2026

RabbitMQ: the message backbone

Every charging session event, meter value, CDR, and vending transaction on my platforms flows through RabbitMQ. When the broker is designed well, the whole system degrades gracefully; designed badly, it becomes the single point of failure you built on purpose.

Where RabbitMQ fits

Between services that must not block on each other. A charger reports a session stop; the session service records it and publishes an event; billing, notifications, partner-facing OCPI push, and analytics all consume independently. If the analytics consumer is down for an hour, sessions keep closing and invoices keep generating — that isolation is the entire value proposition.

Topology that scales with the platform

  • Topic exchanges with disciplined routing keys: session.stopped.cpo-x style keys let consumers bind precisely — adding a consumer never touches producers.
  • Queue per consumer service, not per event type: each service owns its queue, its prefetch, and its scaling.
  • Dead-letter exchanges everywhere: a poison message parks for inspection instead of blocking the queue — with alerting on DLQ depth, because a growing DLQ is an incident in progress.
  • Quorum queues for the money path: replicated queues for CDR and billing events; classic queues where loss is tolerable.

Consumer rules that prevent 2 a.m. calls

  • Idempotency is mandatory: every consumer handles redelivery — dedupe on message ID or natural business key before side effects.
  • Manual acks after successful processing, never auto-ack — an exception after auto-ack is a silently lost event.
  • Bounded retries with backoff, then DLQ — infinite retry on a malformed payload is a self-inflicted outage.
  • Prefetch tuned per workload: 1 for heavy CDR processing, 50+ for lightweight notifications.

Worked example: topology and a safe consumer

Topic exchange, quorum queue on the money path, dead-letter parking, and manual acks — declared in code so environments never drift:

await channel.ExchangeDeclareAsync("platform.events", ExchangeType.Topic, durable: true);
await channel.QueueDeclareAsync("billing.session-events", durable: true, arguments:
    new Dictionary<string, object> {
        ["x-queue-type"] = "quorum",                       // replicated: money path
        ["x-dead-letter-exchange"] = "platform.dlx",
        ["x-delivery-limit"] = 5                           // then park, don't loop
    });
await channel.QueueBindAsync("billing.session-events", "platform.events", "session.stopped.*");

consumer.ReceivedAsync += async (_, ea) =>
{
    try
    {
        await HandleAsync(Deserialize(ea.Body));           // idempotent inside
        await channel.BasicAckAsync(ea.DeliveryTag, false);
    }
    catch (TransientException)
    {
        await channel.BasicNackAsync(ea.DeliveryTag, false, requeue: false); // -> retry via DLX
    }
};

FAQ

RabbitMQ or Kafka?

RabbitMQ for work distribution and routing between services — my default. Kafka when you need replayable history or stream processing at scale. Most platforms need reliable routing long before they need a distributed log.

How do you handle broker failure?

Clustered brokers with quorum queues, publisher confirms on critical paths, and an outbox pattern so events survive service crashes between database commit and publish.

RabbitMQ alongside MQTT?

Yes — MQTT for device-to-cloud (chargers, vending machines), bridged into RabbitMQ for service-to-service distribution. Different protocols for different trust and reliability domains.