Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

notifications

Named channels that notify steps send into. rflow ships its own senders, so you don't need an external webhook relay.

rflow_version: 1
name: my-project
 
config:
  port: 3947
  db_connection: ${DATABASE_URL}
 
notifications: 
  channels: 
    dev: 
      console: {} 
    ops: 
      telegram: 
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
    eng: 
      slack: 
        webhook_url: ${SLACK_WEBHOOK}
    alerts: 
      discord: 
        webhook_url: ${DISCORD_WEBHOOK}
    pager: 
      pagerduty: 
        routing_key: ${PD_ROUTING_KEY}
    oncall: 
      opsgenie: 
        api_key: ${OPSGENIE_API_KEY}
        eu: true
    sms: 
      twilio: 
        account_sid: ${TWILIO_ACCOUNT_SID}
        auth_token: ${TWILIO_AUTH_TOKEN}
        from: "+15550001111"
        to: "+15552223333"
    audit: 
      email: 
        from: "rflow ops <ops@example.com>"
        to: [treasury@example.com] 
        smtp: 
          host: smtp.example.com
          username: ${SMTP_USER}
          password: ${SMTP_PASS}
 
workflows:
  heartbeat:
    trigger:
      cron: { expression: "0 9 * * *" }
    steps:
      - id: ping
        notify:
          channel: dev
          message: "rflow is up"

Each channel configures exactly one sender (validation error otherwise).

console

The zero-setup sender: no credentials, nothing to reach. The rendered message is written to rflow's log output (the rflow start stream) and journaled like any other sender. Use it locally, then swap in a real channel. No fields: console: {} and a bare console: both parse.

notifications:
  channels:
    dev:
      console: {} 

telegram

FieldDescription
bot_token✅ Bot token from @BotFather
chat_id✅ Target chat/group id

slack

FieldDescription
webhook_url✅ An incoming webhook url

discord

FieldDescription
webhook_url✅ A channel webhook url

pagerduty

Sends through the Events API v2 (events.pagerduty.com/v2/enqueue, event_action: trigger).

FieldDescription
routing_key✅ The integration (routing) key of an Events API v2 integration

Severity follows what fired the notification: notify: steps send error, circuit breaker trips and liveness/stall breaches send critical, and approval requests send info.

opsgenie

Creates alerts via the Alert API (Authorization: GenieKey <api_key>).

FieldDescription
api_key✅ An API integration key
euUse the EU host api.eu.opsgenie.com (default false = api.opsgenie.com)

Priority follows the same taxonomy as pagerduty severity: circuit trips and liveness/stall breaches are P1, notify: steps P3, approval requests P5.

twilio

SMS through the Messages API (basic auth account_sid:auth_token).

FieldDescription
account_sid✅ Account SID
auth_token✅ Auth token
from✅ The sending Twilio number (E.164, e.g. +15550001111)
to✅ The destination number (E.164)

email

One email: sender per channel: a shared envelope plus exactly one transport, either plain SMTP or a first-party provider API (each provider is its own small adapter; there is no generic "mail API" shape). Every credential position is redacted like the other channel secrets.

FieldDescription
from✅ The From header / envelope sender, e.g. rflow ops <ops@example.com>
to✅ Recipient addresses (at least one)
subjectSubject template — default rflow: <workflow>
transport✅ Exactly one of smtp | ses | sendgrid | mailgun | postmark | resend | brevo | gmail_api | microsoft_graph

The message body is plain text (the rendered notify: message).

smtp

Works against any transactional provider's SMTP endpoint and internal MTAs.

FieldDescription
host✅ SMTP server hostname
port587/2587 = STARTTLS required (default 587). 465/2465 = implicit TLS. Any other port (25/2525/…): STARTTLS still required whenever credentials are configured; only the credential-less relay mode falls back to opportunistic TLS
username / passwordOptional PLAIN/LOGIN credentials. Omit both for an IP-allowlist relay (Google Workspace smtp-relay.gmail.com, internal MTAs)
authplain (default when credentials are set) | xoauth2 — pass a caller-supplied access token; rflow never runs an OAuth consent flow
token_commandCommand whose stdout is the XOAUTH2 access token (the email-oauth2-proxy pattern)

Never point it at port 25: direct port-25 is blocked outright on GCP, throttled on AWS/Azure and blocklisted from residential IPs. Provider quirks: Postmark has no implicit-TLS 465 (STARTTLS on 25/2525/587 only), and several providers use a literal SMTP username (SendGrid: apikey, Resend: resend, Postmark: the server token as both username and password).

Provider quickstarts

Each provider config is complete. Swap in your secret and go:

rflow_version: 1
name: my-project
 
config:
  port: 3947
  db_connection: ${DATABASE_URL}
 
