# openclaw-action workflow This skill ships an importable workflow at: - `assets/openclaw-action.workflow.json` It implements a real local OpenClaw → n8n router. ## What it does - accepts `POST /webhook/openclaw-action` - normalizes incoming JSON into an action contract - supports these actions in the shipped asset: - `append_log` - `get_logs` - `notify` - `send_notification_draft` - `send_email_draft` - `list_email_drafts` - `delete_email_draft` - `send_gmail_draft` (alias: `send_approved_email`) - `create_calendar_event` - `list_upcoming_events` - `update_calendar_event` - `delete_calendar_event` - `approval_queue_add` - `approval_queue_list` - `approval_queue_resolve` - `approval_history_attach_execution` - `fetch_and_normalize_url` - `inbound_event_filter` - returns normalized JSON responses - returns `400` for unknown actions - returns `400` when required args are missing ## Current side effects ### `append_log` - appends records into workflow static data under key: - `actionLog` - keeps the most recent `200` entries - persists in n8n's database when the workflow execution succeeds Example stored record: ```json {"ts":"2026-03-12T07:00:00Z","source":"openclaw-action","request_id":"abc","text":"backup complete"} ``` ### `send_notification_draft` - queues an approval-gated notification proposal into workflow static data under key: - `approvalQueue` - when resolved with `decision=approve`, it executes the existing `notify` path and sends through Telegram + Discord - uses only the already-configured notification credentials in the live n8n instance ### Gmail + Calendar approval-gated actions Actions: - `send_email_draft` - `list_email_drafts` - `delete_email_draft` - `send_gmail_draft` (alias: `send_approved_email`) - `create_calendar_event` - `list_upcoming_events` - `update_calendar_event` - `delete_calendar_event` Behavior: - queue proposals into workflow static data under key: - `approvalQueue` - keep the most recent `200` pending entries - return explicit approval policy metadata per action (`approval.policy`, `approval.required`, `approval.mutation_level`) - do **not** execute Gmail/Calendar side effects directly in the shipped starter workflow - are intended for host-side execution via the included `gog` bridge after explicit approval resolution Approval policy defaults by action family: - notification family - `send_notification_draft` - `approval.family = "notification"` - `approval.required = true` - `approval.mutation_level = "high"` - approved items execute inline in n8n via the existing `notify` path - Gmail family - read-only: `list_email_drafts` - `approval.family = "gmail"` - `approval.required = true` - `approval.mutation_level = "low"` - still queued so operators must explicitly acknowledge host-side Gmail reads - mutating: `send_email_draft`, `delete_email_draft`, `send_gmail_draft` / `send_approved_email` - `approval.family = "gmail"` - `approval.required = true` - `approval.mutation_level = "high"` - Calendar family - read-only: `list_upcoming_events` - `approval.family = "calendar"` - `approval.required = true` - `approval.mutation_level = "low"` - mutating: `create_calendar_event`, `update_calendar_event`, `delete_calendar_event` - `approval.family = "calendar"` - `approval.required = true` - `approval.mutation_level = "high"` - manual/generic approvals - `approval_queue_add` - no automatic side effect is implied; the operator decides what the queued item means Queue/history entries now also carry compact operator-facing fields for low-noise review: - `approvalQueue[].payload_preview` - `approvalQueue[].operator.summary_line` - `approvalHistory[].operator.execution_state` - `approvalHistory[].operator.result_refs` ### `approval_queue_resolve` - removes one item from `approvalQueue` - appends the resolved entry into: - `approvalHistory` - supports optional notification on approval/rejection - executes notification drafts inline when the approved item kind is `notification` - returns both the full resolved item and a compact operator view at: - `result.item_compact` ### `approval_history_attach_execution` - patches an existing resolved history item in `approvalHistory` - designed for host-side executors that run outside n8n itself - used by the included `scripts/resolve-approval-with-gog.py` bridge to attach Gmail/Calendar execution results - the updated history entry now includes low-noise operator metadata such as: - `operator.summary_line` - `operator.execution_state` - `operator.result_refs` - `execution.summary` - returns both the full item and `result.item_compact` ### `fetch_and_normalize_url` - fetches a remote `http` or `https` URL from inside n8n - normalizes HTML/text/JSON into a single response shape - returns title/excerpt/body text suitable for downstream summarization or logging - uses n8n's runtime HTTP helper inside the Code node rather than relying on global `fetch` - supports optional arg `skip_ssl_certificate_validation: true` for runtimes with incomplete CA trust ### `inbound_event_filter` - classifies inbound events as `urgent`, `important`, `watch`, or `deduped` - stores recent events in: - `inboundEvents` - stores recent dedupe keys in: - `eventDedup` - can fan out a notification for urgent/important non-duplicate events ### `notify` - sends a Telegram message using credential: - `Telegram Bot (OpenClaw)` - sends a Discord message using credential: - `Discord Bot Auth` - current targets mirror the already-working reminder workflow ## Why workflow static data first Why this first: - built-in, no extra credentials - persists without guessing writable filesystem paths - good fit for queues, recent breadcrumbs, and small operational state - lets us implement safe approval-gated patterns immediately When to add provider-backed steps later: - email draft creation in Gmail/Outlook - calendar writes in Google Calendar - Airtable/Sheets append pipelines - long-retention logs or external archival ## Intentional security choice The exported workflow leaves the Webhook node auth unset in the JSON file. Why: - n8n credentials are instance-local - secrets should not be embedded in a shareable skill asset After import, set this manually in n8n: - Webhook node → **Authentication** → `Header Auth` - bind a credential with: - header name: `x-openclaw-secret` - header value: your generated shared secret ## Import steps 1. In n8n, create or open a workflow. 2. Import `assets/openclaw-action.workflow.json`. 3. Open the **Webhook** node. 4. Set **Authentication** to `Header Auth`. 5. Bind your local credential. 6. Save. 7. Use **Listen for test event** and call the test URL first. 8. Once successful, activate the workflow for production URL use. ## Expected URLs - test: `http://192.168.153.113:18808/webhook-test/openclaw-action` - prod: `http://192.168.153.113:18808/webhook/openclaw-action` ## Test payloads included - `assets/test-append-log.json` - `assets/test-notify.json` - `assets/test-send-notification-draft.json` - `assets/test-send-email-draft.json` - `assets/test-list-email-drafts.json` - `assets/test-delete-email-draft.json` - `assets/test-send-gmail-draft.json` - `assets/test-send-approved-email.json` - `assets/test-create-calendar-event.json` - `assets/test-list-upcoming-events.json` - `assets/test-update-calendar-event.json` - `assets/test-delete-calendar-event.json` - `assets/test-verify-email-draft-cycle.json` - `assets/test-verify-calendar-event-cycle.json` - `assets/test-fetch-and-normalize-url.json` - `assets/test-approval-queue-list.json` - `assets/test-inbound-event-filter.json` ## Example tests ```bash export N8N_WEBHOOK_SECRET='YOUR_SECRET_HERE' scripts/call-action.sh append_log --args '{"text":"backup complete"}' --pretty scripts/call-action.sh get_logs --args '{"limit":5}' --pretty scripts/call-action.sh notify --args '{"title":"Workflow finished","message":"n8n router test"}' --pretty scripts/call-action.sh send_notification_draft --args-file assets/test-send-notification-draft.json --pretty scripts/call-action.sh send_email_draft --args-file assets/test-send-email-draft.json --pretty scripts/call-action.sh list_email_drafts --args-file assets/test-list-email-drafts.json --pretty scripts/call-action.sh delete_email_draft --args-file assets/test-delete-email-draft.json --pretty scripts/call-action.sh send_gmail_draft --args-file assets/test-send-gmail-draft.json --pretty scripts/call-action.sh send_approved_email --args-file assets/test-send-approved-email.json --pretty scripts/call-action.sh create_calendar_event --args-file assets/test-create-calendar-event.json --pretty scripts/call-action.sh list_upcoming_events --args-file assets/test-list-upcoming-events.json --pretty scripts/call-action.sh update_calendar_event --args-file assets/test-update-calendar-event.json --pretty scripts/call-action.sh delete_calendar_event --args-file assets/test-delete-calendar-event.json --pretty scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty scripts/call-action.sh fetch_and_normalize_url --args '{"url":"http://192.168.153.113:18808/healthz"}' --pretty scripts/call-action.sh fetch_and_normalize_url --args '{"url":"https://example.com","skip_ssl_certificate_validation":true}' --pretty scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty scripts/call-action.sh inbound_event_filter --args-file assets/test-inbound-event-filter.json --pretty python3 scripts/resolve-approval-with-gog.py --id --decision approve --dry-run ``` ## Operator command reference Common approval flows: ```bash # 1) inspect the queue with both full and compact views scripts/call-action.sh approval_queue_list --args '{"limit":10,"include_history":true}' --pretty # 2) reject an item without host-side execution scripts/call-action.sh approval_queue_resolve \ --args '{"id":"approval-abc123","decision":"reject","note":"not safe to run"}' \ --pretty # 3) approve through the host bridge, but keep it side-effect free python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve --dry-run # 4) approve for real through the host bridge python3 scripts/resolve-approval-with-gog.py --id approval-abc123 --decision approve # 5) re-check recent history in compact form scripts/call-action.sh approval_queue_list --args '{"limit":5,"include_history":true}' --pretty ``` What to look for in low-noise history output: - `result.pending_compact[]` and `result.history_compact[]` - `summary_line` for a one-line operator digest - `execution_state` for `pending`, `awaiting_host_execution`, `dry_run`, `executed`, or `failed` - `result_refs` for durable IDs such as `draft_id`, `message_id`, or `event_id` ## Canned recurring verification flows ### Gmail draft queue → approve → verify → cleanup 1. Queue the canned payload: ```bash scripts/call-action.sh --args-file assets/test-verify-email-draft-cycle.json --pretty ``` 2. Find the new approval id from `pending_compact`. 3. Approve with the host bridge: ```bash python3 scripts/resolve-approval-with-gog.py --id --decision approve ``` 4. Re-run `approval_queue_list` and confirm the matching history item shows: - `execution_state = "executed"` - `result_refs.draft_id` populated 5. Cleanup by queueing `assets/test-delete-email-draft.json` with the returned draft id and approving that item. ### Calendar event queue → approve → verify → cleanup 1. Queue the canned payload: ```bash scripts/call-action.sh --args-file assets/test-verify-calendar-event-cycle.json --pretty ``` 2. Approve with the host bridge: ```bash python3 scripts/resolve-approval-with-gog.py --id --decision approve ``` 3. Re-run `approval_queue_list` and confirm the matching history item shows: - `execution_state = "executed"` - `result_refs.event_id` populated 4. Cleanup by queueing `assets/test-delete-calendar-event.json` with the returned `event_id` and approving that item. ## Expected success examples ### send_notification_draft ```json { "ok": true, "request_id": "test-notify-draft-001", "result": { "action": "send_notification_draft", "status": "queued_for_approval", "pending_id": "approval-abc123", "approval_status": "pending" } } ``` ### send_email_draft ```json { "ok": true, "request_id": "test-email-draft-001", "result": { "action": "send_email_draft", "status": "queued_for_approval", "pending_id": "approval-abc123", "approval_status": "pending" } } ``` ### list_email_drafts ```json { "ok": true, "request_id": "test-list-email-drafts-001", "result": { "action": "list_email_drafts", "status": "queued_for_approval", "pending_id": "approval-ghi789", "approval_status": "pending", "approval": { "policy": "approval_queue_resolve", "required": true, "mutation_level": "low" } } } ``` ### delete_email_draft ```json { "ok": true, "request_id": "test-delete-email-draft-001", "result": { "action": "delete_email_draft", "status": "queued_for_approval", "pending_id": "approval-jkl012", "approval_status": "pending" } } ``` ### send_gmail_draft / send_approved_email ```json { "ok": true, "request_id": "test-send-gmail-draft-001", "result": { "action": "send_gmail_draft", "requested_action": "send_gmail_draft", "status": "queued_for_approval", "pending_id": "approval-mno345", "approval_status": "pending" } } ``` ### create_calendar_event ```json { "ok": true, "request_id": "test-calendar-event-001", "result": { "action": "create_calendar_event", "status": "queued_for_approval", "pending_id": "approval-def456", "approval_status": "pending" } } ``` ### list_upcoming_events ```json { "ok": true, "request_id": "test-list-calendar-events-001", "result": { "action": "list_upcoming_events", "status": "queued_for_approval", "pending_id": "approval-pqr678", "approval_status": "pending", "approval": { "policy": "approval_queue_resolve", "required": true, "mutation_level": "low" } } } ``` ### update_calendar_event ```json { "ok": true, "request_id": "test-update-calendar-event-001", "result": { "action": "update_calendar_event", "status": "queued_for_approval", "pending_id": "approval-stu901", "approval_status": "pending" } } ``` ### delete_calendar_event ```json { "ok": true, "request_id": "test-delete-calendar-event-001", "result": { "action": "delete_calendar_event", "status": "queued_for_approval", "pending_id": "approval-vwx234", "approval_status": "pending" } } ``` ### fetch_and_normalize_url ```json { "ok": true, "request_id": "test-fetch-001", "result": { "action": "fetch_and_normalize_url", "status": "ok", "url": "http://192.168.153.113:18808/healthz", "title": "", "content_type": "application/json; charset=utf-8" } } ``` ### inbound_event_filter ```json { "ok": true, "request_id": "test-inbound-001", "result": { "action": "inbound_event_filter", "status": "stored", "classification": "urgent", "duplicate": false, "notified": true } } ``` ## Host bridge notes The included host bridge `scripts/resolve-approval-with-gog.py` is for the case where Gmail/Calendar auth exists on the OpenClaw host via `gog`, not inside n8n itself. Behavior: - resolves an approval item through `openclaw-action` - executes supported kinds on the host: - `email_draft` → `gog gmail drafts create` - `email_list_drafts` → `gog gmail drafts list` - `email_draft_delete` → `gog gmail drafts delete` - `email_draft_send` → `gog gmail drafts send` - `calendar_event` → `gog calendar create` - `calendar_list_events` → `gog calendar events` - `calendar_event_update` → `gog calendar update` - `calendar_event_delete` → `gog calendar delete` - writes execution metadata back via `approval_history_attach_execution` Important automation note: - real unattended execution needs `GOG_KEYRING_PASSWORD` available to the executor - the included bridge auto-loads `/home/openclaw/.openclaw/credentials/gog.env` when present - keep that file mode `600` if you use it for `GOG_ACCOUNT` / `GOG_KEYRING_PASSWORD` - without the password, non-TTY `gog` calls will fail when the file keyring tries to prompt - `--dry-run` works without touching Google state and is useful for plumbing verification ## Validation Run the local validator before import/package changes: ```bash python3 scripts/validate-workflow.py assets/openclaw-action.workflow.json ```