Industrial data pipelines
in one YAML file.

I/O Smith connects MQTT, Kafka, HTTP, and databases with declarative flows — JSON transforms, retries, dead-lettering, and full observability built in. No glue code. No boilerplate. Just configuration.

Full engine, every connector, no credit card.
Runs 6 hours a day, free forever.

samples/mqtt-to-litedb/flow.yaml
version: "1.0"

connectors:
  mqtt_broker:
    type: mqtt
    settings:
      host: ${MQTT_HOST}
      port: 1883
  sensor_db:
    type: litedb
    settings:
      database: ${LITEDB_PATH}

flows:
  - name: sensor-to-database
    trigger:
      connector: mqtt_broker
      operation: subscribe
      params:
        topic: sensors/+/data
    steps:
      - name: filter-valid
        type: transform
        transform: filter
      - name: persist
        type: connector
        connector: sensor_db
        operation: insert
    on_error:
      strategy: retry
      max_retries: 3
samples/kafka-to-webhook/flow.yaml
version: "1.0"

connectors:
  kafka_alerts:
    type: kafka
    settings:
      bootstrap_servers: ${KAFKA_BROKERS}
      group_id: iosmith-alert-consumer
  webhook:
    type: http
    settings:
      base_url: ${WEBHOOK_BASE_URL}
      retry_on_5xx: true

flows:
  - name: kafka-critical-alerts
    trigger:
      connector: kafka_alerts
      operation: consume
      params:
        topic: ${KAFKA_TOPIC}
    steps:
      - name: filter-critical
        type: transform
        transform: filter
      - name: forward-to-webhook
        type: connector
        connector: webhook
        operation: post
        params:
          path: /alerts
    on_error:
      strategy: skip
Docker & Kubernetes ready · MQTT · Kafka · HTTP · LiteDB · 1,000+ msg/s on 4 cores

Stop writing the same integration service again.

Every plant, broker, and legacy API gets its own microservice: a consumer loop, some JSON mangling, retry logic you half-tested, and logging you'll regret. Multiply that by every integration and you're maintaining a fleet of near-identical services. I/O Smith replaces them with one engine and one config file.

Parse. Register. Run.

01

Declare

Write connectors and flows in YAML 1.2. Schema-validated at startup with line/column errors — fails fast, CI-friendly, exits non-zero before touching any data.

02

Connect

Built-in connectors: MQTT v3.1.1/v5, Kafka, HTTP/REST webhooks, and LiteDB. Declared once under connectors, shared across flows, with lifecycle and reconnection managed by the engine.

03

Run

Each flow runs as an isolated, backpressured channel pipeline with per-flow concurrency. One flow failing never takes down another.

Built like infrastructure, because it is.

HTTP connector

Expose flows as HTTP endpoints or call external APIs as a step. Supports GET, POST, custom headers, and auth — declared in YAML, no server code required.

JSON transforms

map, filter, set_field take full JSON expressions over payload, headers, and trace context. Compiled once, cached, timeout-bounded. Syntax errors caught at startup, not at 3 a.m.

Error handling as config

retry with backoff, dead_letter to any connector, or skip — declared per flow in YAML. No try/catch pyramids.

Observability by default

Per-flow throughput, failure counters by error type, P50/P95/P99 step latency, queue depth, connector status. W3C trace context on every envelope; one correlation ID finds every log line a message ever touched.

Runtime templating

Step parameters resolve template expressions against the live message — payload fields, headers, trigger metadata, timestamps, UUIDs. Secrets and config via ${ENV_VAR} placeholders, validated at startup.

Secure by default

Secrets only via ${ENV_VAR} — hardcoded credentials are rejected at validation. TLS on all network connectors. Parameterized DB queries, never string interpolation.

Deploy anywhere

Standalone binary, Docker container, or Kubernetes with config via ConfigMap and secrets via Secrets. Starts processing within 5 seconds.

Real flows, not pseudocode.

samples/mqtt-to-litedb/flow.yaml
version: "1.0"

connectors:
  mqtt_broker:
    type: mqtt
    settings:
      host: ${MQTT_HOST}
      port: 1883
      client_id: iosmith-sensor-listener
      qos: 1
      username: ${MQTT_USER}
      password: ${MQTT_PASSWORD}

  sensor_db:
    type: litedb
    settings:
      database: ${LITEDB_PATH}
      collection: sensor_readings

