Est.

Queues, Topics, and Event Sourcing in SaaS Backend Design

Choose the right async primitive before it costs you data at 3 a.m.

· 10 min read
Cover illustration for “Queues, Topics, and Event Sourcing in SaaS Backend Design”
SaaS Backend Infrastructure · October 16, 2026 · 10 min read · 2,180 words

Most SaaS backends start with the same quiet assumption: that any system which moves messages between services is just "async messaging." Queues, topics, and event streams make three different promises, and getting them mixed up is what turns a working product into one that loses events at 3 a.m. The fix starts with treating each primitive as a contract, not a synonym.

Why queues, topics, and event streams are not the same primitive

A message queue delivers each message to exactly one consumer, then deletes it. The contract is task distribution: work should be done once, by whichever worker claims it first. Two workers pulling from the same queue never both process the same job. That's the point.

A pub/sub topic fans a single message out to every active subscriber at the moment of publication. The contract is broadcast notification. Nothing waits around.

An event stream, like Apache Kafka or Amazon Kinesis, is a durable, ordered, replayable log. Each consumer group reads from its own offset, independently of every other group. The contract is a permanent record that any number of readers can traverse at any time, at any speed, starting from any point. Nothing is deleted on acknowledgment, because acknowledgment means something different here.

The mismatch between contract and use case is what produces silent data loss, duplicated work, or missed events. It doesn't come from implementation bugs. If your billing worker restarts at night, you could still lose a payment notification even with flawless code, because the primitive is wrong.

Diagram: Three Primitives, Three Contracts. Visualizes: Show the three async messaging primitives as a ranked or stepped comparison anchored to their core contract, not their implementation.

How to match the primitive to the SaaS job it actually needs to do

The right primitive follows from the job, not from which tool your team already knows. Command dispatch calls for a queue. System-wide notifications call for a topic. If you need replay or multi-reader consistency, you need a stream.

Queue jobs are the discrete, done-and-gone kind: sending a transactional email after signup, resizing an uploaded image, processing a webhook payload. One unit of work, one worker, then it's finished. If two workers both ran it, you'd send two welcome emails, and nobody wants that.

Topic jobs fan out to multiple services that each react independently. A UserUpgraded event might tell the billing service to update plan state, the onboarding service to unlock features, and the analytics service to record a data point. None of them need to read last month's events.

Stream jobs are for cases where different consumers need to read the same history at different times or speeds. A metering pipeline ingesting API call events for usage-based billing is the classic case. Event sourcing read-model projections are a third.

This classification also determines broker choice downstream. AWS SNS/SQS or EventBridge give you cloud-native fan-out without running a broker yourself. Pick the job first, then the tool follows.

What event sourcing actually commits a team to — and where it earns that commitment

Event sourcing is not a messaging pattern. It's a persistence strategy that replaces mutable state with an immutable, replayable sequence of business facts. That distinction changes what a system can and cannot do, and it sits a layer above your broker choice.

In a traditional CRUD system, the database stores the current row: status = paid. In an event-sourced system, the store holds OrderCreated, ItemAddedToOrder, PaymentAuthorized. Current state is derived by replaying those events, not by reading a current row.

The replay capability is where event sourcing earns its complexity. Rebuild a broken read model by replaying from the beginning. None of that is possible when the database only remembers what's true right now.

CQRS enters naturally here. The command side appends events and enforces business rules. The query side reads from projections optimized for the UI, reporting, or analytics. The write and read models have genuinely different performance and shape requirements, so splitting them stops being an abstraction and becomes the obvious move.

The bounded contexts that earn event sourcing are the ones where "how did we get here" is a first-class product question: financial ledgers, billing history, audit-heavy regulated workflows. Projection pipelines, idempotent handlers, event schema versioning, and replay procedures are now owned by the team. These are real ongoing costs, not one-time setup.

Why most SaaS teams should reach event sourcing last, not first

