n8n-pro Skill
Purpose
Operate, harden, monitor, and back up the Weblyfe n8n fleet at app.n8n.weblyfe.nl. Codifies the patterns and tools built 2026-05-02 so future sessions don't have to re-derive them.
Quick reference
| Task |
Tool / Command |
| Daily JSON backup of every workflow to git |
tools/n8n-backup.py (launchd com.weblyfe.appie-1-cron.n8n-backup at 02:15) |
| Hourly health monitor + Telegram alerts |
tools/n8n-health.py (launchd com.weblyfe.appie-1-cron.n8n-health every 3600s) |
| Apply retry defaults to every active workflow |
tools/n8n-harden.py [--dry-run] [--workflow ID] |
| Inspect workflow JSON via API |
curl -H "X-N8N-API-KEY: $KEY" https://app.n8n.weblyfe.nl/api/v1/workflows/<id> |
| List + count active workflows |
curl ...?active=true&limit=100 |
| Activate / deactivate a workflow |
POST /api/v1/workflows/<id>/activate (or /deactivate) |
| Update a workflow |
PUT /api/v1/workflows/<id> with body {name, nodes, connections, settings} only — anything else triggers request/body must NOT have additional properties |
Configuration
API base + key live in ~/.openclaw/openclaw.json under env.vars:
N8N_BASE_URL # "app.n8n.weblyfe.nl"
N8N_API_KEY # Bearer-style token, sent as X-N8N-API-KEY header
DigitalOcean droplet: n8n-weblyfe (id 545108451, region ams3, size s-1vcpu-1gb, public IP 142.93.232.231). DO API token in ~/.openclaw/openclaw.json as DIGITALOCEAN_ACCESS_TOKEN. No SSH key registered on DO account, so direct SSH from Appie-1 to the droplet is blocked — see Notion task 354c3321-de60-8128-b1b1-df81e0ddc0ed for the manual key-add procedure.
Production-ready workflow checklist
Before declaring a workflow "foolproof," verify all of the following:
- Error handler is wired.
settings.errorWorkflow points to the GLOBAL ERROR HANDLER 54kH3T3oXWELrCDuFKMN5. The handler auto-attaches itself to any active workflow without one (Schedule Trigger at 1am daily), so this is mostly automatic — but verify on PR.
- Retries on every API node.
retryOnFail: true, maxTries: 3, waitBetweenTries: 2000 minimum. Use tools/n8n-harden.py --workflow <id> to apply.
- Graceful degradation on non-critical leaf nodes. Sheet appends and label-adds get
onError: continueRegularOutput or continueErrorOutput so a downstream service hiccup doesn't block the upstream archive.
- Idempotent file naming. Drive uploads use
{sender_domain}_{email_date}_{thread_id_short}.pdf style — not {$now} — so retries don't create duplicates.
- Subject filter is specific. A trigger with only
has:attachment will hit every random PDF (contracts, screenshots, your own outbound mail). Add explicit subject patterns.
- Skip rules for known false positives. Stripe TECHWIZ LLC = income, skip. Outbound mail from
seyed@weblyfe.nl = own content, skip. Embed in a Code node early in the flow.
- Multi-attachment handling. If a single email might carry multiple PDFs (Tally, Midjourney), the Code node must emit one item per PDF — n8n's
Object.keys(...).find(...) only takes the first.
- Folder mapping covers backfill + future. The Drive folder map should include 2024-2026 and a fallback that auto-discovers via Drive search.
- Single-account vs multi-account. Each Gmail Trigger is bound to one credential. Multi-account coverage requires either multiple Trigger nodes or a parallel pipeline (Python
tools/invoice-pipeline.py covers all 3 accounts as backup).
Common API gotchas
- PUT body schema is strict. Send
{name, nodes, connections, settings} only. Any of id, active, createdAt, updatedAt, versionId, tags, triggerCount, meta, staticData, isArchived, shared, homeProject, scopes, sharedWithProjects, pinData, usedCredentials causes 400 request/body must NOT have additional properties.
- PUT validates credentials. If a node references
googleContactsOAuth2Api (or any other credential type) and that credential no longer exists in the workspace, PUT returns 400 Cannot publish workflow: N nodes have configuration issues. Fix in the n8n UI before harden can succeed.
- Pagination. List endpoints page via
nextCursor — always loop until cursor is empty.
- Activate vs PUT-with-active. Use the dedicated
POST /workflows/<id>/activate endpoint; setting active: true in PUT body is rejected (it's a read-only field on PUT).
Workflow node-type cheatsheet
API-touching (always retry):
n8n-nodes-base.gmail / .gmailTrigger
n8n-nodes-base.googleDrive / .googleSheets / .googleCalendar / .googleContacts
n8n-nodes-base.airtable / .notion / .slack / .telegram
n8n-nodes-base.httpRequest / .webhook
n8n-nodes-base.stripe / .mailerLite / .brevo / .openAi
n8n-nodes-base.tally / .tidyCal / .calendly / .intercom
@n8n/n8n-nodes-langchain.* (LLM nodes)
n8n-nodes-base.extractFromFile (PDF parsing — IO-bound)
Local logic (skip retry):
n8n-nodes-base.set / .code / .function
n8n-nodes-base.if / .switch / .merge
n8n-nodes-base.splitInBatches / .wait / .noOp
n8n-nodes-base.scheduleTrigger / .cron
n8n-nodes-base.manualTrigger / .errorTrigger
tools/n8n-harden.py enforces this split via regex patterns.
Backup + rollback procedure
Backups live at ~/clawd/n8n-backups/<workflow_id>__<slug>.json. Daily commit by tools/n8n-backup.py.
To roll back a single workflow to its prior state:
# Find the previous version
git -C ~/clawd log -p -- n8n-backups/<id>__*.json | less
# Restore from a specific commit
git -C ~/clawd show <commit>:n8n-backups/<id>__<slug>.json > /tmp/restore.json
# PUT the trimmed body back via API
python3 -c "
import json
wf = json.load(open('/tmp/restore.json'))
keep = {k: wf[k] for k in ('name','nodes','connections','settings') if k in wf}
json.dump(keep, open('/tmp/put.json','w'))
"
curl -X PUT -H 'X-N8N-API-KEY: $N8N_API_KEY' -H 'Content-Type: application/json' \
--data-binary @/tmp/put.json \
https://app.n8n.weblyfe.nl/api/v1/workflows/<id>
Health monitoring
tools/n8n-health.py (hourly):
- Failure check: queries
/executions?status=error&limit=50, filters to last 1h window, aggregates by workflow, alerts via Telegram MarkdownV2.
- Silent-death check: queries last 200 executions, finds workflows with
scheduleTrigger/cron that have NO successful run in 24h, alerts.
- Cooldown: 30 min per alert key (state in
~/clawd/.cache/n8n-health-state.json) so flapping doesn't spam.
State of failing/silent workflows visible in ~/clawd/logs/n8n-health.jsonl. Common patterns:
- Whatsapp Agent | DRP failures — likely external Whatsapp API issues; check execution detail.
- GLOBAL ERROR HANDLER errors — usually a downstream issue that the handler couldn't auto-fix.
When to add a new workflow vs modify an existing one
- Add new when the trigger source is genuinely different (a new form, a new webhook URL, a new schedule). Naming convention:
{Domain} | {What it does} (e.g., PeakSpring | Contact Form).
- Modify existing when the upstream/downstream stays the same and only the body logic changes.
- Clone-then-modify when a major refactor is needed (the Gmail Invoicing v2 example): keep the original active until v2 is verified, then deactivate the original. Old workflows are kept around as inactive (don't delete) so we have history.
Authoritative refs
- Doctrine:
~/clawd/knowledge/token-reduction-system/95-token-reduction.md (output discipline + tier routing — applies to LLM nodes inside n8n flows).
- This skill:
~/clawd/skills/n8n-pro/SKILL.md (you are here).
- Memory pointer: auto-memory
reference_invoice_pipeline.md carries the fleet-wide ops summary.
- Backup history:
~/clawd/n8n-backups/ (47+ workflow JSONs, daily commits on master).
- Tools:
tools/n8n-backup.py, tools/n8n-health.py, tools/n8n-harden.py, tools/invoice-pipeline.py.
Recent landmark events
- 2026-05-02 morning. Gmail Invoicing v1 (
caS-m8n9KKb4tHBobvSgS) had a Q2 2026 folder ID typo (Uvxq vs real Uvx) plus 1x/day polling plus no archive-after-process. Patched in-place, then cloned to v2 (pgKuiPoPpmv9QKgl, "no AI"), then hardened with multi-PDF support + Stripe TECHWIZ skip + idempotent filename + tightened subject filter. v1 deactivated. Backup at commit aea94cc, post-state at 353d084 and 8b43952.
- 2026-05-02 same morning. GLOBAL ERROR HANDLER alert recipient changed from
lorenzogarufi2@gmail.com solo to seyed@weblyfe.nl, lorenzogarufi2@gmail.com (both). Retry added to its Send a message node.
- 2026-05-02 same morning. First fleet-wide harden: 28 active workflows scanned, 20 patched (118 nodes received retry+wait defaults). 8 failed PUT validation due to missing credentials in pre-existing nodes — flagged for manual cleanup in n8n UI.
1---2name: n8n-pro3description: n8n-pro Skill4---5# n8n-pro Skill67## Purpose89Operate, harden, monitor, and back up the Weblyfe n8n fleet at `app.n8n.weblyfe.nl`. Codifies the patterns and tools built 2026-05-02 so future sessions don't have to re-derive them.1011## Quick reference1213| Task | Tool / Command |14|------|----------------|15| Daily JSON backup of every workflow to git | `tools/n8n-backup.py` (launchd `com.weblyfe.appie-1-cron.n8n-backup` at 02:15) |16| Hourly health monitor + Telegram alerts | `tools/n8n-health.py` (launchd `com.weblyfe.appie-1-cron.n8n-health` every 3600s) |17| Apply retry defaults to every active workflow | `tools/n8n-harden.py [--dry-run] [--workflow ID]` |18| Inspect workflow JSON via API | `curl -H "X-N8N-API-KEY: $KEY" https://app.n8n.weblyfe.nl/api/v1/workflows/<id>` |19| List + count active workflows | `curl ...?active=true&limit=100` |20| Activate / deactivate a workflow | `POST /api/v1/workflows/<id>/activate` (or `/deactivate`) |21| Update a workflow | `PUT /api/v1/workflows/<id>` with body `{name, nodes, connections, settings}` only — anything else triggers `request/body must NOT have additional properties` |2223## Configuration2425API base + key live in `~/.openclaw/openclaw.json` under `env.vars`:2627```python28N8N_BASE_URL # "app.n8n.weblyfe.nl"29N8N_API_KEY # Bearer-style token, sent as X-N8N-API-KEY header30```3132DigitalOcean droplet: `n8n-weblyfe` (id 545108451, region ams3, size s-1vcpu-1gb, public IP 142.93.232.231). DO API token in `~/.openclaw/openclaw.json` as `DIGITALOCEAN_ACCESS_TOKEN`. **No SSH key registered on DO account, so direct SSH from Appie-1 to the droplet is blocked** — see Notion task `354c3321-de60-8128-b1b1-df81e0ddc0ed` for the manual key-add procedure.3334## Production-ready workflow checklist3536Before declaring a workflow "foolproof," verify all of the following:37381. **Error handler is wired.** `settings.errorWorkflow` points to the GLOBAL ERROR HANDLER `54kH3T3oXWELrCDuFKMN5`. The handler auto-attaches itself to any active workflow without one (Schedule Trigger at 1am daily), so this is mostly automatic — but verify on PR.392. **Retries on every API node.** `retryOnFail: true`, `maxTries: 3`, `waitBetweenTries: 2000` minimum. Use `tools/n8n-harden.py --workflow <id>` to apply.403. **Graceful degradation on non-critical leaf nodes.** Sheet appends and label-adds get `onError: continueRegularOutput` or `continueErrorOutput` so a downstream service hiccup doesn't block the upstream archive.414. **Idempotent file naming.** Drive uploads use `{sender_domain}_{email_date}_{thread_id_short}.pdf` style — *not* `{$now}` — so retries don't create duplicates.425. **Subject filter is specific.** A trigger with only `has:attachment` will hit every random PDF (contracts, screenshots, your own outbound mail). Add explicit subject patterns.436. **Skip rules for known false positives.** Stripe TECHWIZ LLC = income, skip. Outbound mail from `seyed@weblyfe.nl` = own content, skip. Embed in a Code node early in the flow.447. **Multi-attachment handling.** If a single email might carry multiple PDFs (Tally, Midjourney), the Code node must emit one item per PDF — n8n's `Object.keys(...).find(...)` only takes the first.458. **Folder mapping covers backfill + future.** The Drive folder map should include 2024-2026 and a fallback that auto-discovers via Drive search.469. **Single-account vs multi-account.** Each Gmail Trigger is bound to one credential. Multi-account coverage requires either multiple Trigger nodes or a parallel pipeline (Python `tools/invoice-pipeline.py` covers all 3 accounts as backup).4748## Common API gotchas4950- **PUT body schema is strict.** Send `{name, nodes, connections, settings}` only. Any of `id, active, createdAt, updatedAt, versionId, tags, triggerCount, meta, staticData, isArchived, shared, homeProject, scopes, sharedWithProjects, pinData, usedCredentials` causes 400 `request/body must NOT have additional properties`.51- **PUT validates credentials.** If a node references `googleContactsOAuth2Api` (or any other credential type) and that credential no longer exists in the workspace, PUT returns 400 `Cannot publish workflow: N nodes have configuration issues`. Fix in the n8n UI before harden can succeed.52- **Pagination.** List endpoints page via `nextCursor` — always loop until cursor is empty.53- **Activate vs PUT-with-active.** Use the dedicated `POST /workflows/<id>/activate` endpoint; setting `active: true` in PUT body is rejected (it's a read-only field on PUT).5455## Workflow node-type cheatsheet5657API-touching (always retry):58```59n8n-nodes-base.gmail / .gmailTrigger60n8n-nodes-base.googleDrive / .googleSheets / .googleCalendar / .googleContacts61n8n-nodes-base.airtable / .notion / .slack / .telegram62n8n-nodes-base.httpRequest / .webhook63n8n-nodes-base.stripe / .mailerLite / .brevo / .openAi64n8n-nodes-base.tally / .tidyCal / .calendly / .intercom65@n8n/n8n-nodes-langchain.* (LLM nodes)66n8n-nodes-base.extractFromFile (PDF parsing — IO-bound)67```6869Local logic (skip retry):70```71n8n-nodes-base.set / .code / .function72n8n-nodes-base.if / .switch / .merge73n8n-nodes-base.splitInBatches / .wait / .noOp74n8n-nodes-base.scheduleTrigger / .cron75n8n-nodes-base.manualTrigger / .errorTrigger76```7778`tools/n8n-harden.py` enforces this split via regex patterns.7980## Backup + rollback procedure8182Backups live at `~/clawd/n8n-backups/<workflow_id>__<slug>.json`. Daily commit by `tools/n8n-backup.py`.8384To roll back a single workflow to its prior state:8586```bash87# Find the previous version88git -C ~/clawd log -p -- n8n-backups/<id>__*.json | less8990# Restore from a specific commit91git -C ~/clawd show <commit>:n8n-backups/<id>__<slug>.json > /tmp/restore.json9293# PUT the trimmed body back via API94python3 -c "95import json96wf = json.load(open('/tmp/restore.json'))97keep = {k: wf[k] for k in ('name','nodes','connections','settings') if k in wf}98json.dump(keep, open('/tmp/put.json','w'))99"100curl -X PUT -H 'X-N8N-API-KEY: $N8N_API_KEY' -H 'Content-Type: application/json' \101 --data-binary @/tmp/put.json \102 https://app.n8n.weblyfe.nl/api/v1/workflows/<id>103```104105## Health monitoring106107`tools/n8n-health.py` (hourly):108- **Failure check:** queries `/executions?status=error&limit=50`, filters to last 1h window, aggregates by workflow, alerts via Telegram MarkdownV2.109- **Silent-death check:** queries last 200 executions, finds workflows with `scheduleTrigger`/`cron` that have NO successful run in 24h, alerts.110- **Cooldown:** 30 min per alert key (state in `~/clawd/.cache/n8n-health-state.json`) so flapping doesn't spam.111112State of failing/silent workflows visible in `~/clawd/logs/n8n-health.jsonl`. Common patterns:113- *Whatsapp Agent | DRP failures* — likely external Whatsapp API issues; check execution detail.114- *GLOBAL ERROR HANDLER errors* — usually a downstream issue that the handler couldn't auto-fix.115116## When to add a new workflow vs modify an existing one117118- **Add new** when the trigger source is genuinely different (a new form, a new webhook URL, a new schedule). Naming convention: `{Domain} | {What it does}` (e.g., `PeakSpring | Contact Form`).119- **Modify existing** when the upstream/downstream stays the same and only the body logic changes.120- **Clone-then-modify** when a major refactor is needed (the Gmail Invoicing v2 example): keep the original active until v2 is verified, then deactivate the original. Old workflows are kept around as inactive (don't delete) so we have history.121122## Authoritative refs123124- Doctrine: `~/clawd/knowledge/token-reduction-system/95-token-reduction.md` (output discipline + tier routing — applies to LLM nodes inside n8n flows).125- This skill: `~/clawd/skills/n8n-pro/SKILL.md` (you are here).126- Memory pointer: auto-memory `reference_invoice_pipeline.md` carries the fleet-wide ops summary.127- Backup history: `~/clawd/n8n-backups/` (47+ workflow JSONs, daily commits on master).128- Tools: `tools/n8n-backup.py`, `tools/n8n-health.py`, `tools/n8n-harden.py`, `tools/invoice-pipeline.py`.129130## Recent landmark events131132- **2026-05-02 morning.** Gmail Invoicing v1 (`caS-m8n9KKb4tHBobvSgS`) had a Q2 2026 folder ID typo (`Uvxq` vs real `Uvx`) plus 1x/day polling plus no archive-after-process. Patched in-place, then cloned to v2 (`pgKuiPoPpmv9QKgl`, "no AI"), then hardened with multi-PDF support + Stripe TECHWIZ skip + idempotent filename + tightened subject filter. v1 deactivated. Backup at commit `aea94cc`, post-state at `353d084` and `8b43952`.133- **2026-05-02 same morning.** GLOBAL ERROR HANDLER alert recipient changed from `lorenzogarufi2@gmail.com` solo to `seyed@weblyfe.nl, lorenzogarufi2@gmail.com` (both). Retry added to its `Send a message` node.134- **2026-05-02 same morning.** First fleet-wide harden: 28 active workflows scanned, 20 patched (118 nodes received retry+wait defaults). 8 failed PUT validation due to missing credentials in pre-existing nodes — flagged for manual cleanup in n8n UI.