MQTT Bridge
This module connects Drupal to an MQTT broker, allowing bidirectional communication with a focus on reliability and preventing message loss. It supports publishing Drupal events to MQTT topics and subscribing to MQTT topics to trigger Drupal actions, all while handling broker outages and ensuring messages are processed even if Drupal is temporarily unavailable.
MQTT Bridge connects Drupal to an MQTT broker in both directions, built for sites where message loss and broker outages are operational realities rather than edge cases.
Publishing works two ways: inject MqttPublisher and publish from your own code, or configure publish rules in the UI and let Drupal events do it. On the inbound side, a CLI subscriber runs outside PHP-FPM and hands every received message to Drupal's Queue API; your code reacts to an event dispatched later by the queue worker.
Three decisions shape the whole module:
Acknowledgements follow durability, not receipt. QoS 1 PUBACK and QoS 2 PUBREC are sent only after the queue write succeeds. If the inbound queue reaches its ceiling, the acknowledgement is withheld so the broker redelivers — backpressure instead of silent loss.
A broker outage never interrupts Drupal. Entity saves and cron only enqueue; the worker connects later. Failed work retries with bounded exponential backoff, then moves to a dead-letter queue for explicit operator replay.
Metadata only, by default. An MQTT broker usually sits outside the site's trust boundary, and brokers and consumers log topics and payloads liberally. Every value that names a person or quotes free text — account names, client IPs, exception
messages, request paths, changed config keys — is a separate opt-in checkbox. Configuration values are never published at all. Credentials are read only from settings.php, never from exported configuration.
Features
Publishing
- MqttPublisher service for publishing from custom code, with topic validation that rejects + and #.
- Exportable publish rules as configuration entities, enabled or disabled independently.
- Five trigger plugins: entity CRUD (type, bundles, operations, selected fields), cron run, user session and login flood, configuration change (filterable by name or prefix), site health and errors (unhandled exceptions above a chosen HTTP status, optional maintenance mode).
- Topic templates with {event}, {operation}, {entity_type}, {entity_id}, {bundle}, {uuid}, {config_name}, {rule_id}. Token values are percent-encoded, so they cannot inject MQTT wildcards or extra topic levels.
- Per-rule QoS, retain flag, payload size cap, and an optional static JSON object nested under data that cannot override event metadata.
- Entity messages carry event metadata plus only the configured field-storage values — never a serialized entity.
Subscribing
- Resilient CLI subscriber with a stable client ID for persistent broker sessions, bounded exponential reconnect backoff with jitter, and graceful SIGINT/SIGTERM shutdown.
- QoS 1 and 2 acknowledgements deferred until durable queue storage; QoS 0 is best-effort by protocol.
- Bounded inbound payload size and queue depth. Oversized messages are acknowledged and discarded rather than becoming poison messages redelivered forever.
- MqttMessageReceivedEvent dispatched from the queue worker, so slow or throwing application code cannot terminate the socket process.
Reliability
- Four queues: mqtt_bridge_inbound, mqtt_bridge_outbound, and a dead-letter queue for each.
- Bounded exponential retry with a configurable attempt ceiling.
- Dead-letter queues have no automatic worker — replay is a deliberate operator action after the cause is fixed.
Operations
- Message lifecycle log at Reports → MQTT Bridge messages, keyed by a stable event_id so one message can be read as a timeline across the subscriber, the queue workers, and the web requests that produced it. Filterable by ID, direction, status, and age over bookmarkable URLs.
- Eight recorded states: received, rejected, queued, delivered, published, retried, dead_lettered, discarded.
- Status report warns while either dead-letter queue holds messages, and errors if php-mqtt/client disappears after installation.
- Six Drush commands: mqtt:test, mqtt:publish, mqtt:subscribe, mqtt:status, mqtt:log, mqtt:migrate-from-drupal-mqtt.
- Four independent permissions, so log access and rule administration can be granted separately from broker configuration.
Post-Installation
1. Put credentials in settings.php, not in configuration. The module never reads these from Drupal config:
$settings['mqtt_bridge.credentials'] = [
'username' => getenv('MQTT_USERNAME') ?: NULL,
'password' => getenv('MQTT_PASSWORD') ?: NULL,
'tls_client_certificate_key_passphrase' => getenv('MQTT_CLIENT_KEY_PASSPHRASE') ?: NULL,
];
2. Configure the broker at /admin/config/services/mqtt-bridge, then verify with drush mqtt:test. Use TLS in production — peer and peer-name verification cannot be disabled while TLS is on, and certificate paths must be absolute and readable by the PHP process.
3. Grant permissions. All four are restricted; none is granted by default:
4. Add publish rules at Configuration → Web services → MQTT Bridge → Publish rules.
5. Run the subscriber as a service, outside PHP-FPM, with a unique stable client ID per concurrent process:
[Service]
ExecStart=drush --root=/var/www/html/web mqtt:subscribe 'devices/#' --qos=1
Restart=always
RestartSec=5
KillSignal=SIGTERMConfiguration is re-read on reconnect; restart the process after changing settings for an immediate rollout.
6. Run the queues. Drupal cron works, but a dedicated worker gives low latency:
drush queue:run mqtt_bridge_inbound
drush queue:run mqtt_bridge_outboundBefore going to production, review three volume characteristics documented in the README: a configuration import publishes one message rather than hundreds; exception reporting defaults to HTTP 500 so ordinary crawler 404s do not flood the broker;
Additional Requirements
Library:
php-mqtt/client ^2.3 — resolved automatically by Composer. Installation is refused with a named error if it is missing.
Recommended but not required:
- Drush 13.7+ for the mqtt:* commands. The module works without Drush, but you lose the practical way to run the subscriber as a service.
- ext-pcntl for graceful SIGINT/SIGTERM shutdown of the subscriber. Without it the subscriber still runs; it just cannot shut down cleanly on signal.
- ext-openssl for TLS connections.
- A process supervisor — systemd, Supervisor, or a dedicated container — for the subscriber.
No contrib module dependencies.
Developer API
Full documentation ships in mqtt_bridge.api.php. MQTT Bridge exposes no alter hooks by design: the broker connection, queues, and retry policy stay inside the module where they can be reasoned about.
Publish from code
$publisher = \Drupal::service(\Drupal\mqtt_bridge\Service\MqttPublisher::class);
$publisher->publish('devices/device-1/commands', json_encode([
'command' => 'restart',
], JSON_THROW_ON_ERROR), qos: 1, retain: FALSE);
Prefer the narrow services — MqttPublisher, MqttSubscriberRunner, MqttConnectionTester, MqttHealthReport. MqttClientManager remains as a thin facade for code written against the previous module, but nothing inside this module uses it.