notifications:
  channels:
    audit-email:
      email:
        from: "rflow ops <ops@example.com>"
        to: [treasury@example.com]
        # ── pick exactly ONE transport ── 
        sendgrid: { api_key: ${SENDGRID_KEY} } 
        # resend:   { api_key: ${RESEND_KEY} } 
        # brevo:    { api_key: ${BREVO_KEY} } 
        # postmark: { server_token: ${POSTMARK_TOKEN}, message_stream: outbound } 
        # mailgun:  { api_key: ${MAILGUN_KEY}, domain: mg.example.com, region: us }  # us | eu #
        # ses:      { region: us-east-1, access_key_id: ${AWS_KEY}, secret_access_key: ${AWS_SECRET} } 
        # gmail_api:       { client_id: ${G_CLIENT_ID}, client_secret: ${G_SECRET}, refresh_token: ${G_REFRESH} } 
        # microsoft_graph: { tenant_id: ${MS_TENANT}, client_id: ${MS_CLIENT}, client_secret: ${MS_SECRET}, from_mailbox: ops@example.com } 
 
workflows:
  audit-report:
    trigger:
      cron: { expression: "0 9 * * *" }
    steps:
      - id: report
        notify:
          channel: audit-email
          message: "daily treasury report"
ProviderAPI authNotes
sendgridBearerSMTP alternative: username literally apikey
resendBearerrflow always sends the User-Agent Resend requires
brevoapi-key headernew accounts need manual transactional activation before sends deliver (SMTP rejects with 535 until then); the SMTP key ≠ the API key
postmarkX-Postmark-Server-Tokenmessage_stream defaults to Postmark's outbound; no SMTP on 465
mailgunBasic api:<key>region: eu switches to api.eu.mailgun.net. A domain lives in ONE region
sesSigV4 (SESv2 SendEmail)the ses: transport takes IAM keys; SES SMTP credentials are a different, region-specific pair. Use them with the smtp: transport instead. Sandbox accounts only reach verified recipients
gmail_apiOAuth refresh token / service accountsee below
microsoft_graphclient credentialssee below

Gmail and Google Workspace

Mailbox accounts suit low-volume ops notices; at volume use a transactional provider above.

  • Consumer Gmail (app password + smtp:) — raw account passwords have been dead since 2022. Enable 2-Step Verification, mint an app password (Google Account → Security → 2-Step Verification → App passwords), then smtp: { host: smtp.gmail.com, username: you@gmail.com, password: ${APP_PASSWORD} }. App passwords are auto-revoked when the account password changes; caps at roughly 500 mails/day.
  • Workspace relay (no credentials at all) — smtp-relay.gmail.com supports IP-allowlist relaying (Admin console → Apps → Google Workspace → Gmail → Routing → SMTP relay service): omit username/password and list your egress IP there. 10k/user/day.
  • gmail_api with a refresh token — a GCP project + OAuth consent screen mint the client_id/client_secret/refresh_token trio; rflow exchanges the refresh token at send time (it never runs the consent flow itself).
  • gmail_api with a service account (Workspace) — the one fully non-interactive Google path: create a service account, enable domain-wide delegation for the gmail.send scope in the Admin console, then gmail_api: { service_account_key: ${G_SA_JSON}, impersonate: ops@example.com }.

Microsoft 365 and Outlook.com

  • Prefer microsoft_graph. Basic SMTP AUTH on Microsoft 365 is disabled by default from the end of Dec 2026 (550 5.7.30) and is removed entirely in H2 2027. Register an app in Entra, grant the application Mail.Send permission with admin consent, create a client secret, and set from_mailbox to the sending mailbox.
  • Scope the app with an ApplicationAccessPolicy. Application Mail.Send is tenant-wide send-as-anyone by default. Constrain the app registration to the one ops mailbox (New-ApplicationAccessPolicy -PolicyScopeGroupId ops@example.com ...).
  • Consumer Outlook.com is unsupported: password SMTP and app passwords died in Sept 2024 and Graph has no unattended path for consumer accounts. Route through any transactional relay instead.

Doctor checks

rflow doctor validates every configured email channel without sending a message: SMTP gets a real connect + EHLO + STARTTLS negotiation + an AUTH dry-run when credentials are present; the API transports validate the credential against a no-send endpoint (SendGrid scopes, Mailgun domain, Postmark server, Resend domains, Brevo account, SES GetAccount); Google and Microsoft transports acquire a token and stop. Failure hints name the provider-specific fix: 550 5.7.30 → switch to microsoft_graph, Brevo 535 → transactional activation pending, Gmail 535 → use an app password.

Using channels

rflow_version: 1
name: transfer-alerts
 
config:
  port: 3948
  db_connection: ${DATABASE_URL}
 
networks:
  - name: ethereum
    chain_id: 1
    rpc: ${ETH_RPC}
 
contracts:
  USDC:
    abi: ./abis/erc20.json
    addresses:
      ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
 
notifications:
  channels:
    ops:
      telegram:
        bot_token: ${TG_BOT_TOKEN}
        chat_id: ${TG_CHAT_ID}
 
workflows:
  large-transfer:
    trigger:
      event:
        contract: USDC
        name: Transfer
        network: ethereum
        where: "${{ trigger.args.value > wei('1000000', 6) }}"
    steps: 
      - id: alert
        notify: 
          channel: ops
          message: "large transfer: ${{ format_units(trigger.args.value, 6) }} USDC"

Messages are full templates: any expression works, and values under secrets: are redacted if they ever end up in one.