<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>mqutils</title><link>https://mqutils.dev/</link><description>Recent content on mqutils</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://mqutils.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Upgrading from v1</title><link>https://mqutils.dev/upgrading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/upgrading/</guid><description>&lt;blockquote>
&lt;p>This page is rendered from &lt;a href="https://gitlab.com/digitalxero/mqutils/-/blob/main/UPGRADING.md">UPGRADING.md&lt;/a> in the repository.&lt;/p>&lt;/blockquote>
&lt;p>v2 is a breaking release. The import paths, the handler contract, and several
transport behaviors changed. This guide covers everything a v1 consumer needs.&lt;/p>
&lt;h2 id="import-paths">Import paths&lt;a class="anchor" href="#import-paths" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>Every module gained the Go-required &lt;code>/v2&lt;/code> suffix:&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>v1&lt;/th>
 &lt;th>v2&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mqutils&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mqutils/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mqutils/types&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mqutils/v2/types&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>—&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mqutils/v2/runner&lt;/code> (new)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-amqp&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-amqp/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-kafka&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-kafka/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-nats&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-nats/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-aws&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-aws/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-gcp&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-gcp/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>go.digitalxero.dev/mq-redis&lt;/code>&lt;/td>
 &lt;td>&lt;code>go.digitalxero.dev/mq-redis/v2&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="handlers-return-an-error-the-headline-change">Handlers return an error (the headline change)&lt;a class="anchor" href="#handlers-return-an-error-the-headline-change" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// v1: handlers acked/nacked manually and returned nothing.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">msg&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Message&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Nack&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Ack&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// v2: return nil to acknowledge, an error to reject. The shared consumer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// runtime settles the message (with retry) based on the return value.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">msg&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Message&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">msg&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;ul>
