← All posts
·16 min read

Claude Code with MQTT: IoT Pub/Sub That Stays Up at Scale

Claude CodeMQTTIoTMessaging
Claude Code with MQTT: IoT pub/sub that stays up at scale

Why MQTT without CLAUDE.md ships brokers that drown in retained messages

MQTT is the messaging protocol for IoT and pub/sub workloads where the client count is high, the message rate is bursty, and the network is unreliable. Brokers like Mosquitto, EMQX, and HiveMQ implement the spec and add management consoles, clustering, and bridge connectors. The pitch is that any device can publish or subscribe to a topic with one line of code and the broker handles the fanout. The reality is that the same simplicity makes the failure modes hard to spot. The broker will accept a topic like sensors/+/+/+/+/temperature/value/raw/2026-06-02 from a misconfigured device and route it to every subscriber. It will accept a retained message on a topic with no retention strategy and keep that message forever. It will accept a QoS 2 publish from a device that disconnects mid-handshake and hold the in-flight message in the persistent session forever. It will let any client subscribe to # (every topic) with no ACL check and fan every message in the system to a single curious subscriber.

A Claude Code session that drafts MQTT producer or consumer code without project rules generates code that works on a development broker with three devices and falls apart with three thousand. The publisher uses QoS 1 for every message because the docs example shows it that way, which doubles the broker write rate compared to QoS 0 for telemetry that nobody cares about losing. The subscriber subscribes to devices/# because that is the easy pattern, which means the broker has to fan every message in the system to that subscriber. The persistent session is enabled with clean_start=False, but the client ID is randomised on every restart, which orphans a session per restart and the broker's session storage grows without bound. The ACL is configured as topic readwrite # for the default user because that user is for testing, and nobody removes it.

This guide covers the CLAUDE.md configuration that locks Claude Code into MQTT patterns that survive production: a topic taxonomy where the broker enforces the hierarchy, the right QoS per message class, retained-message hygiene with explicit cleanup, persistent sessions that match the client's restart semantics, ACLs that scope each device to its own topic prefix, and permission hooks that gate the destructive broker operations. For the alternative messaging patterns where MQTT's pub/sub fit is wrong, Claude Code with Kafka covers the log-based streaming patterns. For the simpler in-process job queue that handles the workloads MQTT often gets pressed into, Claude Code with BullMQ covers the patterns where pub/sub is overkill.

The MQTT CLAUDE.md template

The CLAUDE.md at your project root is read at the start of every Claude Code session. For an MQTT integration it needs to declare: the broker (Mosquitto, EMQX, HiveMQ), the protocol version (MQTT 5 preferred over 3.1.1), the topic taxonomy, the QoS policy per message class, the retained-message rules, the session strategy, the ACL pattern, and the hard rules that block the misconfigurations Claude makes most often.

# MQTT rules

## Stack
- Broker: EMQX 5.x (or Mosquitto 2.x for small deployments)
- Protocol: MQTT 5 (NOT 3.1.1 unless explicitly required by legacy devices)
- Client library: paho-mqtt for Python, mqtt for Node.js, mqtt-rs for Rust
- TLS-only on port 8883, plaintext disabled at the listener level
- Authentication: client certs preferred, JWT for browser/mobile clients

## Project structure
- mqtt/topic-taxonomy.md           , single source of truth for topic naming
- mqtt/acl/                        , one ACL file per device class or service
- mqtt/clients/                    , service code that publishes or subscribes
- mqtt/bridges/                    , bridge configs to other brokers or cloud IoT
- mqtt/auth/                       , authentication backend configs (JWT keys, cert CA)
- ops/mqtt/                        , broker config, monitoring, terraform

## Topic taxonomy (MANDATORY)
- Format: <tenant>/<device-class>/<device-id>/<channel>/<event>
- Wildcards in subscription, NEVER in publish
- # subscription only allowed for broker-internal services with explicit ACL
- Topic depth: 3 to 6 segments, never deeper without documentation
- Lowercase, hyphen-separated, no slashes inside segments

## QoS policy (MANDATORY per message class)
- Telemetry (high volume, idempotent): QoS 0
- Commands (must arrive): QoS 1 with idempotency key in payload
- State sync (must arrive exactly once): QoS 2, used sparingly
- NEVER QoS 1 or 2 for telemetry above 100 msg/sec per device