The biggest practical mistake in SaaS backend design is adopting event sourcing, CQRS, and microservices simultaneously on an MVP. It adds months of development time for problems that do not yet exist. AgileSoftLabs' 2026 SaaS stack guide is direct about this: start with a modular monolith.

The right starting primitive for most SaaS products is PostgreSQL as the primary store, a simple queue for async jobs, and a read replica before any CQRS. That combination handles a large multiple of read traffic with minimal code changes. You can ship, and you can scale reads without rewriting your write model.

Event sourcing's complexity costs are concrete. All of that requires engineering capacity that could otherwise go to product. On an MVP, the opportunity cost is brutal. The staged path is modular monolith with async queues, then selective event streaming for high-throughput or audit-critical domains, then event sourcing only for the bounded contexts where replay is a genuine product requirement. The signal that a team is ready is a concrete problem, not a theoretical one: an audit requirement from a compliance review, a billing dispute they cannot reconstruct, a read model too slow to rebuild from the primary store.

The three reliability patterns every event-driven SaaS backend needs before it goes to production

A well-chosen primitive fails in production without three supporting patterns: the transactional outbox, idempotent consumers, and a monitored dead-letter queue. Most teams discover this by losing events under load rather than by design. The transactional outbox fixes the most common failure. Publishing events directly from application code after a database commit means losing events when the process crashes between the commit and the publish. The fix is simple: write the event to an outbox table in the same transaction as the state change. A separate relay process (Debezium, a dedicated poller, or a Temporal workflow) reads the outbox and publishes to the broker. The event cannot be lost without the transaction also rolling back.

This is not optional at production scale. The window between a successful commit and a failed publish is small but real, and in a payment or billing context it translates to lost revenue or incorrect customer state. You don't get a second chance to charge someone.

Idempotent consumers handle the fact that event brokers deliver at-least-once by default. A consumer must process the same event twice without producing a different outcome the second time. Without idempotency, at-least-once delivery becomes a source of double charges and duplicate records under retry conditions.

A monitored dead-letter queue catches poison messages. One message that a consumer cannot process will block a queue indefinitely or trigger infinite retries, unless it routes to a DLQ after a configurable retry ceiling. The DLQ is not a graveyard. It must alert on non-empty state, because a message in the DLQ is an unhandled business failure, not a dismissed error. You need to know about it the moment it lands.

How usage-based billing exposes the metering pipeline as a stream problem, not a billing problem

Usage-based billing cannot be grafted onto a payment-transaction architecture. Usage events arrive at a fundamentally different rate and volume than payment transactions. A quantity-update model built for the former breaks under the latter.

Legacy billing architectures make engineers build an external database, aggregate usage themselves, and push totals to the billing system at period end. The event stream is invisible to the billing layer and must be manually reconciled. That reconciliation is where revenue leaks.

Modern usage-based billing infrastructure flips this. The metering layer absorbs the raw event firehose directly, handles aggregation internally, and outputs financial truth. The billing system reads projections, not raw events. This is precisely the CQRS pattern applied to monetization: the write side is the event stream of API calls, tokens consumed, or storage used. The read side is the aggregated billing projection the customer invoice is built from.

For SaaS teams operating under GDPR or processing proprietary customer telemetry, keeping the metering pipeline entirely within their own cloud environment is a data-sovereignty requirement. Open-source billing infrastructure that deploys inside the operator's environment, such as Lago, addresses this directly. The full picture of a customer's state is immediately visible.

How broker and infrastructure choice shapes everything the application layer depends on

Broker selection is not a reversible configuration choice. It sets the ceiling on throughput, the floor on operational burden, and the shape of vendor dependency before the first event is published. You'll live with this decision for years.

Apache Kafka, via Confluent Cloud, AWS MSK, or self-managed KRaft deployments, is the established default for high-throughput durable streams. It provides log-based storage, partitioning for parallelism, and replication for resilience. When someone says "event stream" without qualifiers, they usually mean Kafka.

