Binions integrates with the outside world through five “broker” daemons, each of which speaks one small, generic vocabulary across many concrete technologies. Instead of a separate connector for every email provider, every storage vendor, and every AI model, a broker exposes a handful of plain verbs — send, upload, classify — and a single field on the call picks the underlying technology. You learn the verb once, and the same playbook step works whether the mailbox is IMAP, MQTT, or AMQP, or whether the bucket is S3, MinIO, or plain SFTP.
The one idea on this page. A Binions integration is never “a connector you install.” It is a generic verb plus a selector that names the technology, plus a connection you registered once by an alias. Get those three things straight and every integration in the platform looks the same.
Most automation tools grow by adding connectors: one tile for Gmail, another for Outlook, another for Amazon S3, and so on into the thousands. Every tile is its own little program with its own quirks, and the catalogue is never quite complete. Binions takes the opposite route. It defines a small family of generic operations — the kind of thing you do with any mailbox, any object store, any web API — and lets one field choose the concrete implementation at the moment the call runs.
The pay-off is consistency. The verbs do not change as you add technologies, so:
mail.send means you can send over SMTP today and publish to a message queue tomorrow — same verb, different selector.Each broker is one of the platform’s background services — a daemon. The other daemons (the logger, the scheduler, the database service, and so on) keep the platform running; these five are the ones that reach outward to other systems. Every one follows the same shape: generic verbs in, a selector to choose the technology, and the real-world systems it can reach.
| Broker daemon | Generic verbs | Selector | Connects to |
|---|---|---|---|
| mailbox-service | mail.register_mailbox, mail.send | protocol: — imap / mqtt / amqp (plus SMTP for sending) | Email and message queues |
| aiinjector-service | ai.classify, ai.extract, ai.inject | provider: — the AI engine to call | AI models |
| webhookcaller-service | webhook.register_endpoint, webhook.send | REST, SOAP, gRPC, or approved SSH commands (chosen by the protocol: field) | Any web API, gRPC service, or allow-listed remote command |
| datatransporter-service | data.upload, data.download, data.list_objects, data.transform | backend: — s3 / minio / sftp / ftp / local_fs | Object storage and file transfer |
| traefiklinker-service | traefik.register_route | protocol: — http / https / ws / wss / sse / grpc / grpc-web | The HTTP edge — publishes routes inbound |
Four of the five point outward (the platform calls another system). The odd one out is traefiklinker-service: it works in the other direction, publishing a route on the public HTTP edge so the outside world can reach you. The webhook caller is the universal outbound HTTP client — because SOAP is simply an XML document sent over HTTP, the same webhook.send verb delivers a SOAP request just by setting the right Content-Type and body, with no new verb required.
The selector is the whole trick. The field has a different name per broker —
protocol:for mailboxes and the HTTP edge,provider:for AI,backend:for storage — but it always does the same job: it names the technology, and the broker routes your generic call to the matching implementation.
The selector is an ordinary field on the registration step. If you leave it out, the broker uses a sensible default, so the simplest playbooks stay short:
protocol: is treated as IMAP; a bucket with no backend: defaults to S3-compatible storage. Existing playbooks keep working unchanged as new technologies are added.protocol: mqtt and the very same mail.register_mailbox step now subscribes to an MQTT topic instead of an IMAP folder.Because every broker keeps the same vocabulary regardless of selector, the events it emits are uniform too. A message that arrives over MQTT and one that arrives over IMAP both surface to your playbook as the same kind of “mail received” fact, with the provider-specific details (the topic, the folder, the routing key) preserved inside the event so you can still filter on them.
Integrations are wired up in two clearly separated layers. This is the single most important habit to learn, because it is what keeps credentials out of your day-to-day workflows.
| Provisioning playbooks | Register a connection once, under an alias. This is the only place a secret reference may appear. They run automatically at start-up. |
| Business playbooks | Do the actual work, referencing each connection by the alias it was registered under — never by a raw URL, host, or password. |
A provisioning playbook is triggered by the platform’s boot event and filtered to the playbook service, so it re-establishes every connection each time the platform starts. It is the one context allowed to read a secret, using the ${secret.KEY} syntax — which resolves to a password file under /etc/binions/ rather than a value typed into the file:
# provisioning — registers the "billing" mailbox once, at boot
name: register-billing-mailbox
trigger:
event: Fact.System.Boot
filter:
component.eq: playbook-service
steps:
- id: register
run: mail.register_mailbox
with:
alias: billing
idle: true
imap:
host: imap.example.com
port: 993
username: billing@example.com
password: ${secret.BILLING_MAIL_PASSWORD}
smtp:
host: smtp.example.com
port: 465
username: billing@example.com
password: ${secret.BILLING_MAIL_PASSWORD}
Every business playbook then refers to that mailbox purely by its alias. There is no host, no port, and no password anywhere in the workflow — only the name billing:
# business — reacts to mail on the "billing" mailbox
name: invoice-from-accountant
trigger:
event: Fact.Mail.Received
filter:
via.eq: billing
steps:
- id: extract
run: ai.extract
with:
text: ${trigger.envelope.body_text}
fields:
- name: supplier_name
- name: amount_gross
type_hint: decimal
Secrets stay in one place. Only a provisioning step may carry a
${secret.KEY}reference, and the value itself lives in a protected file on the host — never inline in a playbook and never in your version control. If you ever see a raw URL or password in a business playbook, that connection has not been provisioned properly yet.
Every integration call rides the same internal event bus, and every event carries a correlation id. That single thread is what turns a chain of separate integrations into one auditable story. Consider a common pipeline:
billing mailbox.ai.extract to pull the supplier and amount out of the message body.webhook.send posts the result to a registered endpoint — a chat channel, a CRM, anything with a URL.Four different systems, four different brokers, one workflow — and because the same correlation id travels with every step, you can read the whole run end to end. The logger writes each step as a structured log line tagged with that id, so a single search reconstructs exactly what happened, in order, across all four integrations.
The broker pattern is designed so a technology can be added without disturbing the verbs, and not every option is fully built yet. Some selector values are production-ready and some are still placeholders in the current alpha.
Check the catalogue before you build. A few providers — particularly some AI engines — are stubs in the alpha: the verb works and the wiring is real, but the call returns canned output rather than reaching a live service. The Integration catalogue lists exactly which technology each broker supports today and which are planned, so plan against what is shipping, not what is named.
The principle, though, is stable: as a provider graduates from stub to live, your playbooks do not change — you keep the same verb and the same alias, and only the catalogue entry moves from “planned” to “supported.”