How it works
- Step 1Pair a runner and add the Slack credential — Install and pair the @jadapps/runner, then store your Slack bot token (the
xoxb-…value) on it under a ref likeslack-prod. The two connector nodes look the token up bycredentialRefat run time viactx.credentials— it is never sent to JAD's server. - Step 2Fork this workflow — Click Fork. The from-blueprint route places the three nodes left-to-right, auto-wires
http-request→csv-validator→slack-postmessage, copies the0 9 * * 1schedule, and creates a private draft owned by you, snapshotted as version 1. - Step 3Point the HTTP Request at your CSV — Open the HTTP Request node and set the
urlto your report endpoint, leavemethodas GET, and adjusttimeoutMs(default 60000) if the download is large. Add acredentialRefhere too if the URL needs auth. - Step 4Set the Slack channel and message — On the Slack node, set
channel(#ops-summaryor aCxxxid) and thecredentialRef. Leavetextblank to post the upstream CSV text, or template a message — the node falls back to the upstream file's first 16KB whentextis empty. - Step 5Confirm the cron schedule — The fork inherits
0 9 * * 1(Monday 09:00 UTC). Edit it to your cadence — the matcher supports*, ranges, lists, and*/Nsteps on standard 5 fields. Times are evaluated in UTC, so offset for your timezone. - Step 6Publish and watch the run history — Publish the workflow. The cron tick enqueues a run when due and pushes a wake to your runner; each execution writes a row to the run history with a full step trace you can inspect or replay.
The real node chain
Exactly what this blueprint places on the canvas, in order, from lib/orchestrator/seo-workflow-blueprints.ts. Edges are auto-wired first-out → first-in when the from-blueprint fork builds the graph.
| Step | Tool slug | Category | Key config | Output port → next input |
|---|---|---|---|---|
| 1 | http-request | connector (Pro) | method GET · url example.com/data.csv · timeoutMs 60000 | out (file) → csv-validator.in (csv) |
| 2 | csv-validator | csv (free) | no config — validates and passes rows through | ok (csv) → slack-postmessage.in (*) |
| 3 | slack-postmessage | connector (Pro) | channel #ops-summary · text "Weekly CSV summary attached." · credentialRef slack-prod | out (json) — Slack API response |
Schedule, credential, and tier matrix
What forking, scheduling, and running this workflow actually require, grounded in the fork route, cron tick, and tier-precheck.
| Concern | How it works | Requirement |
|---|---|---|
| Schedule | Blueprint scheduleCron is 0 9 * * 1; the from-blueprint fork copies it, so your draft is scheduled on creation | Edit the cron in the Triggers tab; evaluated in UTC |
| Cron firing | Cloudflare Cron Triggers hit the tick route, which enqueues a workflow_run_queue row for each due workflow and pushes a wake to your runner | A paired runner online (it also polls every ~10s as a fallback) |
| Slack credential | Stored on the runner; the Slack node resolves it from ctx.credentials[credentialRef] and adds the Bearer header itself | A runner credential under the ref you set (e.g. slack-prod) |
| Tier to run | Both connectors are isPro: true; tier-precheck returns the strictest minTier across the graph | Pro — the two connector nodes are gated; csv-validator alone is free |
| Run history | Each run writes to workflow_runs with a trace blob, status, and start/end timestamps; the runs API returns the latest 50 | Owner of the workflow (the runs route checks user_id) |
Per-node configuration fields
Config fields exposed on each node in the orchestrator, from the tool registry. Connector tokens are referenced, never embedded in the graph.
| Node | Field | Purpose |
|---|---|---|
| HTTP Request | url / method / timeoutMs | Where to fetch the CSV, verb (GET here), and the abort timeout (default 60000ms) |
| HTTP Request | credentialRef / headers / body | Optional auth credential ref, extra headers (JSON or Name: value lines), and request body |
| CSV Validator | (none) | Validates the file; ok port re-emits the CSV unchanged, summary shows Health N/100 · N rows · N issues |
| Slack: Post Message | channel / credentialRef | Channel name (#ops-summary) or id (Cxxx), and the runner credential holding the bot token (required) |
| Slack: Post Message | text / blocks / thread_ts | Message text (falls back to upstream CSV text), optional Block Kit JSON, optional thread reply timestamp |
Cookbook
These recipes use the exact node slugs and config keys this blueprint ships with. Connector nodes (http-request, slack-postmessage) execute on your paired runner; the CSV validator runs there too as part of the scheduled chain. Swap the url, channel, and credentialRef to match your setup — everything else maps one-to-one to the graph you fork.
The blueprint as forked
ExampleThe three-node chain exactly as the from-blueprint route builds it, with the schedule copied in.
schedule_cron: 0 9 * * 1 (Mondays, 09:00 UTC)
[1] http-request
method: GET
url: https://example.com/data.csv
timeoutMs: 60000
out(file) ──▶
[2] csv-validator
(no config)
summary: "Health 100/100 · 1284 rows · 0 issues"
ok(csv) ──▶
[3] slack-postmessage
channel: #ops-summary
text: "Weekly CSV summary attached."
credentialRef: slack-prodPost the validated CSV body instead of static text
ExampleLeave the Slack text blank and the node posts the upstream file's content (first 16KB) so the channel sees the actual rows.
slack-postmessage config: channel: #revenue-updates credentialRef: slack-prod text: (leave empty) Runner behaviour: text is empty, so the node reads the first fileRef from scratchDir and posts up to 16KB of it, appending "…[truncated]" if longer.
Daily backlog at 8am UTC
ExampleChange the cron field to fire every weekday morning. The tick walks minute-by-minute since last_fired_at, so a ~5-minute tick still catches the 08:00 slot.
schedule_cron: 0 8 * * 1-5 Fields: minute=0 hour=8 dom=* month=* dow=1-5 (Mon–Fri). Evaluated with getUTCHours/getUTCDay, so 08:00 here is 08:00 UTC, not local time.
Auth the source download
ExampleWhen the CSV sits behind a token, add a credentialRef and headers on the HTTP Request node — the runner injects the stored credential.
http-request config:
method: GET
url: https://api.internal/report.csv
credentialRef: reports-api
headers: {"Accept":"text/csv"}
timeoutMs: 120000Thread the digest under a pinned message
ExampleReply into an existing thread by passing thread_ts so weekly digests stack in one place instead of flooding the channel.
slack-postmessage config: channel: #ops-summary credentialRef: slack-prod thread_ts: "1718000000.000100" text: "Weekly summary — see attached rows." Returns outputs.response (Slack JSON), channel, and status on the out(json) port.
Edge cases and verbatim errors
No runner paired
failThe HTTP Request and Slack nodes are connectors — they only execute on a paired @jadapps/runner. If the browser tries to run them (runner offline), the connector handler throws "<slug> is a connector and requires a paired @jadapps/runner." Pair a runner and set it in the orchestrator status pill before publishing.
Missing Slack credential ref
credential_missingIf credentialRef is blank the Slack node returns missing_credential; if the ref is set but not found on the runner it returns credential_missing ("credential <ref> not found on runner"). Add the credential under the exact ref name and re-run.
Free tier user runs the workflow
failBoth connectors are isPro: true. tier-precheck returns the strictest minTier across the graph and blocks the run with a single upgrade CTA naming the gated nodes. The csv-validator step alone is free, but the chain needs Pro to execute the HTTP and Slack steps.
Slack token rejected
errorOn a non-2xx response or Slack returning ok:false, the node returns ok=false with a slack_<error> code (e.g. slack_invalid_auth, slack_channel_not_found). The run is marked failed in the run history with that code so you can fix the token or channel and replay.
Source URL returns empty or non-CSV
invalidThe HTTP Request will still stream whatever the endpoint returns. The csv-validator then reports a low health score and parse issues in its summary, but it passes the bytes through on the ok port — so an empty body posts an empty/garbled Slack message. Check the validator summary before trusting the digest.
Schema drift in the export
GREENThe csv-validator node ships with no schema config, so it checks structural health (column-count consistency, ragged rows) rather than a fixed schema. Renamed or reordered columns won't fail the run; wire a schema input into the validator's second port if you need strict column enforcement.
HTTP download exceeds the timeout
errorThe HTTP Request aborts at timeoutMs (default 60000). A slow or large report throws and aborts the run under the default error policy. Raise timeoutMs (max 600000) on the node, or set the node's error policy to retry for transient network blips.
Cron fires but nothing runs
GREENThe tick only enqueues a run and pushes a wake — it does not execute. If your runner is offline at fire time, the queue row waits and the runner claims it on its next ~10s poll once back online. last_fired_at is stamped so the slot isn't double-fired.
Cron expression typo
failAn invalid cron (wrong field count, out-of-range value, unsupported nickname like @hourly) is rejected by parseCron during the tick; the workflow is skipped and last_fire_error is set on the row so it surfaces for debugging. Use plain 5-field POSIX syntax.
Channel name vs channel id
GREENBoth #ops-summary and a raw Cxxxxxx id work — the node passes channel straight to chat.postMessage. A name only resolves if the bot is a member of that channel, so invite the bot first or use the id to avoid slack_channel_not_found.
Frequently asked questions
What tools does this workflow actually chain?
Three nodes in order: **HTTP Request** (GET the CSV), **CSV Validator** (shape-check and pass through), and **Slack: Post Message** (post via chat.postMessage). It does not use a row-count or sum tool — the summary you see comes from the validator's health/rows/issues report and whatever text you set on the Slack node.
Does my Slack token leave my device?
No. The Slack bot token is stored as a credential on your paired runner and resolved at run time via ctx.credentials[credentialRef]; the runner adds the Bearer header itself. JAD's central API never accepts file uploads and never receives the token — connectors run on the runner.
Is this scheduled out of the box?
Yes. The blueprint's scheduleCron is 0 9 * * 1 (Mondays at 09:00 UTC). The from-blueprint fork copies that schedule into your new workflow, so it is scheduled the moment you fork — unlike forking one of your own workflows, which starts unscheduled.
What timezone is the cron in?
UTC. The matcher compares against getUTCMinutes/getUTCHours/getUTCDate/getUTCMonth/getUTCDay, so 0 9 * * 1 fires at 09:00 UTC. Offset the hour field for your local time (e.g. 09:00 US Eastern in winter would be 0 14 * * 1).
What cron syntax is supported?
Standard 5-field POSIX: minute, hour, day-of-month, month, day-of-week. It supports *, literals, A-B ranges, A,B,C lists, and */N steps. It does not support nicknames (@hourly), seconds, named days/months, or L/W/# — a bad expression is rejected and recorded in last_fire_error.
Which subscription tier do I need?
Pro. Both the HTTP Request and Slack nodes are isPro: true connectors (shown as "PRO · 1 credit"). tier-precheck scans the graph and returns the strictest minTier; with two Pro connectors the whole workflow requires Pro to run.
How do I fork it?
Click Fork on this page. The from-blueprint route builds the graph (240px horizontal stride), auto-wires consecutive nodes via the first compatible out→in ports, copies the schedule and description, creates a private workflow owned by you, and snapshots it as version 1 with a forked audit event.
Can I see the history of each run?
Yes. Every execution writes a row to workflow_runs with a trace blob, a status (running/success/failed/cancelled), start/end timestamps, and input/output summaries. The runs API returns the latest 50 for the workflow's owner, so you get a full audit trail per run.
What if Slack is down or the token is wrong?
The node returns ok=false with a slack_<error> code (or network_error on a fetch failure) and the run is recorded as failed. Fix the credential or channel and trigger another run — the cron will also retry on its next due slot.
Can I post the actual CSV rows instead of a fixed message?
Yes. Leave the Slack node's text empty and it falls back to the upstream file content, posting up to the first 16KB (appending "…[truncated]" beyond that). For a tidier digest, template text yourself or pass Block Kit JSON in the blocks field.
Can I summarise more than one CSV?
The shipped chain handles one source. To combine sources, add a node like csv-merger before the validator, or fork the workflow once per source and point each at a different channel. The validator and Slack node stay the same.
What related workflows should I look at?
If your data starts dirty, run dedupe-excel-csv-workflow or merge feeds with merge-supplier-inventory-csvs-workflow first. For a different sink, pdf-batch-extract-to-sheets and rss-to-notion-digest follow the same fetch-process-deliver shape. To pre-clean a CSV by hand, see csv-cleaner and csv-deduplicator.
Local-first by design
This workflow executes entirely on your jadapps-runner. API keys, database credentials, and OAuth tokens are stored in an AES-GCM-encrypted vault on your device — they are never uploaded to JAD Apps' servers. The server only stores the workflow graph (the recipe), not the secrets.