DEV Community

Cover image for How to Replace an n8n Workflow with Claude Code (Step by Step)
Tariq Osmani
Tariq Osmani

Posted on Originally published at smartaiworkspace.tech

How to Replace an n8n Workflow with Claude Code (Step by Step)

TL;DR

Yes, Claude Code can replace an n8n workflow when the job is mostly custom logic and light on prebuilt connectors. It does not run the workflow for you. It writes ordinary code that you host. Keep n8n or Zapier when non-developers must edit the flow or it leans on many integrations. The step that decides whether a migration is safe is defining what "success" means when something fails.

Can Claude Code replace Zapier and n8n? For some workflows, yes. It fits flows that are mostly custom logic with few connectors and a developer as owner, and it does not fit flows that non-developers edit on a canvas. This post walks the migration in order, using one I did on my own site: a self-hosted n8n webhook replaced by a direct Resend route built with Claude Code.

The tool-level split is in n8n vs Claude Code. The business lesson from this same migration is in the contact form case study, so here I stick to method.

Can Claude Code Replace n8n or Zapier?

It can replace the workflow, not the platform. Claude Code writes code and you run it, so what you give up is the canvas, the prebuilt connectors and the built-in run history.

Factor (as of Nov 2026) Code Claude Code writes n8n Zapier
Who edits it A developer, in a repo Anyone, on a canvas Non-developers, no code
Connectors None built in. MCP servers, or an API client it writes Prebuilt nodes 9,000+ apps
Cost model Your hosting, plus a Claude plan while building Free to self-host (Community Edition). Cloud counts a whole workflow run as one execution: Starter €20/mo for 2.5K, billed annually Per successful task: Pro from $19.99/mo billed annually for 750 tasks, Free 100 tasks

Sources are listed at the end. Prices were checked on 2026-11-01 and change often.

Should This Workflow Move at All?

Move a workflow when the logic is the hard part and the connectors are the easy part. Three questions decide it:

  1. Is it mostly custom logic (validation, branching, scoring) rather than connectors?
  2. Will a developer own it from now on?
  3. Is it one trigger with one or two outputs, so you can test it with real payloads?

Three yes answers means it is a candidate. My contact form was a mixed case. The route's only job was to hand a validated payload to n8n, and it already lived in a Next.js codebase with a developer owner, but the n8n side did a lot more than relay it (see step 1). It was also the part that kept failing. My commit message from 2026-08-31 says it plainly: the self-hosted n8n webhook "went down roughly weekly, silently dropping contact leads."

Step 1: How Do You Document the n8n Workflow First?

Export it, then make Claude Code describe it before it writes anything. In n8n, open the Workflow menu (the three dots) and choose Download to get a JSON file. n8n's docs warn that the export includes credential names and IDs, and that HTTP Request nodes can carry auth headers, so scrub it before sharing.

Ask Claude Code for an inventory, not code: the trigger, each node's inputs and outputs, and every credential it touches. Check the Webhook node's response mode too (respond immediately, when the last node finishes, or via a Respond to Webhook node). Your replacement has to return what the caller already expects. Then pull real payloads from the Executions tab. They become your test data.

The old n8n contact form workflow, titled Lead Qualification Bot. A Webhook node feeds Normalize Lead Data, a Google Sheets duplicate check and an If node, with a Slack alert for duplicates. New leads go to an AI Lead Scorer on GPT-4o-mini, then Parse Failed and Score 7 or higher checks. Hot leads get a Claude Sonnet 4.6 email through Gmail, a Slack message and a Google Sheets row. Lower scores get a nurture auto-reply and a warm sheet row.

The canvas did more than relay a payload. By its own notes it scored each lead from 1 to 10 on budget, intent, urgency and service fit, sent a personalized email to hot leads and a nurture reply to the rest, posted to Slack, skipped duplicates, and logged every lead to Google Sheets. The inventory is where you decide which of those steps are worth keeping.

The replacement route covers the capture and notification half of that canvas, which was the half that kept failing. The scoring, Slack alert, Sheets log and duplicate check are not in it: none of those calls exist anywhere in the codebase today. Claude Code builds what the spec lists and nothing else, so a step you leave out of the spec does not carry over. Decide on purpose which steps you are keeping, and write each one into the spec.

The route I rebuilt validates the submission and rate-limits it, then sends the internal notification and the customer confirmation through Resend. The payload it handed to n8n was a finished one: name, company, email, service, industry and message.

