- skill: SKILL.md (7 actions in description + routing table entry), action-types.md (new section #4 sendNotification: config keys, variables, system-in-app semantics), automation-guide.md/cli-reference.md (--to-user/--title/--message/--severity/--path), development.md (7 actions), metadata.json abstract - cli: action add --type sendNotification now builds config from granular flags (toUserId/title/message/severity/path); ACTION_TYPES includes sendNotification; live-verified via workflow create + action add + delete
8.5 KiB
Automation Guide
Automations (workflows) are event-driven: a trigger fires, then actions run in topological order. inkreach-teable's automation engine is self-built — use the inkreach-teable CLI (not official teable).
Required reading before configuring triggers: trigger-types.md Required reading before writing script actions: action-types.md
Mental Model
[Trigger] → [Action 1] → [Action 2] → ... → [triggerEnd]
- A workflow has exactly one trigger node (type
trigger) and N action nodes (typeaction). - Nodes connect via edges (linear chain; each node has at most one upstream — no parallel branches).
- Activating freezes a snapshot; execution runs against the snapshot, not the draft.
hasDraft: true= unactivated changes exist; re-activateto apply.
Available Commands
| Command | Purpose |
|---|---|
workflow list |
List all workflows in the base |
workflow get <id> |
Detail (nodes + edges + config) |
workflow create --name <n> |
Create (auto-adds a recordCreated trigger) |
workflow update <id> [--name] [--description] |
Rename |
workflow delete <id> |
Soft-delete |
workflow activate <id> |
Snapshot + register trigger + create auto-token |
workflow deactivate <id> |
Unregister trigger |
workflow test <id> |
Mock-input test run (no trigger, no queue) |
trigger set <id> --type <T> ... |
Set trigger type + config |
action add <id> --type <T> ... |
Add an action node |
action update <id> <nodeId> ... |
Update an action node |
action delete <id> <nodeId> |
Remove an action node |
secret set/list/delete |
Manage script secrets |
runs list/get/rerun |
Execution history |
fire button-click/webhook |
Manually trigger for testing |
Trigger Selection
| User scenario | --type |
Key options |
|---|---|---|
| React to new records | recordCreated |
--table-id |
| React to record changes | recordUpdated |
--table-id, --field-ids (empty = all) |
| React when a condition matches | recordCondition |
--table-id, --conditions (required) |
| React to create OR update | recordCreatedOrUpdated |
--table-id, --conditions (optional) |
| Form submission | formSubmitted |
--table-id, --form-ids <csv> |
| Schedule (only type+interval work) | scheduledTime |
--schedule '{"timing":{"type":"minutes","interval":5}}' |
| Button field click | buttonClick |
--table-id, --field-ids |
| External HTTP POST | webhook |
(none; auth backend-ignored) |
Batch processing (record triggers only): add --batch '{"enabled":true,"batchWindow":2000,"batchSize":100}' to merge events within a window into a single run. The script receives an array of records.
Full config details: trigger-types.md.
Action Selection
| Goal | --type |
Key options |
|---|---|---|
| Run JS code (transform, call APIs, compute) | script |
--code '<js>' or --code-file <path>, --timeout <ms> |
| Send email | sendEmail |
--to, --subject, --body <html> |
| Call external HTTP | httpRequest |
--url, --method, --headers <json>, --req-body <json> |
| Create records | createRecord |
--target-table <id>, --records <json> |
| Update records | updateRecord |
--target-table <id>, --records <json> |
| Query records | getRecords |
--target-table <id>, --query <json> |
| Send system notification (站内信) | sendNotification |
--to-user <usrId>, --title, --message, --severity, --path |
Full config details: action-types.md.
Creation Workflow
# 1. Gather IDs
inkreach-teable table get tblXXX # confirm table
inkreach-teable field list tblXXX # find field ids for triggers
# 2. Create workflow
inkreach-teable workflow create --name "My automation"
# → returns wflXXX
# 3. Set trigger
inkreach-teable trigger set wflXXX --type recordUpdated \
--table-id tblXXX --field-ids '["fldStatus"]'
# 4. Add action(s)
inkreach-teable action add wflXXX --type script \
--code 'return { status: triggerInput.record.fields.Status }'
# 5. (Optional) add secrets the script needs
inkreach-teable secret set wflXXX --name API_KEY --value sk_xxx
# 6. Activate
inkreach-teable workflow activate wflXXX
# → returns snapshotId
# 7. Test
inkreach-teable fire button-click tblXXX recXXX fldXXX # for buttonClick
# or just perform the triggering action (create/update a record, POST webhook...)
inkreach-teable runs list wflXXX
Editing an Existing Workflow
# Change trigger config
inkreach-teable trigger set wflXXX --type buttonClick --table-id tblXXX --field-ids '["fldNew"]'
# Add another action
inkreach-teable action add wflXXX --type sendEmail --to a@b.com --subject Done --body '<p>ok</p>'
# Remove an action
inkreach-teable action delete wflXXX node-12345
# RE-ACTIVATE to snapshot the changes
inkreach-teable workflow activate wflXXX
trigger set / action add/update/delete internally do GET→modify→PUT the whole graph, so they're safe. But you must re-activate for changes to take effect at runtime.
Secrets
Scripts often need API keys. Never hardcode — use secrets.
inkreach-teable secret set wflXXX --name OPENAI_KEY --value sk-xxx --description "for AI calls"
inkreach-teable secret list wflXXX # names only — value never returned
inkreach-teable secret update wflXXX secXXX --value sk-new
inkreach-teable secret delete wflXXX secXXX
In a script action, read via process.env.OPENAI_KEY. Secrets are AES-encrypted at rest.
Testing / Triggering
Most triggers fire from real events (record create/update, form submit, schedule). Two triggers are manually fireable for testing:
# Button field click
inkreach-teable fire button-click <tableId> <recordId> <fieldId>
# Webhook (no auth needed)
inkreach-teable fire webhook <baseId> <workflowId> --body '{"event":"test"}'
For other triggers, test by performing the real action (create/update a record, submit the form). Then inspect:
inkreach-teable runs list wflXXX --status failed
inkreach-teable runs get wflXXX <executionId>
# rerun a failed execution
inkreach-teable runs rerun wflXXX <executionId>
workflow test vs real fire
workflow test <id> runs actions synchronously with mock input (a fake
{record:{id:'rec_test',fields:{}}, user:{...}}). It does NOT fire the
trigger or use the queue. Two caveats:
- Upstream outputs are absent in test mode — if a script reads
triggerInput.record.fields.<X>, it gets{}because the mock record has no fields. Test only validates that the script runs without throwing, not that it produces correct data. - For data-dependent validation, do a real fire (button-click / webhook)
or perform the real triggering action, then check
runs list.
Variable References (cross-node data)
An action can reference upstream nodes' output in string fields via {{nodeIndex.path}}:
{{1.record.id}}— trigger record's id (trigger = node 1){{1.record.fields.Status}}— trigger record's Status field{{2.records}}— output of the first action (node 2)
Node index follows topological execution order (trigger is always 1). Script actions can also read the merged upstream output directly via triggerInput.
Common Patterns
Notify on status change
inkreach-teable workflow create --name "Status→email"
inkreach-teable trigger set wflXXX --type recordUpdated --table-id tblXXX --field-ids '["fldStatus"]'
inkreach-teable action add wflXXX --type sendEmail \
--to ops@x.com --subject 'Status changed' \
--body '{{1.record.fields.Status}}'
inkreach-teable workflow activate wflXXX
Daily digest
inkreach-teable trigger set wflXXX --type scheduledTime \
--schedule '{"timing":{"type":"hours","interval":24}}'
Button → script → update another table
inkreach-teable trigger set wflXXX --type buttonClick --table-id tblA --field-ids '["fldGo"]'
inkreach-teable action add wflXXX --type script --code '
const r = triggerInput.record;
// call back into the instance API using the auto-injected token
const res = await fetch(triggerInput._apiBaseUrl + "/api/table/tblB/record", {
method: "POST",
headers: { Authorization: "Bearer " + triggerInput._accessToken, "Content-Type": "application/json" },
body: JSON.stringify({ fields: { Source: r.id } })
});
return { created: await res.json() };
'