&lt;li>Manual &lt;code>msg.Ack()&lt;/code>/&lt;code>msg.Nack()&lt;/code> inside a handler still works; the runtime
treats the duplicate settlement (&lt;code>types.ErrAlreadyAcknowledged&lt;/code>) as success.&lt;/li>
&lt;li>A handler that returns nil &lt;strong>without&lt;/strong> acking is now acknowledged by the
runtime; return an error when processing did not succeed.&lt;/li>
&lt;li>Batch handlers (&lt;code>BatchHandlerFunc&lt;/code>) follow the same contract: nil settles
the whole batch, an error rejects every message the handler did not settle
itself.&lt;/li>
&lt;li>Double ack/nack on a message now uniformly returns
&lt;code>types.ErrAlreadyAcknowledged&lt;/code> on every backend (some previously returned
nil).&lt;/li>
&lt;/ul>
&lt;h2 id="behavior-changes-to-plan-for">Behavior changes to plan for&lt;a class="anchor" href="#behavior-changes-to-plan-for" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Connection loss with &lt;code>auto_reconnect: false&lt;/code> returns an error from
&lt;code>Run&lt;/code>&lt;/strong> instead of idling forever. Enable &lt;code>auto_reconnect&lt;/code> if you relied on
the old hang.&lt;/li>
&lt;li>&lt;strong>Kafka has real at-least-once semantics.&lt;/strong> Acks commit offsets (the old
transport committed everything on receipt regardless of handler outcome).
Nacks republish to &lt;code>retry_topic&lt;/code> with a &lt;code>retry_count&lt;/code> header and land on
&lt;code>dead_letter_topic&lt;/code> once &lt;code>retry_max_retries&lt;/code> is exhausted; without a
&lt;code>retry_topic&lt;/code> failures are retried locally with backoff. Handlers must be
idempotent — rebalances can redeliver in-flight messages.&lt;/li>
&lt;li>&lt;strong>Kafka &lt;code>skip_verify&lt;/code> no longer disables TLS.&lt;/strong> TLS turns on via
&lt;code>kafkas://&lt;/code> or &lt;code>tls_enabled: true&lt;/code>; &lt;code>skip_verify&lt;/code> only relaxes certificate
verification.&lt;/li>
&lt;li>&lt;strong>NATS no longer drops messages under backpressure&lt;/strong> — delivery blocks
(context-aware) when the internal channel is full, matching every other
backend.&lt;/li>
&lt;li>&lt;strong>Batch processing works on every backend&lt;/strong> (&lt;code>enable_batch_processing&lt;/code>,
&lt;code>batch_size&lt;/code>, &lt;code>batch_timeout&lt;/code>), not just AMQP. &lt;code>batch_timeout&lt;/code> is a real
duration string (&lt;code>&amp;quot;250ms&amp;quot;&lt;/code>); the v1 AMQP implementation misapplied it by a
factor of a million and never flushed partial batches.&lt;/li>
&lt;li>&lt;strong>Previously dead configuration is now live.&lt;/strong> If you set any of these in
v1 they silently did nothing and WILL take effect after upgrading:
AMQP &lt;code>channel_pool_size&lt;/code>/&lt;code>mandatory&lt;/code>/producer &lt;code>durable&lt;/code>+&lt;code>auto_delete&lt;/code>;
NATS &lt;code>max_reconnects&lt;/code>/&lt;code>reconnect_wait&lt;/code>/&lt;code>queue_group&lt;/code>/&lt;code>consumer_name&lt;/code>;
SQS &lt;code>dead_letter_queue_url&lt;/code> (applies a RedrivePolicy), consumer tuning and
FIFO group/dedup fields; GCP &lt;code>dead_letter_topic&lt;/code>+&lt;code>max_delivery_attempts&lt;/code>
(DeadLetterPolicy — requires IAM grants to the Pub/Sub service account),
publish batching thresholds, subscription tuning; Redis auth/pool/timeout
fields, stream trimming, and &lt;code>claim_idle_time&lt;/code>/&lt;code>pending_message_max_age&lt;/code>
(XAutoClaim pending recovery, Redis ≥ 6.2).&lt;/li>
&lt;/ul>
&lt;h2 id="removed-api">Removed API&lt;a class="anchor" href="#removed-api" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;code>amqp&lt;/code> producer &lt;code>immediate&lt;/code> config key (RabbitMQ ≥ 3.0 rejects the flag).&lt;/li>
&lt;li>&lt;code>aws&lt;/code> consumer &lt;code>message_retention_days&lt;/code> (was never applied; honoring it
would clobber externally provisioned retention).&lt;/li>
&lt;li>GCP &lt;code>skip_verify&lt;/code> config keys (meaningless for the gRPC client; the
emulator path uses &lt;code>PUBSUB_EMULATOR_HOST&lt;/code>).&lt;/li>
&lt;li>Exported &lt;code>AcknowledgeMessage&lt;/code>/&lt;code>NacknowledgeMessage&lt;/code> helpers in the kafka
and nats packages — use &lt;code>runner.AckWithRetry&lt;/code>/&lt;code>runner.NackWithRetry&lt;/code>.&lt;/li>
&lt;li>&lt;code>Transport.DeclareExchange&lt;/code>/&lt;code>Transport.BindQueue&lt;/code> now take a
&lt;code>context.Context&lt;/code>, and &lt;code>BindQueue&lt;/code>&amp;rsquo;s parameters are exchange-first with a
&lt;code>durable&lt;/code> flag (matching what every implementation always did).&lt;/li>
&lt;/ul>
&lt;h2 id="new-in-v2-non-breaking-once-youre-on-the-new-paths">New in v2 (non-breaking once you&amp;rsquo;re on the new paths)&lt;a class="anchor" href="#new-in-v2-non-breaking-once-youre-on-the-new-paths" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Typed builders&lt;/strong>: &lt;code>mqutils.NewConsumerBuilder()&lt;/code> / &lt;code>NewProducerBuilder()&lt;/code>
configure consumers without viper and take handler funcs directly.&lt;/li>
&lt;li>&lt;strong>Canonical config keys&lt;/strong> accepted by every backend alongside native keys:
&lt;code>destination&lt;/code>, &lt;code>max_retries&lt;/code>, &lt;code>consumer_group&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Symmetric URL schemes&lt;/strong>: consumers and producers register identical
scheme sets (&lt;code>sqss://&lt;/code>, &lt;code>gcp://&lt;/code>, &lt;code>redisstreams://&lt;/code>, &amp;hellip; all work on both
sides now).&lt;/li>
&lt;li>&lt;strong>&lt;code>runner&lt;/code> package&lt;/strong>: the shared consumer loop (ack-retry helpers,
batching, graceful shutdown, reconnect backoff, retry-budget drops with a
dead-letter hook) is exported for building custom backends.&lt;/li>
&lt;li>&lt;strong>AMQP blocked-connection handling&lt;/strong>: when RabbitMQ raises a memory/disk
alarm (&lt;code>connection.blocked&lt;/code>), fire-and-forget publishes are accepted into a
bounded internal queue (&lt;code>publish_queue_size&lt;/code>, default 1000; overflow returns
&lt;code>types.ErrPublishQueueFull&lt;/code>) and flushed in order on unblock — v1 wrote
into the stalled socket and hung. New publisher-confirms mode
(&lt;code>publisher_confirms: true&lt;/code>, &lt;code>confirm_timeout&lt;/code>): every publish waits for the
broker&amp;rsquo;s ack (&lt;code>types.ErrPublishNacked&lt;/code> on nack) and publishes during a
block are rejected immediately with &lt;code>types.ErrConnectionBlocked&lt;/code> for the
caller to handle. NATS gains &lt;code>reconnect_buf_size&lt;/code> to bound the client
buffer that already queues publishes while disconnected.&lt;/li>
&lt;/ul>
&lt;h2 id="reliability-update-for-existing-v2-deployments">Reliability update for existing v2 deployments&lt;a class="anchor" href="#reliability-update-for-existing-v2-deployments" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;h3 id="rabbitmq-queue-expiration-is-opt-in">RabbitMQ queue expiration is opt-in&lt;a class="anchor" href="#rabbitmq-queue-expiration-is-opt-in" aria-label="Link to this section">&lt;/a>&lt;/h3>&lt;p>&lt;code>transient_queue_expires&lt;/code> is now disabled when omitted or zero. A positive
integer enables automatic &lt;code>x-expires&lt;/code> in milliseconds for transient queues
that RabbitMQ 4.3+ requires the library to declare durable. The durability
workaround remains in effect. Intentionally durable queues do not acquire
an expiration automatically.&lt;/p></description></item><item><title>Why mqutils</title><link>https://mqutils.dev/why-mqutils/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/why-mqutils/</guid><description>&lt;h2 id="the-problem-it-solves">The problem it solves&lt;a class="anchor" href="#the-problem-it-solves" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>Every broker SDK has its own connection lifecycle, its own way to acknowledge or reject a message, its own retry story, and its own configuration shape. A service that runs against RabbitMQ in one deployment and SQS in another ends up with two consumer implementations, or with an in-house abstraction that someone has to maintain.&lt;/p>
&lt;p>mqutils is that abstraction, maintained once. It puts a single consumer and producer contract in front of RabbitMQ AMQP and Streams, Kafka, NATS Core and JetStream, AWS SQS, GCP Pub/Sub, and Redis Pub/Sub and Streams. The URL scheme selects the backend; each backend is its own Go module, so you compile only the ones you use.&lt;/p></description></item><item><title>Getting started</title><link>https://mqutils.dev/getting-started/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/getting-started/</guid><description>&lt;p>mqutils puts one consumer and producer interface across seven backend modules. This guide installs the core module plus one backend, then walks through a consumer, a producer, batching, health checks and shutdown.&lt;/p>
&lt;blockquote>
&lt;p>Upgrading from v1? See &lt;a href="https://mqutils.dev/upgrading/">Upgrading from v1&lt;/a>: v2 changed the import paths and the handler contract.&lt;/p>&lt;/blockquote>
&lt;h2 id="installation">Installation&lt;a class="anchor" href="#installation" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>Add the core library and the backend module(s) you need to your Go project:&lt;/p>
&lt;div class="code">&lt;span class="lang">bash&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mqutils/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># One or more backends&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-amqp/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-kafka/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-nats/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-aws/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-gcp/v2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go get go.digitalxero.dev/mq-redis/v2&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>The RabbitMQ Stream module requires Go 1.25 and has separate
&lt;a href="https://mqutils.dev/rabbitmq-streams/#module-availability-and-evaluating-this-change">availability and local-checkout instructions&lt;/a>
while its new vanity mappings and release are prepared. Existing module imports
above are unaffected.&lt;/p></description></item><item><title>Core types</title><link>https://mqutils.dev/docs/types/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/types/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mqutils/v2/types&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#constants">Constants&lt;/a>&lt;/li>
&lt;li>&lt;a href="#variables">Variables&lt;/a>&lt;/li>
&lt;li>&lt;a href="#ApplyConfigAliases">func ApplyConfigAliases(v *viper.Viper, aliases AliasMap)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewErrUnknownPublisherType">func NewErrUnknownPublisherType(url string) error&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RedactURL">func RedactURL(raw string) string&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisterBatchHandler">func RegisterBatchHandler(name string, handler BatchHandlerFunc)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisterConsumer">func RegisterConsumer(consumer NewConsumerFunc, urlPrefixes &amp;hellip;string)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisterHandler">func RegisterHandler(name string, handler HandlerFunc)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisterProducer">func RegisterProducer(producer NewProducerFunc, urlPrefixes &amp;hellip;string)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisteredConsumerPrefixes">func RegisteredConsumerPrefixes() []string&lt;/a>&lt;/li>
&lt;li>&lt;a href="#RegisteredProducerPrefixes">func RegisteredProducerPrefixes() []string&lt;/a>&lt;/li>
&lt;li>&lt;a href="#SetConfigOverride">func SetConfigOverride(v *viper.Viper, key string, value any)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#AliasMap">type AliasMap&lt;/a>&lt;/li>
&lt;li>&lt;a href="#BatchHandlerFunc">type BatchHandlerFunc&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#GetBatchHandler">func GetBatchHandler(name string) BatchHandlerFunc&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#ConfigurationError">type ConfigurationError&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#ConfigurationError.Error">func (e *ConfigurationError) Error() string&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#ConfigurationErrors">type ConfigurationErrors&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#ConfigurationErrors.Add">func (e *ConfigurationErrors) Add(field, message string)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#ConfigurationErrors.Error">func (e *ConfigurationErrors) Error() string&lt;/a>&lt;/li>
&lt;li>&lt;a href="#ConfigurationErrors.HasErrors">func (e *ConfigurationErrors) HasErrors() bool&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#Consumer">type Consumer&lt;/a>&lt;/li>
&lt;li>&lt;a href="#ErrUnknownConsumerType">type ErrUnknownConsumerType&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#NewErrUnknownConsumerType">func NewErrUnknownConsumerType(typeName string) *ErrUnknownConsumerType&lt;/a>&lt;/li>
&lt;li>&lt;a href="#ErrUnknownConsumerType.Error">func (e ErrUnknownConsumerType) Error() string&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#ErrUnknownPublisherType">type ErrUnknownPublisherType&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#ErrUnknownPublisherType.Error">func (e ErrUnknownPublisherType) Error() string&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#HandlerFunc">type HandlerFunc&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#GetHandler">func GetHandler(name string) HandlerFunc&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#HealthCheck">type HealthCheck&lt;/a>&lt;/li>
&lt;li>&lt;a href="#HealthCheckBuilder">type HealthCheckBuilder&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#NewHealthCheckBuilder">func NewHealthCheckBuilder() HealthCheckBuilder&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#HealthCheckFunc">type HealthCheckFunc&lt;/a>&lt;/li>
&lt;li>&lt;a href="#HealthChecker">type HealthChecker&lt;/a>&lt;/li>
&lt;li>&lt;a href="#HealthStatus">type HealthStatus&lt;/a>&lt;/li>
&lt;li>&lt;a href="#Message">type Message&lt;/a>&lt;/li>
&lt;li>&lt;a href="#MessageBuilder">type MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewConsumerFunc">type NewConsumerFunc&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#GetConsumer">func GetConsumer(url string) (NewConsumerFunc, bool)&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#NewProducerFunc">type NewProducerFunc&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#GetProducer">func GetProducer(url string) (NewProducerFunc, bool)&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#Producer">type Producer&lt;/a>&lt;/li>
&lt;li>&lt;a href="#Publisher">type Publisher&lt;/a>&lt;/li>
&lt;li>&lt;a href="#Transport">type Transport&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="constants">Constants&lt;a class="anchor" href="#constants" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>&lt;a name="ConfigKeyDestination">&lt;/a>Canonical configuration keys accepted by every backend alongside its native keys. Backends map these onto their native keys with ApplyConfigAliases before unmarshalling their configuration.&lt;/p></description></item><item><title>Main package</title><link>https://mqutils.dev/docs/mqutils/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/mqutils/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mqutils/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Package mqutils provides a unified abstraction layer for working with multiple message queue systems. It supports AMQP/RabbitMQ, Apache Kafka, NATS, AWS SQS, GCP Pub/Sub, and Redis with a consistent API.&lt;/p>
&lt;p>The package uses URL-based routing to automatically select the appropriate message queue implementation based on the connection URL scheme:&lt;/p>
&lt;ul>
&lt;li>amqp://, amqps:// - RabbitMQ/AMQP&lt;/li>
&lt;li>kafka://, kafkas:// - Apache Kafka&lt;/li>
&lt;li>nats://, natss://, jetstream:// - NATS Core/JetStream&lt;/li>
&lt;li>sqs://, sqss:// - AWS SQS&lt;/li>
&lt;li>pubsub:// - GCP Pub/Sub&lt;/li>
&lt;li>redis://, rediss://, redisstream:// - Redis Pub/Sub &amp;amp; Streams&lt;/li>
&lt;/ul>
&lt;p>Example usage:&lt;/p></description></item><item><title>Kafka security and transactions</title><link>https://mqutils.dev/kafka-security-transactions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/kafka-security-transactions/</guid><description>&lt;p>The Kafka module supports TLS, SASL PLAIN, SCRAM-SHA-256, and SCRAM-SHA-512.
Its transactional producer publishes atomic groups of records across topics and
partitions. Consumer offsets remain independent: this API does not provide an
atomic consume-transform-produce transaction.&lt;/p>
&lt;h2 id="sasl-configuration">SASL configuration&lt;a class="anchor" href="#sasl-configuration" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>Producer and consumer configurations use the same authentication settings. The
settings also apply to reconnects, administrative requests, and retry/dead-letter
publishers created by the Kafka transport.&lt;/p>
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">viper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;kafkas://broker.example.com:9093&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sasl_enabled&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sasl_mechanism&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;SCRAM-SHA-256&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sasl_username&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;KAFKA_USERNAME&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sasl_password&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;KAFKA_PASSWORD&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">producer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">kafka&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewKafkaProducer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">defer&lt;/span> &lt;span class="nx">producer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">producer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="nx">producer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Publish&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;order-42&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;orders&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;customer-7&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nb">byte&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">`{&amp;#34;id&amp;#34;:42}`&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Imports used in these fragments are &lt;code>github.com/spf13/viper&lt;/code>,
&lt;code>go.digitalxero.dev/mq-kafka/v2&lt;/code> as &lt;code>kafka&lt;/code>, and the standard packages shown by
usage. Put credentials into Viper explicitly or bind its environment variables;
&lt;code>${VARIABLE}&lt;/code> inside YAML is not automatically expanded by mqutils.&lt;/p></description></item><item><title>Runner</title><link>https://mqutils.dev/docs/runner/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/runner/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mqutils/v2/runner&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Package runner provides the shared consumer processing loop used by every mqutils backend. It owns the concerns that are identical across message queue systems — dispatching messages to handlers, acknowledging based on the handler&amp;rsquo;s returned error, batch collection with a real flush timer, retry-budget drops with a dead-letter hook, reconnect with exponential backoff, and graceful shutdown draining — so backends only supply the broker-specific pieces via callbacks.&lt;/p></description></item><item><title>RabbitMQ Streams</title><link>https://mqutils.dev/rabbitmq-streams/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/rabbitmq-streams/</guid><description>&lt;p>&lt;code>go.digitalxero.dev/mq-rmqstream/v2&lt;/code> uses RabbitMQ&amp;rsquo;s native Stream protocol.
It is a separate backend from the AMQP module and requires Go 1.25 or newer.
Importing it registers &lt;code>rabbitmq-stream://&lt;/code> and &lt;code>rabbitmq-stream+tls://&lt;/code> for both
producers and consumers.&lt;/p>
&lt;p>Publishing succeeds only after broker confirmation. Consumers acknowledge by
storing progress, not deleting records. Only the settled prefix of delivered
records can advance the checkpoint; unfinished earlier work prevents later
handlers from skipping it. A crash can replay records, and stream retention can
remove records independently of consumption. Design handlers for at-least-once
processing.&lt;/p></description></item><item><title>AMQP / RabbitMQ</title><link>https://mqutils.dev/docs/amqp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/amqp/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-amqp/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#NewAMQPConsumer">func NewAMQPConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewAMQPProducer">func NewAMQPProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewAMQPTransport">func NewAMQPTransport(retry bool, prefetch int) types.Transport&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() types.MessageBuilder&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a name="NewAMQPConsumer">&lt;/a>&lt;/p>
&lt;h2 id="func-newamqpconsumer" class="sig">&lt;span class="kind">func&lt;/span>&lt;a href="https://gitlab.com/digitalxero/mqutils/blob/main/amqp/consumer.go#L321">NewAMQPConsumer&lt;/a>&lt;a class="anchor" href="#func-newamqpconsumer" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewAMQPConsumer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">config&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">viper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Viper&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Consumer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>NewAMQPConsumer creates a new AMQP/RabbitMQ consumer with the provided configuration. This function is typically called by mqutils.NewConsumer when it detects an AMQP URL.&lt;/p></description></item><item><title>Apache Kafka</title><link>https://mqutils.dev/docs/kafka/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/kafka/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;p>See &lt;a href="https://mqutils.dev/kafka-security-transactions/">Kafka security and transactions&lt;/a> for SASL
configuration, the scoped transaction API, and commit-outcome handling.&lt;/p>
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-kafka/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#variables">Variables&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewKafkaConsumer">func NewKafkaConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewKafkaProducer">func NewKafkaProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewKafkaTransport">func NewKafkaTransport() types.Transport&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() types.MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#TransactionalProducer">type TransactionalProducer&lt;/a>&lt;/li>
&lt;li>&lt;a href="#TransactionalProducerBuilder">type TransactionalProducerBuilder&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#NewTransactionalProducerBuilder">func NewTransactionalProducerBuilder() TransactionalProducerBuilder&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="variables">Variables&lt;a class="anchor" href="#variables" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;p>&lt;a name="ErrTransactionRequired">&lt;/a>&lt;/p>
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ErrTransactionRequired rejects ordinary publishes on a transactional producer.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ErrTransactionRequired&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Kafka publication requires InTransaction&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ErrTransactionClosed means the callback publisher is no longer usable.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ErrTransactionClosed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Kafka transaction scope is closed or aborted&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ErrTransactionOutcomeUnknown means commit may have reached Kafka. Do not replay&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// the callback automatically; reconcile application state before retrying.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ErrTransactionOutcomeUnknown&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Kafka transaction commit outcome is unknown&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ErrTransactionalProducerClosed indicates that Close released the producer.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ErrTransactionalProducerClosed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Kafka transactional producer is closed&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ErrTransactionalProducerFailed requires an explicit Start or a new producer.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ErrTransactionalProducerFailed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Kafka transactional producer must be restarted after failure&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>&lt;a name="NewKafkaConsumer">&lt;/a>&lt;/p></description></item><item><title>NATS</title><link>https://mqutils.dev/docs/nats/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/nats/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-nats/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() types.MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewNatsConsumer">func NewNatsConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewNatsProducer">func NewNatsProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewNatsTransport">func NewNatsTransport() types.Transport&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a name="NewMessageBuilder">&lt;/a>&lt;/p>
&lt;h2 id="func-newmessagebuilder" class="sig">&lt;span class="kind">func&lt;/span>&lt;a href="https://gitlab.com/digitalxero/mqutils/blob/main/nats/message.go#L32">NewMessageBuilder&lt;/a>&lt;a class="anchor" href="#func-newmessagebuilder" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewMessageBuilder&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MessageBuilder&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>NewMessageBuilder creates a new NATS message builder. The builder provides a fluent interface for constructing NATS messages with all supported properties and attributes.&lt;/p></description></item><item><title>AWS SQS</title><link>https://mqutils.dev/docs/aws/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/aws/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-aws/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() mqtypes.MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewSQSConsumer">func NewSQSConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewSQSProducer">func NewSQSProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewSQSTransport">func NewSQSTransport() mqtypes.Transport&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a name="NewMessageBuilder">&lt;/a>&lt;/p>
&lt;h2 id="func-newmessagebuilder" class="sig">&lt;span class="kind">func&lt;/span>&lt;a href="https://gitlab.com/digitalxero/mqutils/blob/main/aws/message.go#L70">NewMessageBuilder&lt;/a>&lt;a class="anchor" href="#func-newmessagebuilder" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewMessageBuilder&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="nx">mqtypes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MessageBuilder&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>NewMessageBuilder creates a new SQS message builder. The builder provides a fluent interface for constructing SQS messages with all supported properties and attributes.&lt;/p></description></item><item><title>GCP Pub/Sub</title><link>https://mqutils.dev/docs/gcp/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/gcp/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-gcp/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() mqtypes.MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewPubSubConsumer">func NewPubSubConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewPubSubProducer">func NewPubSubProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewPubSubTransport">func NewPubSubTransport() mqtypes.Transport&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a name="NewMessageBuilder">&lt;/a>&lt;/p>
&lt;h2 id="func-newmessagebuilder" class="sig">&lt;span class="kind">func&lt;/span>&lt;a href="https://gitlab.com/digitalxero/mqutils/blob/main/gcp/message.go#L73">NewMessageBuilder&lt;/a>&lt;a class="anchor" href="#func-newmessagebuilder" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewMessageBuilder&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="nx">mqtypes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MessageBuilder&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>NewMessageBuilder creates a new Pub/Sub message builder. The builder provides a fluent interface for constructing Pub/Sub messages with all supported properties and attributes.&lt;/p></description></item><item><title>Redis</title><link>https://mqutils.dev/docs/redis/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/redis/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-redis/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="index">Index&lt;a class="anchor" href="#index" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;ul>
&lt;li>&lt;a href="#NewMessageBuilder">func NewMessageBuilder() mqtypes.MessageBuilder&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewRedisConsumer">func NewRedisConsumer(config *viper.Viper) (types.Consumer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewRedisProducer">func NewRedisProducer(config *viper.Viper) (types.Producer, error)&lt;/a>&lt;/li>
&lt;li>&lt;a href="#NewRedisTransport">func NewRedisTransport() mqtypes.Transport&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;a name="NewMessageBuilder">&lt;/a>&lt;/p>
&lt;h2 id="func-newmessagebuilder" class="sig">&lt;span class="kind">func&lt;/span>&lt;a href="https://gitlab.com/digitalxero/mqutils/blob/main/redis/message.go#L66">NewMessageBuilder&lt;/a>&lt;a class="anchor" href="#func-newmessagebuilder" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewMessageBuilder&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="nx">mqtypes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MessageBuilder&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>NewMessageBuilder creates a new Redis message builder. The builder provides a fluent interface for constructing Redis messages with support for both Pub/Sub and Streams modes.&lt;/p></description></item><item><title>RabbitMQ Streams</title><link>https://mqutils.dev/docs/rmqstream/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/rmqstream/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;p>See the &lt;a href="https://mqutils.dev/rabbitmq-streams/">RabbitMQ Streams guide&lt;/a> for configuration, ownership,
filtering, durable outbox/inbox examples, and the current module availability note.
This module requires Go 1.25 or newer. The optional Redis store has its own
&lt;a href="https://mqutils.dev/docs/dedupredis/">API reference&lt;/a>.&lt;/p>
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-rmqstream/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Package rmqstream connects mqutils to RabbitMQ&amp;rsquo;s native Stream protocol.&lt;/p>
&lt;p>Importing the package registers rabbitmq-stream:// and rabbitmq-stream+tls://. Producers await broker confirmations. Consumers checkpoint the settled delivery prefix, so concurrent handlers cannot commit past an earlier unfinished record. Acknowledgment stores progress; it does not delete stream data. Retention can remove data independently of consumption, and crash recovery can replay records.&lt;/p></description></item><item><title>Redis deduplication adapter</title><link>https://mqutils.dev/docs/dedupredis/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/docs/dedupredis/</guid><description>&lt;!-- Code generated by gomarkdoc. DO NOT EDIT. Regenerate with `task docs:gen`; edit the front matter in site/docgen/. -->
&lt;p>This optional module implements the Stream backend&amp;rsquo;s atomic deduplication-store
contract. It does not register a broker URL scheme. See the
&lt;a href="https://mqutils.dev/rabbitmq-streams/#application-message-deduplication">application deduplication guide&lt;/a>
for leases, retention, manual acknowledgment, and transactional inbox boundaries.
Redis must retain active claims and completed entries: the adapter requires
&lt;code>maxmemory-policy=noeviction&lt;/code> and checks it using &lt;code>CONFIG GET&lt;/code>.&lt;/p>
&lt;div class="code">&lt;span class="lang">go&lt;/span>&lt;button class="copy" type="button">copy&lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;go.digitalxero.dev/mq-rmqstream-dedupredis/v2&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Package dedupredis provides a durable Redis claim store for RabbitMQ Stream consumer deduplication. Redis must use maxmemory-policy=noeviction. Restart durability depends on Redis persistence and replication: configure AOF and appropriate fsync/replication guarantees for the required failure model.&lt;/p></description></item><item><title>Examples</title><link>https://mqutils.dev/examples/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/examples/</guid><description>&lt;p>Every example on this page targets the v2 API:&lt;/p>
&lt;ul>
&lt;li>Import paths carry a &lt;code>/v2&lt;/code> suffix: &lt;code>go.digitalxero.dev/mqutils/v2&lt;/code>,
&lt;code>go.digitalxero.dev/mqutils/v2/types&lt;/code>, and one &lt;code>go.digitalxero.dev/mq-&amp;lt;backend&amp;gt;/v2&lt;/code>
module per broker. Blank-import a backend module to register its URL schemes.&lt;/li>
&lt;li>Handlers are &lt;code>func(ctx context.Context, msg types.Message) error&lt;/code>. Return
&lt;code>nil&lt;/code> to acknowledge the message, return an error to reject it. The shared
consumer runtime settles the message for you, so there is no &lt;code>Ack()&lt;/code>/&lt;code>Nack()&lt;/code>
bookkeeping in handlers.&lt;/li>
&lt;li>Consumers are built with &lt;code>mqutils.NewConsumerBuilder()&lt;/code> (typed, no viper) or
&lt;code>mqutils.NewConsumer(ctx, viperConfig)&lt;/code>. Producers use
&lt;code>mqutils.NewProducerBuilder()&lt;/code> or &lt;code>mqutils.NewProducer(ctx, viperConfig)&lt;/code> and
must be started with &lt;code>Start(ctx)&lt;/code> before publishing. The Kafka-specific transactional
builder is an exception: its Build connects immediately.&lt;/li>
&lt;li>The canonical keys &lt;code>destination&lt;/code>, &lt;code>max_retries&lt;/code>, and &lt;code>consumer_group&lt;/code> are
accepted by every backend alongside its native keys. Native keys win when
both are set.&lt;/li>
&lt;/ul>
&lt;p>Working samples also live in the repository&amp;rsquo;s
&lt;a href="https://gitlab.com/digitalxero/mqutils/-/tree/main/_examples">&lt;code>_examples/&lt;/code>&lt;/a>
directory.&lt;/p></description></item><item><title>FAQ</title><link>https://mqutils.dev/faq/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://mqutils.dev/faq/</guid><description>&lt;h2 id="general">General&lt;a class="anchor" href="#general" aria-label="Link to this section">&lt;/a>&lt;/h2>&lt;h3 id="why-use-mqutils-instead-of-the-brokers-sdk">Why use mqutils instead of the broker&amp;rsquo;s SDK?&lt;a class="anchor" href="#why-use-mqutils-instead-of-the-brokers-sdk" aria-label="Link to this section">&lt;/a>&lt;/h3>&lt;p>Because the parts that are the same across brokers (settling a message from a handler&amp;rsquo;s return value, retry budgets, batching, reconnect, graceful shutdown, health checks) are written once in the shared runtime, and the parts that differ live behind a URL scheme. You learn one handler contract and one configuration vocabulary. See &lt;a href="https://mqutils.dev/why-mqutils/">Why mqutils&lt;/a> for what it does and, just as important, what it does not do.&lt;/p></description></item></channel></rss>