Step 2: What Should the Spec for Claude Code Say?

A spec is the workflow's contract in plain words: inputs, side effects, and what happens on failure. The failure line is the one people skip. Cover five things:

  • The input shape and validation rules.
  • Each side effect and its order.
  • What counts as success, and what the caller sees otherwise.
  • The config it needs, and what happens when that config is missing.
  • What must not change.

The description of PR #3 records the contract my new route shipped with: the same validation and in-memory rate limiter, "unchanged"; one email to the inbox with replyTo set to the submitter and fields HTML-escaped; "Returns success only after Resend accepts the send; 502 on failure"; and a no-op when RESEND_API_KEY is unset, so local development keeps working.

Step 3: How Do You Build and Test It Locally?

Build it as plain code and keep Claude out of the runtime path unless a step needs judgment. My replacement has no model call. Claude Code wrote a route that validates, rate-limits and sends two emails, and it runs like any other route. If your n8n flow has a scoring or triage step, call the Claude API for that step, or run claude -p in a script. Non-interactive runs exit non-zero on failure, and --output-format json with --json-schema returns structured output you can check.

Test with the payloads from step 1, and test the failure cases on purpose: a missing key, a rejected send, two recipients instead of one. Claude Code hooks can run a shell command after tool calls, which is one way to rerun your tests after every edit.

{/* TARIQ INPUT NEEDED: your actual rebuild time in active hours, from first prompt to merged PR. Only add it if you can stand behind the number. */}

The two commits landed 41 minutes apart on 2026-08-31 (14:44 and 15:25, UTC+5). That gap is two pull requests merging, not a measure of how long the work took.

Step 4: How Do You Make "Success" Mean "Captured"?

Return success only after the thing the caller cares about has happened, and decide in advance which later steps may fail. This is the step that makes a migration safe, and the one I got wrong in the original route.

Two-lane diagram of the contact route. Before: validate, then if the webhook environment variable is set forward to n8n, then return success, with a dashed bypass that skips the forward and still returns success when the variable is unset. After: validate, send the internal notification, return a 502 if it fails, then send a best-effort customer confirmation and return success.

The old route, trimmed:

const webhookUrl = process.env.N8N_CONTACT_WEBHOOK_URL;
if (webhookUrl) {
  const webhookRes = await fetch(webhookUrl, { /* validated fields */ });
  if (!webhookRes.ok) {
    return NextResponse.json({ error: "Failed to submit. Please try again." }, { status: 502 });
  }
}
return NextResponse.json({ success: true }); // also reached when the forward was skipped
Enter fullscreen mode Exit fullscreen mode

If that variable ended up unset, the forward was skipped and the visitor still saw a thank-you. The new route, trimmed:

const { error } = await resend.emails.send({ from, to: inbox, replyTo: data.email, subject, text, html });
if (error) {
  console.error("Resend error:", error);
  return NextResponse.json({ error: "Failed to submit. Please try again." }, { status: 502 });
}

// Best-effort: log it, never fail the request over a courtesy email
try {
  const { error: confirmError } = await resend.emails.send({ from, to: data.email, /* ... */ });
  if (confirmError) console.error("Confirmation email error:", confirmError);
} catch (err) {
  console.error("Confirmation email error:", err);
}

return NextResponse.json({ success: true });
Enter fullscreen mode Exit fullscreen mode

The internal notification is the critical path, so its failure fails the request. The customer confirmation runs after the lead is already captured, so its failure is only logged. The same pattern works for any workflow: rank your steps, and let everything before the capture fail loudly.

I kept one shortcut. With no RESEND_API_KEY the route returns success without sending, which is convenient offline and is also how the original bug worked. If you copy the pattern, make production refuse to run without the key.

PR #4 fixed a second bug that reading the diff would not have caught. CONTACT_INBOX holds two addresses, comma-separated. Resend's to accepts a string or an array of strings, and the comma-joined string was one malformed address, so the second recipient silently received nothing:

// CONTACT_INBOX is a comma-separated list; Resend's `to` needs a string array.
const inbox = process.env
  .CONTACT_INBOX!.split(",")
  .map((s) => s.trim())
  .filter(Boolean);
Enter fullscreen mode Exit fullscreen mode

A migrated flow needs a test with two recipients, not one.

Step 5: How Do You Cut Over Without Losing Events?

Run the replacement on real traffic before you delete the old path, and leave the old webhook published until the new one has handled real events. n8n registers a production webhook when you publish the workflow, so the fallback stays live until you unpublish it. For a form, you can send to both for a week and compare what arrives.