## Retained messages (MANDATORY)
- Retained messages ONLY on state topics (e.g. <tenant>/<device>/state)
- NEVER retain on command or telemetry topics
- Retained messages cleared with an empty payload + retain=true on shutdown
- Retained payload size capped at 4 KB by broker config

## Persistent sessions (MANDATORY)
- clean_start=False ONLY when the client has a stable, persistent client ID
- Client IDs derived from device serial or UUID, never random per connection
- Session expiry interval (MQTT 5) explicit per client class
- Will message (LWT) configured for every device

## ACLs (MANDATORY)
- One ACL rule per device class or service
- Devices: publish to <tenant>/<class>/<own-id>/#, subscribe to <tenant>/<class>/<own-id>/cmd/#
- Services: publish/subscribe scoped to their service prefix
- NEVER an ACL with topic readwrite #
- Default-deny, explicit allows for every topic-action pair

## Listeners and TLS
- Listener on 8883 with TLS only
- Listener on 8884 for WebSocket over TLS (browser clients)
- Plaintext 1883 disabled in production (or bound to loopback for management only)
- Cipher suites restricted to TLS 1.2 and TLS 1.3 modern profile

## Hard rules
- NEVER publish with a topic containing a wildcard
- NEVER subscribe to # without an explicit ACL grant and a documented reason
- NEVER QoS 2 for high-volume topics
- NEVER set retain=true on a command topic
- NEVER use a randomly-generated client ID with clean_start=False
- NEVER ship a broker with an empty or anonymous ACL allow rule
- ALWAYS handle on_disconnect with backoff reconnect
- ALWAYS log the client ID and topic on every publish/subscribe event

Six rules here prevent the majority of incidents Claude generates without them.

The topic taxonomy rule is the foundation. MQTT topics are just strings, and the broker does no schema validation. Without a documented hierarchy, every team invents its own pattern and the broker becomes a mess of sensors/value, device/123/data, iot/things/temp/raw. With the taxonomy <tenant>/<device-class>/<device-id>/<channel>/<event>, every topic has a predictable shape, ACLs can match on prefix, and a subscriber that wants "every temperature reading from tenant A" can express it as acme/+/+/temperature/+.

The QoS-per-class rule prevents the most common throughput collapse. QoS 0 is fire-and-forget: no broker storage, no acknowledgement, fastest. QoS 1 is at-least-once: broker stores the message until the publisher acknowledges, doubles the broker write rate and adds latency. QoS 2 is exactly-once: a four-step handshake, quadruples the broker write rate, and stores in-flight state until the handshake completes. Claude's instinct is "QoS 1 is the safe default" because the docs say so. For a temperature sensor publishing every second, QoS 1 means the broker writes 8x the messages it would at QoS 0 (publisher write, PUBACK, replication, persistence). For the rare command that must arrive, QoS 1 with an idempotency key is the right shape.

The retained-message rule prevents the ghost-message problem. A retained message persists on the broker for that topic until it is explicitly cleared. Without the rule, devices retain their telemetry, so a subscriber that joins after the device disconnects sees a temperature reading from three weeks ago. With the rule, only state topics get retained payloads (current device state), and devices clear them on shutdown.

The persistent session rule prevents session storage explosion. With clean_start=False, the broker keeps the session (subscriptions and queued QoS-1 messages) until the client reconnects with the same ID. If the client ID is randomised, the broker holds a session per restart forever. With stable client IDs and explicit session expiry, the broker can clean up sessions when devices are decommissioned.

Broker configuration: Mosquitto example

Mosquitto is the canonical open-source MQTT broker. The default config has none of the production hardening the CLAUDE.md requires. Replace it with a config that pins the right defaults.

# /etc/mosquitto/mosquitto.conf

persistence true
persistence_location /var/lib/mosquitto/

log_dest stdout
log_type error
log_type warning
log_type notice
log_type information

# Disable plaintext listener
# listener 1883

# TLS listener on 8883
listener 8883 0.0.0.0
protocol mqtt

cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

require_certificate true
use_identity_as_username true

tls_version tlsv1.3

# WebSocket over TLS for browser clients
listener 8884 0.0.0.0
protocol websockets

cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

# Authentication and ACLs
allow_anonymous false
acl_file /etc/mosquitto/acl.conf
auth_plugin /usr/lib/mosquitto/auth-plugin.so

# Limits
max_inflight_messages 20
max_queued_messages 1000
message_size_limit 8192
max_keepalive 300
persistent_client_expiration 30d

