Skip to content
RoutineOS
Esc
↑↓navigate↵open⌘Jpreview
On this page

Move an n8n workflow to a routine

Map a scheduled automation workflow onto a RoutineOS routine. Trigger to schedule, nodes to stages, the sheet to the store, the send to a gate, and what the Desk adds. Worked through for Hiring Signals.

Many teams already run a scheduled job as an n8n (or Zapier or Make) workflow: fetch something, ask a model about it, filter, append to a sheet, send an email. That is the same shape as a watch routine. This guide maps one onto the other, and says what changes on the way.

It is not an importer. RoutineOS reads no n8n export. You rebuild the workflow as a routine folder, using the workflow as the spec.

Why move it

What a routine adds over a workflow of the same shape:

In the workflow In the routine
A row per item in a sheet, appended each run One record per item, keyed by an identity rule, with every sighting kept
A filter node that silently drops items Settings filters that keep dropped items with a reason people can see and overrule
Settings edited inside the workflow Settings on the Desk, with a cost note each
A send node that fires every run A gate: the send waits for a named person’s approval
No memory of what people did Learning: people’s keeps and drops become suggested setting changes
One builder edits it People use the Desk; the builder owns the definition

What it costs: a routine is a scheduled Claude session, not a fixed graph. Model steps can vary from run to run, which is why RoutineOS puts identity, filters, counting and writes in code. See Stages.

The mapping

Workflow part Routine part Where
Schedule trigger schedule, five-field UTC cron, at least one hour apart routine.toml and app.toml [[routine]]
Webhook or app-event trigger No direct equivalent in a watch routine. The scheduled tasks docs list API and GitHub triggers; see Scheduling
HTTP request or scraper node A model stage that searches or fetches, or a code stage for a fixed API prompt.md, lib/
AI node A model stage prompt.md
IF or filter node A list setting with a role (include or exclude), bound to a field in settingFilters routine.toml, definition.json
Set or transform node Record fields in the definition definition.json
Sheet or database append Records in the store, written by routineos.py write [store] adapter = "artifact-db"
Email or chat send node An [[action]] of kind send behind a [gate], and a write [[integration]] with gate = true routine.toml, app.toml
Credentials A connector, or a network secret for one host. Never a key in the prompt or files app.toml [[integration]], [[secret]]
Error workflow state/health, the run record, and the budget alert Prompt header, [budget] alert_channel

Worked example: Hiring Signals

Suppose Hiring Signals runs today as this workflow (illustrative; your node names will differ):

  1. Schedule trigger, Monday and Thursday at 22:00 UTC.
  2. One HTTP request per job board and market.
  3. An AI node that extracts title, company, market and URL, and says why a posting matters.
  4. A filter node: keep titles containing Shopify, headless, commerce or AI automation.
  5. A sheet node: append each kept posting as a row.
  6. An email node: send the best match to the sales lead.

Rebuild it from the template that already has this shape:

python3 skills/routineos/scripts/routineos.py new hiring-signals --from-template jobs-watch --app
created hiring-signals

Now map each node.

1. Schedule. The template’s routine.toml already has it:

[routine]
schedule = "0 22 * * 1,4"  # UTC, Monday and Thursday; minimum interval is 1 hour
first_run = "report-only"

first_run = "report-only" has no workflow equivalent. The routine does nothing outside the store until a person has checked a run.

2 and 3. Fetch and extract. These become model stages. The jobs-watch pack (since 0.6.0) names them search and record. The prompt tells the run what to search and which fields to return. The fields are declared once, in definition.json, for the role type (title, company, company_domain, market, location, remote, salary_max, posted, url, seniority, summary) and the company type.

4. Filter. The filter node’s keyword list becomes a setting:

[[setting]]
name = "title_keywords"
type = "list"
role = "include"
label = "Title keywords"
default = ["Shopify", "headless", "commerce", "AI automation"]
cost_note = "each keyword adds a search per market"

and the definition binds it to the field:

"settingFilters": [{"setting": "title_keywords", "field": "title"}]

The difference: the workflow threw non-matching rows away. The routine writes them with status: "dropped" and dropReason: "not in title_keywords". Matching is a case-insensitive substring, so commerce matches “Ecommerce Developer”.

5. Sheet. Rows become records. The role’s identity is field:url, so the same posting seen on Thursday updates Monday’s record and adds a sighting, instead of adding a second row. Companies use identity domain, and each role links to its company.

6. Email. The send moves behind a gate:

[gate]
enabled = true
transport = "email"
timeout_hours = 48

The run drafts the note as an Inbox message of kind approval, emails the approver a code, and stops. A later run reads the reply. Only approve <code> from the named approver counts. Declare the mail connector in app.toml as a write integration with gate = true; see Build your first routine.

As shipped, the 48-hour gate is shorter than the 72 and 96 hour gaps between Monday and Thursday runs, so a gate expires before the next run reads it. Raise timeout_hours above 96 before switching gated messages on. See Notification channels.

What you gain on the Desk. The sales lead sees new roles on Today, browses dropped ones under “Dropped by settings”, keeps the ones the keywords missed, and approves or rejects intro notes. After enough keeps, learning suggests the missing keyword. See Learning.

A second shape: a weekly report

n8n’s public template “AI marketing report (GA, Google Ads, Meta Ads)” (n8n.io/workflows/2783) pulls seven days of marketing data on a schedule, has a model write a report, and sends it by email or Telegram, per its listing (fetched 2026-10-30). As a routine:

  • Schedule: weekly cron.
  • Fetch: connectors for the ad and analytics accounts, if they exist on your claude.ai account. Check the connectors directory; none is assumed here.
  • Records: one report record per week, identity on the week, so history accumulates.
  • Send: the report email is a notice to the team, not an action on someone else’s behalf. Recommendations that change spend go behind a gate.

There is no template for this today. A weekly-marketing-report pack is a candidate; see Templates.

Common pitfalls

  • Porting the trigger as-is. A webhook that fires per event has no equivalent in a scheduled watch routine. Batch it.
  • Copying the filter as a prompt instruction. Put it in a setting with a role, so code applies it and people can see and change it.
  • Keeping the credential. An API key from the workflow does not go into the routine. Use a connector, or a network secret scoped to one host.
  • Sending on the first run. First runs are report-only. Sends need a gate and a promotion step with its own permission screen.
  • A gate shorter than the schedule gap. The gate is read only on the next run.

Was this page helpful?