One problem that showed up on mine was not in the code at all.

What Should You Monitor After the Cutover?

Monitor the delivery you cannot see in the code, and test the full path yourself. The customer confirmation first landed in Gmail spam. The cause was a new domain with no sending reputation, not broken records: the domain was verified in Resend with DKIM, SPF and Return-Path records, and they aligned. There was no instant fix. I moved the sender from a noreply@ address to a real tariq@ alias that receives replies, and added a reporting address to the DMARC record, starting at p=none so I could tighten it later.

After any deploy that touches intake, submit your own form on production. Check that the notification arrived in an inbox a person reads, that the reply-to is the submitter, and that the confirmation is not in spam.

Neither problem on this page, the missing second recipient and the spam-foldered confirmation, was visible in the code or in a success response. The form said thank you both times. That is the practical lesson of the whole migration: a migrated workflow is finished when a real submission reaches a real inbox, not when the tests pass.

When Should You Not Migrate an n8n Workflow?

Leave a workflow in n8n when the reasons to move do not apply to it:

  • Non-developers need to edit it. A canvas is easier to hand over than a repository.
  • It leans on many prebuilt connectors. Claude Code has no app catalog. It connects through MCP servers, which you should only add from sources you trust, or writes the API client itself. Rebuilding each one costs more than it saves.
  • You rely on per-node retries and reloadable runs. n8n has Retry On Fail and Continue on error per node, plus an error workflow, and lets you load a past execution's data into the editor to debug.
  • It runs more than hourly. Claude Code's cloud scheduled tasks are a research preview, reject schedules more frequent than once per hour, and Anthropic says behavior and limits may change. Can Claude Code run on a schedule covers the options.

Licensing is rarely the deciding factor. n8n's Sustainable Use License covers use for your own internal business purposes.

How Smart AI Workspace Approaches This

I decide per workflow, before anything is built, using the three questions above. New client builds are Claude-based agents and agentic workflows, designed with the failure path first. A connector-heavy flow that a client's team edits stays on its platform. If a workflow is worth moving, I build the replacement, run it against real payloads, and test what a user sees when a step fails. My notes on agentic workflows with Claude Code cover the wider design, and custom AI agents is what I build when the logic needs judgment.

Next Step

If you have an n8n workflow you are unsure about, send it to me and I will tell you whether it should move and why. You can also see what I build or reach me on Upwork.


Sources, all opened 2026-11-01: Webhook node (n8n docs) · n8n pricing · Export and import workflows (n8n docs) · Error handling (n8n docs) · Node settings (n8n docs) · n8n Sustainable Use License · Zapier pricing · Run Claude Code programmatically (Anthropic docs) · Scheduled tasks (Anthropic docs) · Hooks (Anthropic docs) · MCP in Claude Code (Anthropic docs) · Send email API (Resend docs)


More from Smart AI Workspace

Top comments (1)

Collapse
 
livinus_okoz_4661e8c7bb69 profile image
Livinus Okoz • • Edited

Great step-by-step breakdown, Tariq!

Replacing visual canvas workflows with pure code via Claude Code is definitely appealing; especially for eliminating container overhead, version-controlling logic directly in git, and bypassing GUI node limits.

However, after migrating multiple production setups between pure code and n8n, there are three trade-offs teams usually run into that are worth keeping in mind:

Execution Observability & Replays: In n8n, if a webhook payload fails at 3:00 AM on run #2,400, you have the exact input/output JSON payload in execution history with one-click retry. In pure script setups generated by Claude Code, you have to build custom structured logging, Sentry error-tracking, and state persistence to match that visibility.
OAuth2 & Credential Lifecycles: n8n handles token refreshes, encrypted credential storage, and expired session handshakes out-of-the-box. Managing automated refresh token loops across 4–5 different SaaS APIs in standalone scripts can become a maintenance headache over time.
Non-Technical Team Handover: In a team setting, operations or sales reps can visually check which branch a lead took on an n8n canvas; they can't easily debug or maintain Python/TypeScript CLI scripts.
The sweet spot I've found recently is hybrid: using Claude Code to write complex custom logic inside n8n Code Nodes or generate custom n8n community nodes, while letting n8n handle the routing, retries, and credential management.

Are you running these Claude Code scripts as scheduled cron jobs/serverless functions, or daemonizing them with PM2/Docker in production?