MQTT to Node-RED Integration
Node-RED is a browser-based visual programming tool built on Node.js, ideal for wiring together MQTT data streams from Meshtastic with downstream services such as dashboards, databases, and messaging platforms. This page covers installing Node-RED, subscribing to Meshtastic MQTT topics, parsing protobuf and JSON payloads, building a simple monitoring dashboard, and forwarding mesh alerts to Telegram and Discord.
Installing Node-RED
On Raspberry Pi / Debian / Ubuntu
Use the official Node-RED install script for Debian/Ubuntu (it provisions a supported Node.js version). Review the script before running it, per nodered.org:
bash <(curl -sL https://github.com/node-red/linux-installers/releases/latest/download/update-nodejs-and-nodered-deb)
# Enable and start Node-RED as a service
sudo systemctl enable nodered.service
sudo systemctl start nodered.service
# Node-RED web UI is available at http://<IP>:1880
On x86 Linux via npm (Node.js v20+ already installed)
The bare npm method is only for environments where a supported Node.js (v20+) is already present. Install the build prerequisites first:
sudo apt install build-essential git curl
sudo npm install -g --unsafe-perm node-red
# Start manually:
node-red
# Or install as systemd service manually
Installing Required Palettes
Open the Node-RED web UI at http://<IP>:1880, then click the hamburger
menu → Manage Palette → Install. Search for and install:
node-red-dashboard- UI widgets (gauges, charts, text displays).node-red-contrib-protobuf- decode Meshtastic protobuf payloads.node-red-contrib-telegrambot- send messages to Telegram.
The Meshtastic project also publishes an official decode node, @meshtastic/node-red-contrib-meshtastic (linked from meshtastic.org), which you can install the same way.
Alternatively via CLI
cd ~/.node-red
npm install node-red-dashboard node-red-contrib-protobuf node-red-contrib-telegrambot
sudo systemctl restart nodered.service
Subscribing to Meshtastic MQTT Topics
Add an mqtt in node to your flow:
- Server: your broker address and port. (For the Meshtastic public broker
mqtt.meshtastic.org, supply usernamemeshdev/ passwordlarge4cats- it is not anonymous.) - Topic:
msh/#to receive all Meshtastic traffic, ormsh/US/2/json/LongFast/#for JSON output from the LongFast channel. JSON is only published when JSON mode is enabled on the gateway (mqtt.json_enabled true); note thatmsh/#also yields protobuf-encoded/e/topics that the json node cannot parse. - QoS: 0 (at most once) is sufficient for mesh monitoring.
- Output: for protobuf
/e/topics set Output to a buffer; for JSON topics a parsed JSON object / string is fine.
Parsing Protobuf Payloads
The protobuf format requires .proto schema files. The MQTT envelope type ServiceEnvelope is defined in mqtt.proto, so download that plus its imports (mesh.proto, config.proto, portnums.proto, etc.) into the same directory:
mkdir -p ~/.node-red/proto
cd ~/.node-red/proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mqtt.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/mesh.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/config.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/portnums.proto
wget https://raw.githubusercontent.com/meshtastic/protobufs/master/meshtastic/telemetry.proto
Flow wiring for protobuf decode. The encrypted /e/ MQTT payload is raw binary protobuf (not base64), so feed the buffer straight into the decode node:
[mqtt in (Output: a buffer)] → [protobuf decode (ServiceEnvelope)] → [debug / further processing]
Set the mqtt in node's Output to a buffer and add a function node before the protobuf decode that points it at the right schema and type (do not base64-decode the payload first):
// The /e/ payload is already a raw binary protobuf buffer - pass it straight through.
msg.protofile = '/home/pi/.node-red/proto/mqtt.proto';
msg.protobufType = 'meshtastic.ServiceEnvelope';
return msg;
Base64 only applies to the nested decoded.payload bytes (for example when extracting TEXT_MESSAGE_APP text after the envelope is decoded) - not to the MQTT payload itself.
The protobuf decode node only deserializes the bytes into a JavaScript object representing the
ServiceEnvelope. On encrypted channels the inner packet.decoded field is absent - the payload is still AES-encrypted with the channel PSK, which must be decrypted separately (or enable JSON output on the gateway, which decrypts before publishing).
Note on channel decryption: The official@meshtastic/node-red-contrib-meshtasticpackage provides a Meshtastic protobuf decode node. Theprotobuf decodenode by itself only deserializes the envelope; for encrypted channels you must AES-decrypt with the channel PSK, or enable JSON output mode on the gateway (which the gateway decrypts before publishing) for full access to message text and telemetry.
Parsing JSON Payloads (Recommended for Simplicity)
If JSON output is enabled on your gateway node (mqtt.json_enabled true, ESP32 only - nRF52 gateways do not emit JSON), subscribe to the JSON topic and use a
json node to parse directly - no protobuf library needed:
[mqtt in (topic: msh/US/2/json/LongFast/#)] → [json] → [switch] → [handlers]
A decoded JSON message is a flat envelope and looks like:
{
"id": 3456789012,
"channel": 0,
"from": 2887456789,
"to": 4294967295,
"sender": "!abcd1234",
"type": "text",
"payload": {
"text": "Hello from City A!"
},
"timestamp": 1714953600
}
Note: the JSON envelope does not include RF metadata - there is no top-level rssi, snr, or hops_away field. Those values are not available via gateway JSON output; if you need them, obtain them from a local serial/BLE API or from the protobuf MeshPacket (rx_rssi/rx_snr) instead.
Building a Simple Dashboard
With node-red-dashboard installed, add a UI group for "Mesh Monitor":
- Add a ui_text node: label "Last Message", wired from the JSON parse output
using a function to extract
msg.payload.payload.text. - Add a ui_chart node: "Message Rate / min", use an rbe (report-by-exception) node plus a counter function to track message frequency.
- Add a ui_table node: "Recent Messages" - build a rolling array of the last 20 messages using a context store.
RF signal gauges (RSSI/SNR) cannot be driven from the JSON MQTT feed because those fields are not present in the JSON envelope. If you want an RSSI display, source the value from a non-MQTT-JSON path (local serial/BLE API, or the protobuf MeshPacket.rx_rssi on the /e/ decode path).
Access the dashboard at http://<IP>:1880/ui.
Forwarding Alerts to Telegram
Pre-requisite: create a Telegram Bot via @BotFather and note the bot token and your chat ID.
Wire a flow like this:
[mqtt in] → [json] → [switch: msg.payload.type == "text"] → [function: format alert] → [telegram sender]
Function node code to format the Telegram message:
const p = msg.payload;
if (!p.payload || !p.payload.text) return null; // skip non-text packets
msg.payload = {
chatId: "YOUR_CHAT_ID_HERE",
type: "message",
content: `📡 Mesh Message
` +
`From: ${p.sender}
` +
`Text: ${p.payload.text}`
};
return msg;
Configure the Telegram Sender node with your bot token. (RSSI/SNR/hops are intentionally omitted - they are not present in the JSON envelope.)
Forwarding Alerts to Discord
Use a http request node pointed at your Discord webhook URL:
[mqtt in] → [json] → [switch: msg.payload.type == "text"] → [function: build webhook body] → [http request]
Function node code:
const p = msg.payload;
if (!p.payload || !p.payload.text) return null;
msg.method = "POST";
msg.url = "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN";
msg.headers = { "Content-Type": "application/json" };
msg.payload = JSON.stringify({
username: "MeshAmerica Bot",
content: `**Mesh Message** from \`${p.sender}\`
` +
`> ${p.payload.text}`
});
return msg;
Example: Full Flow JSON Export
Below is a minimal Node-RED flow (importable via Import → Clipboard) that subscribes to the JSON MQTT topic, parses messages, displays them in a dashboard, and forwards them to a Discord webhook:
[
{
"id": "mqtt_in_1",
"type": "mqtt in",
"name": "Meshtastic JSON",
"topic": "msh/US/2/json/LongFast/#",
"qos": "0",
"broker": "local_broker",
"wires": [["json_parse_1"]]
},
{
"id": "json_parse_1",
"type": "json",
"name": "Parse JSON",
"wires": [["switch_1", "dashboard_table_1"]]
},
{
"id": "switch_1",
"type": "switch",
"name": "Text only",
"property": "payload.type",
"rules": [{"t": "eq", "v": "text"}],
"wires": [["format_discord_1"]]
},
{
"id": "format_discord_1",
"type": "function",
"name": "Format Discord",
"func": "const p = msg.payload;
if (!p.payload||!p.payload.text) return null;
msg.method='POST';
msg.url='https://discord.com/api/webhooks/XXX/YYY';
msg.headers={'Content-Type':'application/json'};
msg.payload=JSON.stringify({username:'MeshBot',content:`**${p.sender}**: ${p.payload.text}`});
return msg;",
"wires": [["http_discord_1"]]
},
{
"id": "http_discord_1",
"type": "http request",
"name": "Discord Webhook",
"method": "POST",
"wires": [[]]
}
]
Replace the Discord webhook URL placeholder with your real webhook before importing.
No comments to display
No comments to display