- toUserId 解析为空时静默跳过 (succeeded, sentCount=0, skipped:true) 而非 failed
→ 支持'仅有重复订单时才通知'的条件通知编排
- 必填校验只列出实际缺失字段 (message),不再误报已解析的字段
- 文档统一变量格式: {{nodeId.path}} 为主,{{index.path}} 兼容
- spec 6/6,线上实测: 空收件人 skip + 非空正常发送均验证
9.0 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 {{nodeId.path}} (or the compatible {{index.path}} positional form):
{{n0.record.id}}— trigger record's id (n0 = trigger node){{n0.record.fields.Status}}— trigger record's Status field{{2.result}}— output of the first action (numeric index form; index = topological execution order, trigger is always 1)
Both forms resolve to the same value — {{n0.record.id}} ≡ {{1.record.id}}. Use {{nodeId.path}} in new workflows for clarity.
Empty-value semantics: if a variable resolves to an empty string, the node sees the literal empty value. For
sendNotification, an emptytoUserIdmakes the node silently skip (statussucceeded,sentCount: 0) instead of failing — so a script action that returns an empty recipient implements "notify only when X" conditional patterns. An emptymessagestill fails withMissing required fields: message.
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 '{{n0.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() };
'