# Retain limits
retain_available true

The plaintext listener is commented out: production brokers never accept unencrypted traffic. The TLS listener requires client certificates, and the identity from the cert subject becomes the username for ACL lookups. The WebSocket listener gives browser clients a way in over the same TLS profile. The ACL file is loaded at startup and on SIGHUP, with no default-allow fallback. The retain configuration includes a per-payload limit so misbehaving clients cannot retain megabytes of data on a single topic.

The persistent_client_expiration 30d cleans up sessions for clients that have not reconnected in 30 days. Without this, sessions accumulate indefinitely. The expiry is one of the rules from the CLAUDE.md made concrete in broker config.

ACL file structure

The ACL file enforces the topic taxonomy at the broker level. Every device class gets a rule that scopes it to its own slice of the topic tree.

# /etc/mosquitto/acl.conf

# Services that consume telemetry can read the whole telemetry tree
user telemetry-ingest
topic read acme/+/+/telemetry/+

# Services that issue commands can publish into the command channel
user command-dispatcher
topic write acme/+/+/cmd/+

# Per-device pattern: a device with username acme-sensor-12345 can publish its own telemetry
# and subscribe to its own command channel
pattern read acme/sensor/%u/cmd/#
pattern write acme/sensor/%u/telemetry/#
pattern readwrite acme/sensor/%u/state

# Browser clients use JWT, scoped per tenant
user browser-acme
topic read acme/+/+/state

The pattern keyword substitutes %u (username) into the topic path, which gives every device a per-device ACL without writing one rule per device. A device with the username acme-sensor-12345 (derived from its client certificate) can publish only to its own telemetry topic and subscribe only to its own command channel.

The user keyword scopes rules to specific service identities. The telemetry-ingest service has read on every device's telemetry, the command-dispatcher has write on every command channel. Each service runs with a different cert, and the ACL enforces the boundary.

For brokers like EMQX with database-backed ACLs, the same pattern translates to SQL or HTTP API rules. The structure is the same: per-device patterns with username substitution, per-service rules with explicit topic globs.

A Python publisher with the right QoS

A device that publishes telemetry uses QoS 0 for high-volume readings and QoS 1 for state changes. The publisher code wraps the paho client with the right defaults.

# mqtt/clients/sensor_publisher.py
import json
import logging
import os
import time
from paho.mqtt import client as mqtt

DEVICE_ID = os.environ["DEVICE_ID"]
BROKER_HOST = os.environ["MQTT_BROKER_HOST"]
TENANT = os.environ["TENANT"]
DEVICE_CLASS = "sensor"

TOPIC_TELEMETRY = f"{TENANT}/{DEVICE_CLASS}/{DEVICE_ID}/telemetry/temperature"
TOPIC_STATE = f"{TENANT}/{DEVICE_CLASS}/{DEVICE_ID}/state"
TOPIC_LWT = f"{TENANT}/{DEVICE_CLASS}/{DEVICE_ID}/lwt"

log = logging.getLogger(__name__)


def make_client() -> mqtt.Client:
    client = mqtt.Client(
        client_id=f"{DEVICE_CLASS}-{DEVICE_ID}",
        clean_session=False,
        protocol=mqtt.MQTTv5,
    )
    client.tls_set(
        ca_certs="/etc/ssl/mqtt/ca.crt",
        certfile=f"/etc/ssl/mqtt/devices/{DEVICE_ID}.crt",
        keyfile=f"/etc/ssl/mqtt/devices/{DEVICE_ID}.key",
    )
    client.will_set(
        TOPIC_LWT,
        payload=json.dumps({"event": "disconnect", "ts": time.time()}),
        qos=1,
        retain=True,
    )

    client.on_connect = on_connect
    client.on_disconnect = on_disconnect

    return client


def on_connect(client, userdata, flags, rc, props):
    log.info("mqtt_connected", extra={"rc": rc, "session_present": flags.session_present})
    client.publish(
        TOPIC_STATE,
        json.dumps({"state": "online", "ts": time.time()}),
        qos=1,
        retain=True,
    )


def on_disconnect(client, userdata, rc, props=None):
    log.warning("mqtt_disconnected", extra={"rc": rc})


def publish_telemetry(client: mqtt.Client, temperature: float):
    payload = json.dumps({"value": temperature, "ts": time.time()})
    client.publish(TOPIC_TELEMETRY, payload, qos=0, retain=False)


