11
n8n workflow nodes
< 3 s
Slack response time
10
Max labels per request
24/7
Always-on service
In this case study
The problem
Devices in the Joe Marketplace fleet need a printed label with model and serial number before they ship or return to a store. Printing one used to mean finding the one machine with the Brother driver installed, or fighting a shared printer nobody trusted. Non-technical team members should never have to touch a printer driver or a configuration file.
Goal: from a Slack command to a physical label in seconds, from any laptop, with no drivers, no shared printers and no per-machine setup.
How it works
01 · Slack slash command
Team members type /print-label in the #joe-label-device channel. Slack is the tool the team already uses, so there is nothing new to learn.
02 · n8n instant acknowledgement
The webhook fires and a respondToWebhook node answers inside Slack’s three-second window, preventing the “app did not respond” timeout. Processing continues asynchronously.
03 · Label server
A Flask + Pillow + pywin32 server on a headless Windows 11 PC generates a pixel-perfect bitmap and sends it natively to the Brother PT-D610BT. No PowerShell, no corrupt 60-byte jobs.
04 · WireGuard VPN
n8n on the cloud VPS reaches the on-premises PC through an encrypted tunnel on UDP 51820. The PC is invisible to the public internet: no port forwarding, no exposure.
05 · Confirmation and visibility
After printing, a public message posts to the channel with the device model, serial number and quantity, so everyone can see what was printed and when.
The pipeline, step by step
- TriggerSlack
/print-label [model] [serial] [qty] - Acknowledge
respondToWebhook→ “Label request received…” - ValidateCheck the model, parse the serial, clamp the quantity to 1–10
- RouteHTTP POST to the label server on port 5678 over WireGuard
- PrintFlask → Pillow bitmap → pywin32 → Brother PT-D610BT
- DonePost the public confirmation to
#joe-label-device
The tools
n8n · automation backbone
Self-hosted at n8n.richardapplegate.io. Orchestrates the entire flow from Slack webhook to print confirmation in 11 nodes.
Slack · team interface
The “Esper Device Label” app provides the /print-label command. Nobody needs a printer driver.
Flask + Pillow · label server
label_server.py generates label bitmaps with pixel-level control over DPI and dimensions, which fixed the corrupt 60-byte jobs PowerShell produced.
pywin32 · native print API
Direct Windows GDI print calls. Reads LOGPIXELSX from the device context to get the true DPI at runtime, avoiding a 900 px to 682 px clipping bug.
WireGuard · encrypted tunnel
VPS-to-PC tunnel over UDP 51820. The label PC stays off the public internet while remaining reachable from n8n.
NSSM · service manager
Wraps the Flask server as a proper Windows service that survives reboots. The headless PC auto-logs in via Sysinternals Autologon with sleep and lock disabled.
Infrastructure
The system splits cleanly into two environments: a cloud VPS running the full Docker-based self-hosted stack, and an on-premises Windows PC dedicated exclusively to label printing. WireGuard bridges the two.
Team
Slack · /print-label · #joe-label-device
Cloud VPS · Docker
n8n, Grafana, Portainer, RustDesk Pro · WireGuard 10.8.0.1
On-prem · Windows 11
label_server.py · Flask on port 5678 · NSSM service · 10.8.0.16
Hardware
Brother PT-D610BT over USB
Because the automation surface never touches the hardware directly, any future printer, Brother, Zebra or otherwise, can be added at the Windows layer without changing n8n or Slack. RustDesk Pro provides headless remote access for maintenance, so no monitor or keyboard is ever plugged into the label PC once it is running.
Lessons learned
n8n IF node bug
Any non-empty string is true. In n8n v2.2, even "false" or "no" evaluates as boolean true, so IF nodes are unreliable for string branching. Code nodes that return [] halt execution on the unwanted path instead.
Slack’s three-second window
Acknowledge first, work second. Slack drops slash-command connections after three seconds. The acknowledgement node must fire before any HTTP calls or validation; the real work continues in parallel afterwards.
pywin32 over PowerShell
Native GDI printing fixed corrupt output. PowerShell print jobs produced 60-byte garbage. Reading GetDeviceCaps(8) after creating the device context returns the real runtime DPI and prevents bitmap clipping.
Webhook auth side effects
Header auth leaks into triggers. Setting httpHeaderAuth on HTTP Request nodes silently made the Webhook trigger require auth too, which blocked Grafana. Every Webhook trigger now sets authentication: 'none' explicitly.
Richard Applegate