Apache Pulsar is gaining enterprise traction for multi-tenancy, geo-replication, and tiered storage for long-term event retention. That matters for regulated SaaS that must retain event history across jurisdictions. The Apache Pulsar docs note that tiered storage offloads data from hot to cold storage, such as S3 and GCS, as it ages out.

AWS EventBridge, Google Cloud Pub/Sub, and Azure Event Hubs are managed shortcuts for teams that don't want to operate brokers. At low volume, they're a gift. At high volume, they're a bill you didn't plan for.

RabbitMQ covers queue-heavy workloads with complex routing requirements but offers lower throughput than Kafka. And the event sourcing case imposes a specific requirement: the broker must support replayability and consumer-group offsets. A simple queue that deletes on acknowledgment cannot serve as an event store.

Schema governance is a broker-layer concern that teams consistently underweight. Event schemas need a registry and a versioning strategy, because a breaking schema change silently breaks every downstream consumer without a compile error. That's the worst kind of failure: the kind that doesn't fail loudly.

What AI-native workflows add to the event-driven backend picture

AI agents consuming backend events have the same primitive requirements as any other consumer: predictable schemas, idempotent handlers, and observable delivery. But they surface a new category of failure when backends aren't designed for programmatic consumption. An AI agent wants an API that returns structured, predictable outputs. It wants an MCP server or CLI interface it can call programmatically. Usage-based billing is therefore not just a growth lever but an architectural requirement for agent-facing SaaS. You can't bill an agent per seat. It doesn't have one.

The metering pipeline becomes even more critical when agents are the product users. API call volumes from agents can dwarf human interaction volumes. All three end with an unhappy customer.

The structural risk that applies to agent stacks generally applies here. A lightweight event-driven setup that works well at low ARR can become a significant operational cost at scale if always-on agents generate event volumes the infrastructure was not sized for. The agent can query the full state of a customer relationship, who authenticated, what they consumed, what they owe, without orchestrating joins across disconnected services.

Putting the primitives together: a staged adoption path for SaaS teams

Diagram: The Staged Path to Event-Driven Architecture. Visualizes: Visualize the three-stage adoption sequence as a progression or staircase.

The SaaS teams that get event-driven architecture right do not adopt it wholesale. They adopt the right primitive for the right job at the right stage, then add reliability patterns and complexity only as concrete problems demand. Stage 1 is a modular monolith with async queues. PostgreSQL is the primary store. The outbox pattern goes in from day one to prevent event loss even at low volume. This stage handles the vast majority of early SaaS workloads without operational overhead. You're not operating a broker, you're shipping a product.

Stage 2 is selective event streaming for high-throughput or fan-out domains. Add a durable event stream, Kafka or a managed equivalent, when metering, audit, or multi-service fan-out becomes a concrete requirement. Usage-based billing metering belongs here: the event stream absorbs the product telemetry firehose, and the billing projection reads aggregated output.

Stage 3 is event sourcing for bounded contexts that earn it. Apply event sourcing selectively to the domains where replay and full business history are genuine product requirements: financial ledger, billing dispute resolution, compliance audit trails. CQRS enters here naturally, the write model appends events and the read model serves projections, but only for the bounded context, not the whole system.

The Merchant of Record model matters at the seam between metering and billing. When tax compliance, chargebacks, and invoicing are handled by the platform rather than the product team, the event-driven metering pipeline can feed directly into financial output. The team doesn't build a separate reconciliation layer. That's one less distributed systems problem to own.

A unified backend that holds authentication, payments, the customer database, and the metering pipeline in one system reduces the synchronization problem that makes event-driven architecture brittle in fragmented stacks. Every event is already co-located with the customer record it describes, eliminating a whole class of cross-system consistency bugs. Not by interrogating five disconnected services.

More in SaaS Backend Infrastructure