def main():
    client = make_client()
    client.connect(BROKER_HOST, port=8883, keepalive=60)
    client.loop_start()
    try:
        while True:
            publish_telemetry(client, read_sensor())
            time.sleep(1)
    finally:
        client.publish(TOPIC_STATE, json.dumps({"state": "offline"}), qos=1, retain=True)
        client.loop_stop()
        client.disconnect()

The client ID is stable: sensor-12345. The session is persistent: clean_session=False. The Last Will and Testament is configured at connect time, so if the device dies without sending a clean disconnect, the broker publishes the LWT to the LWT topic for downstream consumers. The state topic uses QoS 1 with retain=true, so a subscriber joining after the device has connected sees the current state. The telemetry topic uses QoS 0 with retain=false, so the broker does not write to disk for every reading.

The graceful shutdown publishes an explicit offline state and disconnects. Without this, the LWT fires only on ungraceful disconnects, which means a device restart for a clean reason still emits the "device died" event to downstream services.

A Node.js subscriber with topic scoping

A service that consumes telemetry subscribes to the parent topic, not the wildcard. The subscriber code wraps the mqtt client with the right defaults.

// mqtt/clients/telemetry-ingest.ts
import mqtt from 'mqtt';

const BROKER_URL = process.env.MQTT_BROKER_URL!;
const TENANT = process.env.TENANT!;
const SERVICE_ID = 'telemetry-ingest';

const client = mqtt.connect(BROKER_URL, {
  clientId: `${SERVICE_ID}-${process.env.HOSTNAME}`,
  clean: false,
  protocolVersion: 5,
  properties: {
    sessionExpiryInterval: 7 * 24 * 60 * 60,
  },
  ca: [require('fs').readFileSync('/etc/ssl/mqtt/ca.crt')],
  cert: require('fs').readFileSync(`/etc/ssl/mqtt/services/${SERVICE_ID}.crt`),
  key: require('fs').readFileSync(`/etc/ssl/mqtt/services/${SERVICE_ID}.key`),
});

const TOPIC_PATTERN = `${TENANT}/+/+/telemetry/+`;

client.on('connect', (packet) => {
  console.log({ event: 'mqtt_connected', sessionPresent: packet.sessionPresent });
  client.subscribe(TOPIC_PATTERN, { qos: 0 }, (err, granted) => {
    if (err) {
      console.error({ event: 'mqtt_subscribe_failed', error: err.message });
      return;
    }
    console.log({ event: 'mqtt_subscribed', granted });
  });
});

client.on('message', async (topic, payload) => {
  const segments = topic.split('/');
  if (segments.length !== 5) {
    console.warn({ event: 'mqtt_malformed_topic', topic });
    return;
  }
  const [tenant, deviceClass, deviceId, channel, metric] = segments;
  const data = JSON.parse(payload.toString());

  await ingestReading({
    tenant,
    deviceClass,
    deviceId,
    metric,
    value: data.value,
    ts: data.ts,
  });
});

client.on('error', (err) => {
  console.error({ event: 'mqtt_error', error: err.message });
});

client.on('disconnect', () => {
  console.log({ event: 'mqtt_disconnected' });
});

process.on('SIGTERM', () => {
  client.end(false, () => process.exit(0));
});

The client ID is telemetry-ingest-<hostname>, which is stable across restarts of the same instance and unique across instances. The session expiry interval (sessionExpiryInterval: 7 * 24 * 60 * 60) tells the broker to keep the session for seven days if the client disconnects, which covers normal deploys and weekend outages. The subscription pattern is the explicit <tenant>/+/+/telemetry/+, not the lazy #. The single-level wildcards mean the subscriber only sees telemetry topics with the right depth.

The message handler validates the topic shape before processing, which catches misconfigured devices that publish to topics outside the taxonomy. The ingest call is async and handles its own backpressure. For high-volume topics, add a batching layer that flushes every 100 messages or 500 ms, whichever comes first.

Bridge to cloud IoT or another broker

For deployments that combine an on-premise broker with AWS IoT Core, Azure IoT Hub, or another MQTT cluster, configure a bridge that mirrors topics across the connection. The bridge runs as a broker-to-broker MQTT connection with its own ACLs and topic mapping.

# Mosquitto bridge configuration

connection aws-iot-bridge
address iot.eu-west-2.amazonaws.com:8883

