Advanced Multi-Feature Flow
Complex production example combining webhook ingestion, templates, email notifications, human approvals, retries, and persistence.
Quick Answer
Use this pattern when you need one flow to process webhook events, send templated email notifications, gate with approval, and persist artifacts/audit state.
Use Case
Order risk workflow with:
- webhook + manual + schedule triggers
- template-driven HTTP/notification payloads
- human approval wait
- email notifications via SendGrid HTTP channel
- persisted result artifacts (
res://) - KV audit write for cross-run lookup
Full Definition
{:slug :advanced-order-orchestration
:name "Advanced Order Orchestration"
:concurrency {:type :keyed
:key-field :order-id
:on-new-version :drain}
:requires [{:slot :orders-api
:type :http-api
:label "Orders API"
:base-url "https://api.example.com"
:auth {:type :bearer}}
{:slot :sendgrid
:type :http-api
:label "SendGrid API"
:base-url "https://api.sendgrid.com"
:auth {:type :bearer}}
{:slot :webhook-signing-secret
:type :secret
:label "Webhook Signing Secret"}]
:templates [{:id :fetch-risk
:type :http-request
:request {:method :get
:path "/orders/{{order-id}}/risk"}}
{:id :approval-notice
:type :notification
:channels {:http {:connection :sendgrid
:path "/v3/mail/send"
:method :post
:json {:personalizations [{:to [{:email "approver@example.com"}]}]
:from {:email "approvals@example.com"}
:subject "Approve order {{order-id}}"
:content [{:type "text/plain"
:value "Approve: {{approval-url}}\nReject: {{rejection-url}}"}]}}}}]
:functions [{:id :risk-summary
:language :clojure
:code "(fn [risk] {:risk-level (:risk-level risk) :risk-score (:score risk) :needs-approval (= :high (:risk-level risk))})"}]
:triggers [{:type :manual :label "Run" :enabled true :config {}}
{:type :schedule
:label "Hourly"
:enabled true
:config {:cron "0 * * * *"}}
{:type :event
:label "Order Updated"
:enabled true
:config {:source :webhook
:event-name "orders.updated"
:path "/hooks/orders"
:auth {:type :hmac-sha256
:header "Stripe-Signature"
:secret-ref :webhook-signing-secret}}}]
:flow
'(let [input (flow/input)
order-id (:order-id input)
order (flow/step :http :fetch-order
{:connection :orders-api
:method :get
:path (str "/orders/" order-id)
:retry {:max-attempts 3 :backoff-ms 500}})
risk (flow/step :http :fetch-risk
{:connection :orders-api
:template :fetch-risk
:data {:order-id order-id}
:retry {:max-attempts 3 :backoff-ms 1000}})
summary (flow/step :function :risk-summary
{:ref :risk-summary
:input risk})
risk-artifact (flow/step :function :persist-risk
{:input {:order order :risk risk :summary summary}
:persist {:type :blob}})
approval ^{:label "Manager approval required?"
:yes "Wait for manager action"
:no "Auto-approve"}
(if (:needs-approval summary)
(flow/step :wait :manager-approval
{:key (str "approval:order:" order-id)
:timeout "2h"
:on-timeout :continue
:default-value {:action :reject :reason :timeout}
:notify {:template :approval-notice
:data {:order-id order-id
:risk-score (:risk-score summary)}}})
{:action :approve :reason :auto})
action (keyword (or (:action approval) "reject"))
fulfillment ^{:label "Fulfillment decision"
:yes "Create fulfillment"
:no "Skip fulfillment"}
(if (= :approve action)
(flow/step :http :fulfill-order
{:connection :orders-api
:method :post
:path (str "/orders/" order-id "/fulfill")
:retry {:max-attempts 4 :backoff-ms 1500}})
{:status :not-fulfilled})
_audit (flow/step :kv :write-audit
{:type :kv
:operation :set
:key (str "orders:audit:" order-id)
:value {:order-id order-id
:action action
:risk-level (:risk-level summary)
:artifact-ref (:ref risk-artifact)}
:ttl 604800})
notify (flow/step :notify :send-outcome-email
{:type :notify
:channels {:http {:connection :sendgrid
:path "/v3/mail/send"
:method :post
:json {:personalizations [{:to [{:email "ops@example.com"}]}]
:from {:email "alerts@example.com"}
:subject "Order {{order-id}} result"
:content [{:type "text/plain"
:value "action={{action}}, artifact={{artifact-ref}}"}]}}}
:data {:order-id order-id
:action (name action)
:artifact-ref (:ref risk-artifact)}})]
{:order-id order-id
:action action
:approval approval
:risk-summary summary
:risk-artifact-ref (:ref risk-artifact)
:fulfillment fulfillment
:notification notify})}
Explanation Highlights
- Webhook trigger is modeled as event trigger with
:source :webhookand explicit auth. - Risk payload is persisted as
res://to avoid large inline state. - Wait step uses canonical
:key+:timeout+:on-timeoutsemantics. - Branch metadata labels (
^{:label ...}with:yes/:no) make decisions readable in run timelines. - Approval notification template drives email-like HTTP channel delivery with action links.
- Notify step sends final outcome email through SendGrid HTTP API.
- KV write stores compact audit state for later lookup.
Operational Notes
- keep webhook auth on; avoid unauthenticated public endpoints
- keep wait timeouts bounded and branch timeout behavior explicitly
- treat
:httpchannel as the canonical email delivery path - watch result sizes: persist early when output may exceed inline limits