flows:
  - name: sensor-to-database
    description: Receives MQTT sensor readings and persists them to LiteDB
    concurrency: 2
    trigger:
      connector: mqtt_broker
      operation: subscribe
      params:
        topic: sensors/+/data
    steps:
      - name: decode-json
        type: transform
        transform: json_decode

      - name: filter-valid
        type: transform
        transform: filter
        with:
          condition: '$.payload.sensorId != "" and $.payload.value != null and $.payload.machineId != "606ba38d-2979-4a91-a76d-2415e011d1fd"'

      - name: map-envelope
        type: transform
        transform: map
        with:
          expression: '{"sensorId": $.payload.sensorId, "value": $.payload.value, "unit": $.payload.unit}'

      - name: add-timestamp
        type: transform
        transform: set_field
        with:
          field: received_at
          value: '$now()'

      - name: add-topic
        type: transform
        transform: set_field
        with:
          field: mqtt_topic
          value: '$.headers.topic'

      - name: persist
        type: connector
        connector: sensor_db
        operation: insert
        params:
          collection: sensor_readings

    on_error:
      strategy: retry
      max_retries: 3
      retry_delay_ms: 1000

MQTT sensor readings persisted to LiteDB

  • decode-json Parse raw MQTT payload as JSON
  • filter-valid Drop malformed or blocked sensor readings
  • map-envelope Reshape to canonical sensor schema
  • add-timestamp Stamp ingestion time via $now()
  • add-topic Carry MQTT topic into the record
  • persist Insert into LiteDB sensor_readings collection
samples/kafka-to-webhook/flow.yaml
version: "1.0"

connectors:
  kafka_alerts:
    type: kafka
    settings:
      bootstrap_servers: ${KAFKA_BROKERS}
      group_id: iosmith-alert-consumer
      auto_offset_reset: latest
      sasl_mechanism: PLAIN
      sasl_username: ${KAFKA_USER}
      sasl_password: ${KAFKA_PASSWORD}
      security_protocol: SASL_SSL

  webhook:
    type: http
    settings:
      base_url: ${WEBHOOK_BASE_URL}
      timeout_ms: 5000
      headers:
        Authorization: "Bearer ${WEBHOOK_TOKEN}"
      retry_on_5xx: true

flows:
  - name: kafka-critical-alerts
    description: Forwards critical Kafka events to an HTTP webhook
    concurrency: 1
    trigger:
      connector: kafka_alerts
      operation: consume
      params:
        topic: ${KAFKA_TOPIC}
    steps:
      - name: decode-json
        type: transform
        transform: json_decode

      - name: filter-critical
        type: transform
        transform: filter
        with:
          condition: "{{payload.severity}}"

      - name: forward-to-webhook
        type: connector
        connector: webhook
        operation: post
        params:
          path: /alerts

    on_error:
      strategy: skip

Critical Kafka alerts forwarded to an HTTP webhook with retry

  • decode-json Deserialize Kafka message payload
  • filter-critical Pass only messages where severity is set
  • forward-to-webhook POST to webhook with automatic retry on 5xx
samples/http-to-mqtt/flow.yaml
version: "1.0"

connectors:
  http_ingress:
    type: http_ingress
    settings:
      port: ${HTTP_PORT}
      path: /commands

  mqtt_broker:
    type: mqtt
    settings:
      host: ${MQTT_HOST}
      port: 1883
      client_id: iosmith-command-publisher
      qos: 1
      username: ${MQTT_USER}
      password: ${MQTT_PASSWORD}

flows:
  - name: http-to-device-command
    description: Accepts HTTP POST device commands and publishes them to the MQTT command topic
    concurrency: 4
    trigger:
      connector: http_ingress
      operation: listen
    steps:
      - name: decode-json
        type: transform
        transform: json_decode

      - name: filter-valid-command
        type: transform
        transform: filter
        with:
          condition: '$.payload.deviceId != "" and $.payload.command != null'

      - name: map-command
        type: transform
        transform: map
        with:
          expression: '{"deviceId": $.payload.deviceId, "command": $.payload.command, "params": $.payload.params}'

      - name: add-dispatched-at
        type: transform
        transform: set_field
        with:
          field: dispatched_at
          value: '$now()'

      - name: add-trace-id
        type: transform
        transform: set_field
        with:
          field: traceId
          value: '$.trace.traceId'

      - name: encode-json
        type: transform
        transform: json_encode

      - name: publish-command
        type: connector
        connector: mqtt_broker
        operation: publish
        params:
          topic: devices/commands

    on_error:
      strategy: skip