bridge_cafile /etc/mosquitto/certs/aws-iot-ca.pem
bridge_certfile /etc/mosquitto/certs/aws-iot-cert.pem
bridge_keyfile /etc/mosquitto/certs/aws-iot-key.pem

bridge_protocol_version mqttv50
clientid mosquitto-bridge-prod

# Outbound: publish device states to AWS IoT shadow topics
topic state out 1 acme/sensor/+/state $aws/things/+/shadow/update

# Inbound: subscribe to cloud-issued commands
topic cmd in 1 $aws/things/+/shadow/get/accepted acme/sensor/+/cmd

start_type automatic
restart_timeout 10
cleansession false
notifications false

The bridge config defines an outbound topic mapping (device state goes to AWS IoT Shadow topics) and an inbound one (AWS IoT commands come back as device command topics). The bridge_protocol_version mqttv50 matches the MQTT 5 setting. The cleansession false keeps the bridge's session persistent across restarts. The notifications false suppresses the bridge from publishing its own connect/disconnect events into the broker's topic tree.

For Claude Code with AWS integrations where the bridge endpoint is AWS IoT Core, the cert files come from AWS IoT's device certificate flow. For Azure, the auth pattern is SAS tokens with hourly rotation, which means the bridge needs a sidecar that refreshes the token before expiry.

Permission hooks for broker operations

Brokers have CLI commands and HTTP endpoints that can drop persistent sessions, wipe retained messages, or restart the broker. Permission hooks in .claude/settings.local.json gate the destructive ones.

{
  "permissions": {
    "allow": [
      "Bash(mosquitto_sub*)",
      "Bash(mosquitto_pub -t */state -m*)",
      "Bash(mosquitto_rr*)",
      "Bash(curl http://emqx:18083/api/v5/monitor*)",
      "Bash(curl http://emqx:18083/api/v5/clients*)"
    ],
    "deny": [
      "Bash(systemctl restart mosquitto*)",
      "Bash(systemctl stop mosquitto*)",
      "Bash(rm -rf /var/lib/mosquitto*)",
      "Bash(curl*DELETE*api/v5/clients*)",
      "Bash(emqx ctl retainer clean*)",
      "Bash(mosquitto_pub*-r*-n*)"
    ]
  }
}

The allow list permits read-only subscribe and topic inspection. The deny list catches the operations that restart the broker, wipe state, force-disconnect clients, or clear retained messages. The last pattern mosquitto_pub*-r*-n* blocks the "publish empty retained" command, which is how operators (and accidents) clear retained messages on production topics.

Combined with the Claude Code hooks system for project-wide policy, the result is a setup where Claude can investigate the broker without being able to break it. For broker access control inside the codebase, Claude Code permissions covers the broader pattern.

Common Claude Code mistakes with MQTT

Six patterns Claude generates incorrectly without CLAUDE.md constraints.

1. QoS 1 for telemetry

Claude generates: client.publish(topic, payload, qos=1) for sensor readings.

Correct pattern: client.publish(topic, payload, qos=0) for telemetry, QoS 1 reserved for commands and state changes.

2. Random client ID with persistent session

Claude generates: client_id=str(uuid.uuid4()), clean_session=False.

Correct pattern: client ID derived from device serial or service hostname, persistent across restarts.

3. Subscribe to # without ACL grant

Claude generates: client.subscribe("#") for a debugging service that stayed in production.

Correct pattern: explicit topic patterns scoped to what the service actually needs, ACL enforced at broker level.

4. Retain on telemetry topic

Claude generates: client.publish(topic, payload, qos=1, retain=True) for sensor readings.

Correct pattern: retain only on state topics, never on telemetry or commands.

5. Missing LWT

Claude generates: a device client with no will_set() call.

Correct pattern: every device sets a Last Will payload on connect, broker publishes it on ungraceful disconnect.

6. Anonymous broker

Claude generates: a broker config with allow_anonymous true "for testing."

Correct pattern: allow_anonymous false, client cert auth, ACL file with default-deny.

Add each pattern to CLAUDE.md with the corrected form. Combined with CLAUDE.md examples for the general structure, the result is MQTT code Claude generates correctly the first time.

Observability and metrics

Every MQTT broker emits internal metrics. Mosquitto's $SYS topic tree publishes client counts, message rates, and retained-message totals. EMQX has a Prometheus endpoint with richer per-topic metrics.

For Mosquitto, subscribe to $SYS/# from a metrics relay service that converts the messages into Prometheus format. The metrics that matter: $SYS/broker/clients/connected (active clients), $SYS/broker/messages/received (publish rate), $SYS/broker/messages/inflight (in-flight QoS 1/2 messages, should be near zero), $SYS/broker/retained messages/count (retained message total).

For EMQX, scrape /api/v5/prometheus/stats directly:

- job_name: emqx
  static_configs:
    - targets: [emqx-1:18083, emqx-2:18083, emqx-3:18083]
  metrics_path: /api/v5/prometheus/stats

The alerts that pay back the setup: in-flight QoS 1/2 above 1000 sustained (a subscriber is too slow or disconnected), retained-message count growing without bound (devices retaining on the wrong topics), client connect/disconnect rate above 100 per minute (a fleet of devices is in a reconnect storm), per-topic message rate above 10k/sec (a runaway device or a misconfigured publisher).

Pair with Claude Code with Grafana for dashboards. The relevant per-tenant view: messages per second by tenant, retained messages by tenant, top topics by message rate, top clients by message rate. Outliers on any of these usually indicate a misconfigured device or a missing ACL rule.

Testing MQTT integrations

The hard part of MQTT testing is the QoS handling. Unit-test the message handler as a pure function with deterministic inputs. For integration tests, run a real broker (Mosquitto in a container) and exercise the full publish-subscribe roundtrip.

// tests/integration/telemetry-ingest.test.ts
import mqtt from 'mqtt';
import { GenericContainer } from 'testcontainers';

let broker: any;
let brokerUrl: string;

beforeAll(async () => {
  broker = await new GenericContainer('eclipse-mosquitto:2')
    .withExposedPorts(1883)
    .withCopyContentToContainer([
      {
        content: 'listener 1883\nallow_anonymous true\npersistence false\n',
        target: '/mosquitto/config/mosquitto.conf',
      },
    ])
    .start();
  brokerUrl = `mqtt://${broker.getHost()}:${broker.getMappedPort(1883)}`;
});

afterAll(async () => {
  await broker.stop();
});

test('publishes a telemetry topic and receives it on the right pattern', async () => {
  const subscriber = mqtt.connect(brokerUrl);
  const received: any[] = [];

  await new Promise<void>((resolve) => subscriber.on('connect', () => resolve()));

  subscriber.subscribe('acme/+/+/telemetry/+');
  subscriber.on('message', (topic, payload) => {
    received.push({ topic, payload: payload.toString() });
  });

  const publisher = mqtt.connect(brokerUrl);
  await new Promise<void>((resolve) => publisher.on('connect', () => resolve()));

  publisher.publish('acme/sensor/dev-1/telemetry/temperature', '{"value":22.5}', { qos: 0 });

  await new Promise((resolve) => setTimeout(resolve, 100));

  expect(received).toHaveLength(1);
  expect(received[0].topic).toBe('acme/sensor/dev-1/telemetry/temperature');

  subscriber.end();
  publisher.end();
});

The test spins up a Mosquitto container with permissive auth, subscribes to the telemetry pattern, publishes a matching message, and asserts on the received payload. This catches the entire class of "the topic pattern does not actually match" bugs that pure-function unit tests miss.

For QoS 1 and QoS 2 tests, assert on the broker-side acknowledgement (packet.cmd === 'puback') and verify retransmission behaviour by killing the subscriber mid-handshake.

Building MQTT that survives the second device

The MQTT CLAUDE.md in this guide produces a deployment where every topic follows the taxonomy, every publish uses the right QoS for its class, retained messages only land on state topics, persistent sessions match stable client IDs, ACLs default to deny, and the broker config rejects plaintext traffic. The result is an MQTT pipeline that scales from the first device to the ten-thousandth without the broker becoming a graveyard of ghosts.

The underlying principle is the same as any messaging infrastructure with Claude Code. MQTT without a CLAUDE.md produces a working setup on a development broker that collapses under any real workload. The CLAUDE.md is what makes the production-safe defaults the default for Claude.

For the log-based streaming patterns where MQTT is the wrong tool, Claude Code with Kafka covers the alternative. For the in-process job queue that handles the workloads pub/sub often gets pressed into, Claude Code with BullMQ covers the simpler pattern. For the cloud IoT integration that bridges on-premise brokers to AWS or Azure, Claude Code with AWS covers the IoT Core flow.

Get Claudify. Ship an MQTT broker that handles the second hundred devices the same as the first.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir Claudify - Featured on Startup Fame