HTTP POST device commands fanned out to MQTT broker

  • decode-json Parse incoming HTTP request body
  • filter-valid-command Reject commands missing deviceId or command
  • map-command Normalize to device command schema
  • add-dispatched-at Stamp dispatch time
  • add-trace-id Carry W3C trace ID into the MQTT payload
  • encode-json Serialize back to JSON for MQTT publish
  • publish-command Publish to devices/commands topic

Every example above ships with the trial download and runs with iosmith run flow.yaml.

Numbers we hold ourselves to.

1,000+ msg/s
Sustained throughput
per flow on 4 cores / 4 GB RAM
< 5 s
From launch to processing
startup time on standard hardware
< 2 s
Config validation
with actionable errors on invalid YAML
≤ 50 ms
JSON evaluation
per message on typical payloads

Metrics sourced from the engine specification. Benchmark methodology will be published when available.

Coming next Drop-in connector plugins — extend I/O Smith to proprietary protocols without waiting on our release cycle. See the roadmap →

Where I/O Smith fits.

Cells marked ⭐ are dimensions where that alternative outperforms I/O Smith. We'd rather help you pick the right tool than win a table.

Dimension I/O SmithNode-REDKafka ConnectCustom microservices
Config format YAML file — git-trackable, diffable, reviewable Visual graph (JSON underneath — noisy diffs) JSON / properties files Code in any language
Code-reviewable Yes — PR diff is the config diff Limited — graph exports are hard to review Yes Yes
Industrial protocols MQTT, Kafka, HTTP, LiteDB (built in) Very broad — 300+ community nodes Kafka ecosystem only Unlimited — write it yourself
Ops burden One binary, one config file, one metrics endpoint Node-RED server + flow management UI to operate Kafka cluster + Connect workers to operate Per-service: CI, deploy, monitor, scale
Best suited for Declarative multi-protocol integration without writing code ⭐ Visual prototyping and low-code / citizen-developer authoring ⭐ Pure Kafka pipelines — 100s of community connectors ⭐ Arbitrary protocol support or bespoke business logic

Every feature, free — for 6 hours a day.
Go 24/7 when you're ready.

Free

Full engine · Free forever

  • Full engine — every built-in connector and transform, no feature gates
  • 6 hours of engine runtime per day, resets daily at 00:00 UTC
  • Free forever — evaluate, prototype, run dev/test on your schedule
  • Docker image or standalone binary, no credit card
Download free

Licensed

Unlimited runtime · Talk to us for pricing

  • Unlimited 24/7 runtime — activated via license key, no redeploy required
  • SLA-backed support and priority fixes
  • Deployment and architecture reviews
  • Early access to roadmap connectors
Talk to sales

Still have questions about the free tier?

What happens when the 6 hours run out?

Trigger connectors pause and in-flight messages are dropped — there is no delivery guarantee at the cap. Do not run the free tier against data you cannot afford to lose. Processing resumes automatically at the daily reset (00:00 UTC), or instantly when you activate a license key — no redeployment needed.

Does the timer only count while the engine is running?

Yes: the counter tracks engine runtime, not wall-clock time. Stop the engine, stop the clock. Restarting the engine does not reset the daily budget — the counter is persisted locally so process restarts cannot circumvent the cap.

How do I go from free to licensed?

Drop your license key into the same deployment via an environment variable or config file. The engine validates it at runtime and lifts the cap immediately — no redeploy, no message-state carryover. Flows restart fresh from your existing YAML config.

From download to flowing in 30 minutes.

docker run -v ./flow.yaml:/app/flow.yaml iosmith/engine

Copy a bundled sample, point it at your broker, watch the metrics endpoint light up — with 5½ hours of today's budget to spare.

Talk to sales

Tell us what you're integrating. We'll get back to you within 24 hours.