agentclaw

Articles

Six Agency Automation Workflows You Can Copy, With the JSON

Sophie Adams and Jason Lee · Aug 10, 2026 · 23 min read

Cover card reading: six agency automation workflows you can copy, with the JSON, over the agentclaw claw mark.

TL;DR

  • The same twelve-step workflow, run once a day for forty clients, bills as 13,200 Zapier tasks, 14,400 Make credits, or 1,200 n8n executions. The metering model decides the bill, not the template.
  • Three published API limits break a copied workflow before its logic does: Google Sheets at 60 writes a minute per user, Slack at roughly one message a second per channel, and Airtable at five requests a second per base with a 30 second lockout after a 429.
  • All six workflows here share the same seven parts. The three that template galleries leave out are the client resolver, the idempotency guard and the exception queue.
  • n8n's public library lists 11,190 community templates, and almost none of them assume you will run the same workflow for forty different clients from one account.
  • 31 percent of the 494 agency professionals AgencyAnalytics surveyed in 2026 run more than fifty clients, which is the volume at which a single-tenant template stops being a template.

There is a version of this post that lists forty links to other people's template galleries and calls itself a resource. This is not that. These are six workflows we end up rebuilding for almost every agency we work with, written out with the config, the schemas and the node JSON, plus the specific thing that breaks in each one once you are running it for more than a handful of clients. Copy them. They are more useful to you than they are to us.

Why free template galleries stop working around client twenty

Because almost every published template is single-tenant, and an agency is not.

n8n's public library lists 11,190 workflow templates. Make and Zapier both run libraries in the thousands. The quality is genuinely fine. The problem is structural: a template is written to do a thing once, for whoever imported it, with credentials hard-wired and the destination spreadsheet named in the node. Import it and it works. Import it forty times, once per client, and you now maintain forty copies of the same logic, each drifting from the others every time somebody fixes a bug in one and forgets the rest. Which platform you are cloning into changes how much that hurts, and we compared n8n and Zapier on exactly that.

That is not a hypothetical volume. In the 2026 AgencyAnalytics benchmark report, which surveyed 494 agency professionals between February and April 2026, 18 percent of agencies said they run 51 to 100 clients and another 13 percent run more than 100. Nearly a third of the field is past the point where forty copies of a workflow is a reasonable thing to own. Meanwhile 49 percent of those agencies have ten or fewer full-time staff, so there is nobody whose job is to keep forty copies in sync.

So the useful unit is not a template. It is a workflow that takes the client as an input.

How many clients an agency is actually running

Almost a third of agencies carry more than fifty clients, which is where a per-client copy of a workflow stops being maintainable.

1 to 5 clients

6%

6 to 15

25%

16 to 25

20%

26 to 50

17%

51 to 100

18%

101 or more

13%

n = 494 agency professionals, surveyed February to April 2026. 64 percent United States, 13 percent Australia, 8 percent United Kingdom, 7 percent Canada.

Source: AgencyAnalytics 2026 Marketing Agency Benchmarks (2026)

The seven parts every workflow here has

Every one of the six below is the same skeleton with different work in the middle. Learn the skeleton once and the six files stop looking like six things.

  1. A trigger. A form, a webhook, a schedule, a status change.
  2. A client resolver. The first real step is always turning whatever the trigger gave you into a client_id, then loading that client's configuration. Nothing downstream is allowed to name a client, a folder, or a spreadsheet directly.
  3. An idempotency guard. A key derived from the inputs, checked against a log of keys already processed. Without it, a retry sends the report twice.
  4. The work. The part that differs between the six.
  5. A verification step. A check on the output before anybody sees it, not after.
  6. Delivery.
  7. An exception queue. Anything that fails a check, or returns a 429 or a 5xx, lands in one place with enough context to be replayed.

Parts two, three and seven are the ones missing from nearly every published template, and they are the three that decide whether your copy is still running in March.

Flow diagram of the shared workflow skeleton: a trigger resolves a client_id, an idempotency guard stops a repeat run, the work runs, a verification check routes output either to delivery and a run log or to an exception queue, and any 429 or 5xx also goes to the exception queue.
The dotted path is the one templates omit. A 429 has to go somewhere you can replay from, or the run fails silently and you find out when the client asks where their report is.
Show the data behind this diagram
  • Trigger fires (form, webhook, schedule or status change).
  • Resolve client_id and load that client's configuration.
  • Idempotency guard: if the key has been seen, stop, the run already happened.
  • Otherwise do the work.
  • Verification check on the output.
  • Pass: deliver, then log the run and the idempotency key.
  • Fail: write to the exception queue.
  • Any 429 or 5xx during the work also writes to the exception queue.

How to read the files below

Four conventions run through all six, so they are written down once here rather than repeated in every file.

Clients live in a registry, not in nodes. One record per client, loaded by the resolver, referenced by every step after it. Adding a client is a row, not a workflow.

{
  "client_id": "acme-north",
  "display_name": "Acme North",
  "status": "active",
  "timezone": "America/New_York",
  "owner_email": "ops@youragency.example",
  "drive_root": "0ABCdEfGhIjKlMnOpQ",
  "crm_record_id": "cr_8812",
  "slack_channel_id": "C01ABCDEF",
  "connected_platforms": ["ga4", "google_ads", "meta_ads"],
  "report_day": "monday",
  "sla_hours": 48
}

Secrets are never in the file. Every credential is a platform credential reference. If you paste an API key into a workflow you are about to share, you have created a different problem than the one you were solving.

Every run carries an idempotency key. Derived from the inputs, not from a timestamp. For a monthly report that is client_id + period. For an intake it is the form submission id. The guard is boring and it is the single highest-value node in the file.

// n8n Code node, runs immediately after the client resolver
const key = `${$json.client_id}:${$json.period}`;
const seen = await $getWorkflowStaticData('global');
seen.processed = seen.processed || {};
if (seen.processed[key]) {
  return [];            // already ran, stop here
}
seen.processed[key] = new Date().toISOString();
return [{ json: { ...$json, idempotency_key: key } }];

That version keeps state in n8n's own static data, which is fine to start and wrong by about client thirty. Move processed to a real table when you notice.

Every file below is a starting point with the interesting part left in. The parts that are specific to you are marked. The parts that are specific to nobody are already written.

1. Client intake to a working workspace

This is the one to build first, because it runs at the moment a client is most alert to whether you are organized.

The form answers become a client record, a folder tree, a CRM entry, a kickoff brief and a channel, in that order, with one human step at the end. What makes it worth automating is not the time saved on any single onboarding. It is that every client after this one gets the identical folder structure, which is what makes workflow two possible at all.

{
  "name": "Intake to workspace",
  "nodes": [
    { "name": "Intake form", "type": "n8n-nodes-base.formTrigger", "typeVersion": 2,
      "position": [0, 0],
      "parameters": { "formTitle": "New client intake", "responseMode": "lastNode" } },
    { "name": "Create client record", "type": "n8n-nodes-base.airtable", "typeVersion": 2,
      "position": [220, 0],
      "parameters": { "operation": "upsert", "matchingColumns": ["client_id"] } },
    { "name": "Folder tree", "type": "n8n-nodes-base.googleDrive", "typeVersion": 3,
      "position": [440, 0],
      "parameters": { "operation": "folder", "folderId": "={{ $json.drive_root }}" } },
    { "name": "Kickoff brief", "type": "n8n-nodes-base.chainLlm", "typeVersion": 1,
      "position": [660, 0],
      "parameters": { "promptType": "define", "text": "={{ $json.intake_answers }}" } },
    { "name": "Brief passes checks", "type": "n8n-nodes-base.if", "typeVersion": 2,
      "position": [880, 0] },
    { "name": "Post to channel", "type": "n8n-nodes-base.slack", "typeVersion": 2.2,
      "position": [1100, -80] },
    { "name": "Exception queue", "type": "n8n-nodes-base.airtable", "typeVersion": 2,
      "position": [1100, 120],
      "parameters": { "operation": "create" } }
  ],
  "connections": {
    "Intake form":         { "main": [[{ "node": "Create client record", "type": "main", "index": 0 }]] },
    "Create client record":{ "main": [[{ "node": "Folder tree", "type": "main", "index": 0 }]] },
    "Folder tree":         { "main": [[{ "node": "Kickoff brief", "type": "main", "index": 0 }]] },
    "Kickoff brief":       { "main": [[{ "node": "Brief passes checks", "type": "main", "index": 0 }]] },
    "Brief passes checks": { "main": [
        [{ "node": "Post to channel", "type": "main", "index": 0 }],
        [{ "node": "Exception queue", "type": "main", "index": 0 }]
    ] }
  }
}

What breaks first. The folder-tree step. Drive folder creation is not atomic across a tree, so a partial failure halfway through leaves a client with three of five folders and a workflow that thinks it succeeded. Make the folder step idempotent by name lookup before create, and have it return the full expected tree so the check at the end can compare against it rather than trusting the last node's exit code.

What to change. The folder names, the CRM fields, and the brief prompt. Everything else is structure. If you want the longer version of what to automate first in a studio, we wrote that up separately in intake, briefs and review loops for a content studio.

2. The weekly reporting run, fanned out across every client

This is the workflow that pays for itself, and it is also the one that reliably explodes the first time it runs for the whole client list at once.

The shape: on a schedule, read the client registry, then for each active client pull each connected platform, normalize everything into one row shape, write it, generate the commentary, and route it for a human look before it goes out. The normalization is the part worth stealing. If every platform's numbers land in one shape, adding a seventh platform later is a mapper rather than a rewrite.

{
  "metric_row": {
    "client_id": "acme-north",
    "period_start": "2026-08-01",
    "period_end": "2026-08-07",
    "source": "google_ads",
    "metric": "cost",
    "value": 4820.55,
    "currency": "USD",
    "pulled_at": "2026-08-08T06:02:11Z",
    "idempotency_key": "acme-north:2026-W32:google_ads:cost"
  }
}

The fan-out itself needs pacing, and pacing is the thing every gallery template omits because a single-tenant workflow never needs it.

// n8n Code node: batch the client list so the run does not stampede
const BATCH = 5;          // clients processed per wave
const clients = $input.all().filter(i => i.json.status === 'active');
const waves = [];
for (let i = 0; i < clients.length; i += BATCH) {
  waves.push({
    json: {
      wave: i / BATCH,
      delay_seconds: (i / BATCH) * 12,   // spread waves across the minute
      clients: clients.slice(i, i + BATCH).map(c => c.json)
    }
  });
}
return waves;

What breaks first. Rate limits, every time, and not the ones you expect. The pull side is usually fine. The write side is what falls over, because writing forty clients' rows into one workbook is forty writes in a few seconds from a single service account.

What to change. BATCH and delay_seconds, against the numbers in the next section. The rest holds. There is a fuller treatment of the reporting and QA side in our writeup on automating a paid media agency.

Three cards showing published API limits: Google Sheets at 60 write requests per minute per user per project and 300 per project, Slack chat.postMessage at roughly one message per second per channel, and Airtable at five requests per second per base with a 30 second wait after a 429.
Set your batch size against the tightest ceiling in the run, not the loosest. In most agency reporting workflows that is Airtable, and the 30 second penalty is what turns one 429 into a failed run.Sources: Google Sheets API usage limits, 2026; Slack Web API rate limits, 2026; Airtable Web API rate limits, 2026
Show the data behind this infographic
ServicePublished limitWhat happens over it
Google Sheets API60 read and 60 write requests per minute per user per project; 300 per minute per project429 Too many requests; Google recommends truncated exponential backoff with a maximum backoff of 32 to 64 seconds
Slack chat.postMessageRoughly one message per second per channel, plus a workspace-wide limit429 with a Retry-After header giving the seconds to wait
Airtable Web API5 requests per second per base; 50 per second per personal access token429, after which no request succeeds for 30 seconds

3. Sourcing and screening, with the rubric in a file

Recruiting and staffing agencies run this one hardest, but any agency hiring for itself gets the same benefit.

A candidate arrives, gets parsed into structured fields, gets scored against the rubric for that specific role, and gets routed by band. The reason to put the rubric in a config file rather than a prompt is that a file can be diffed, reviewed and pointed at when somebody asks why a candidate was screened out. A prompt buried in a node cannot.

{
  "rubric_id": "senior-paid-media-2026-08",
  "role": "Senior Paid Media Manager",
  "must_have": [
    { "id": "platform_depth", "test": "Hands-on management of Google Ads or Meta Ads budgets above 50k USD per month" },
    { "id": "agency_context", "test": "Has carried more than one client account concurrently" }
  ],
  "scored": [
    { "id": "measurement", "weight": 3, "test": "Evidence of owning conversion tracking or attribution setup" },
    { "id": "writing", "weight": 2, "test": "Evidence of client-facing written communication" },
    { "id": "tooling", "weight": 1, "test": "Named specific reporting or automation tooling" }
  ],
  "bands": [
    { "min": 12, "route": "screen_call" },
    { "min": 7,  "route": "human_review" },
    { "min": 0,  "route": "hold" }
  ],
  "never_auto_reject": true,
  "require_evidence_span": true
}

Two flags at the bottom are doing more work than the rest of the file. never_auto_reject means the low band routes to a queue a person clears, not to a rejection email. require_evidence_span means every score has to come back with the text it was derived from, so a reviewer checks a claim in seconds instead of rereading the CV.

What breaks first. Rubric drift. Somebody edits the scoring for one role, the bands no longer mean what they meant, and last month's shortlist is not comparable to this month's. Version the rubric in the id, as above, and store the rubric_id on every score you write.

What to change. All of it, honestly. The structure is the transferable part. More on the surrounding workflow in sourcing, screening and scheduling for a recruiting agency.

4. The review loop that does not lose track of round three

Most agencies do not have a review problem. They have a review-state problem: nobody can say without asking whether a given deliverable is with the writer, the reviewer or the client, and how many times it has been round.

So model it as a state machine and let the workflow own the transitions.

{
  "states": ["drafting", "auto_check", "internal_review", "revising", "client_review", "approved"],
  "initial": "drafting",
  "transitions": [
    { "from": "drafting",        "on": "submit",        "to": "auto_check" },
    { "from": "auto_check",      "on": "pass",          "to": "internal_review" },
    { "from": "auto_check",      "on": "fail",          "to": "revising", "notify": "author" },
    { "from": "internal_review", "on": "changes",       "to": "revising", "increment": "round" },
    { "from": "internal_review", "on": "approve",       "to": "client_review" },
    { "from": "revising",        "on": "submit",        "to": "auto_check" },
    { "from": "client_review",   "on": "changes",       "to": "revising", "increment": "round" },
    { "from": "client_review",   "on": "approve",       "to": "approved" }
  ],
  "escalate_when": { "round": 3, "to": "account_lead" },
  "stale_after_hours": 48
}

escalate_when is the line that changes behavior. Round three is not a quality problem, it is a brief problem, and surfacing it to the account lead automatically is how it stops being invisible. stale_after_hours catches the other failure, which is a deliverable sitting in client_review for nine days while everyone assumes somebody else is chasing.

What breaks first. People acting outside the machine. Someone approves in a thread instead of moving the state, and the workflow's picture of reality quietly diverges from the real one. The fix is not more automation. It is making the state change the same action as the approval, so there is no way to do one without the other.

What to change. The state names, to match what your team already says out loud. Do not make people learn new words for stages they already have names for.

5. The QA gate that runs before anything reaches a client

This one is short and it catches an embarrassing amount.

Every client-facing artifact passes a registry of checks before delivery. Each check is a small function with a stable id, a severity, and a decision about whether failing it blocks the send. Keeping them in a registry rather than hard-coded in each workflow means adding a check applies it everywhere at once.

{
  "checks": [
    { "id": "links_resolve",     "severity": "block", "applies_to": ["report", "deck", "post"] },
    { "id": "numbers_traceable", "severity": "block", "applies_to": ["report", "deck"],
      "note": "Every figure in the narrative must match a row in the metric table for the same period" },
    { "id": "client_name_match",  "severity": "block", "applies_to": ["report", "deck", "post", "email"],
      "note": "Catches the copy-paste from the previous client. This one earns its place on its own." },
    { "id": "pii_scan",           "severity": "block", "applies_to": ["report", "deck", "post", "email"] },
    { "id": "period_current",     "severity": "block", "applies_to": ["report"] },
    { "id": "tone_house_style",   "severity": "warn",  "applies_to": ["post", "email"] }
  ],
  "on_block": "exception_queue",
  "on_warn": "annotate_and_continue"
}

What breaks first. Nothing, which is the problem. A gate with no failures for a month gets described as noise and switched off, and then client_name_match is not there on the day it was going to matter. Log passes as well as failures so the gate can show what it caught.

What to change. Add checks, do not remove them. The two worth adding first are specific to whatever your team has actually shipped wrong before.

6. The exception queue, which is the file nobody publishes

Search any template gallery for an exception-handling workflow and you will find very little, because it is unglamorous and it does not demo well. It is also the reason the other five are still running six months later.

Every failed check, every 429, every 5xx, every timeout writes one record here. The schema matters more than the storage: the record has to carry enough to replay the work without a human reconstructing what happened.

{
  "exception_id": "exc_01J8ZK",
  "occurred_at": "2026-08-08T06:04:41Z",
  "workflow": "weekly_reporting",
  "client_id": "acme-north",
  "idempotency_key": "acme-north:2026-W32:google_ads:cost",
  "stage": "write_metrics",
  "class": "rate_limit",
  "http_status": 429,
  "retry_after_seconds": 30,
  "attempt": 2,
  "replayable": true,
  "payload_ref": "s3://runs/2026-W32/acme-north/google_ads.json",
  "resolution": null
}

Four fields do the real work. idempotency_key is what makes a replay safe rather than a second send. class is what lets you count failures by kind instead of staring at a list. replayable is an honest flag for the cases where it is not, such as a form submission whose upstream data is gone. payload_ref points at the inputs, because a replay without the original inputs is a rerun and a rerun gets different numbers.

What breaks first. The queue fills up and nobody looks at it. Give it an owner and a weekly number, or it becomes a landfill with good schema design.

What to change. payload_ref storage, to wherever you already keep run artifacts. Everything else in the schema earns its place the first time you have to replay a week.

What running all six actually costs in platform fees

Here is the part that decides more than the templates do, and almost nobody works it out before committing.

Take one of these workflows at twelve steps, run once a day, for forty clients. That is 1,200 runs a month. What you get billed for depends entirely on what the platform counts as a unit.

Zapier counts a task each time it successfully completes an action step, and states plainly that triggers and polling do not consume tasks. Eleven actions after the trigger, times 1,200 runs, is 13,200 tasks a month. Zapier's Professional tier starts at 750 tasks for 19.99 dollars a month billed annually, so this workload sits several tiers up.

Make counts a credit per module action, and its pricing page is explicit that reading data from an app or a webhook counts too, while routers and error handlers are free. All twelve modules, times 1,200 runs, is 14,400 credits. The Core plan is 12 dollars a month for 10,000, so again, up a tier.

n8n counts an execution as one run of the whole workflow. Its documentation is blunt about it: the number of steps and the amount of data do not change the count. Twelve steps or fifty, 1,200 runs is 1,200 executions, which fits inside the Starter plan's 2,500 at 20 euros a month. There is also a free self-hosted community edition, which changes the arithmetic again in exchange for you owning the uptime.

None of this makes one platform correct. It makes the metering model a design input. A workflow with many small steps is expensive per-step and cheap per-run, and if you are on a per-step meter the sensible move is to collapse steps into code nodes, which is exactly the opposite of what you would do for readability.

Grouped bar chart of metered units consumed by one twelve-step workflow run daily for forty clients against the units included in each platform's entry paid plan: Zapier 13,200 tasks against 750 included, Make 14,400 credits against 10,000, n8n 1,200 executions against 2,500.
Same workflow, same client list, same schedule. The only variable is what the platform decided to count, and it is the difference between fitting inside an entry plan and not.Sources: Zapier pricing, 2026; Make pricing, 2026; n8n pricing, 2026
Show the data behind this chart
PlatformUnitConsumed per month by this workloadIncluded in the entry paid plan
ZapierTask, per successful action step13,200 tasks750 tasks on Professional, from $19.99/month billed annually
MakeCredit, per module action14,400 credits10,000 credits on Core, from $12/month
n8nExecution, per whole workflow run1,200 executions2,500 executions on Starter, from EUR 20/month

Which platform each of these files lands on best

What one unit is

Zapier
A successful action step
Make
A module action
n8n
One run of the whole workflow

This workload, per month

Zapier
13,200 tasks
Make
14,400 credits
n8n
1,200 executions

Entry paid plan

Zapier
750 tasks, from $19.99/mo annual
Make
10,000 credits, from $12/mo
n8n
2,500 executions, from EUR 20/mo

Fastest polling on entry paid tier

Zapier
2 minutes on Professional
Make
Scheduling is per scenario
n8n
Scheduling is per workflow

Self-host option

Zapier
No
Make
No
n8n
Yes, free community edition

Best fit among these six

Zapier
Intake, where step count is low and volume is per-client
Make
The QA gate, where routers and error handlers cost nothing
n8n
The reporting fan-out, where one run touches many steps

Prices and allowances from each vendor's published pricing page, read August 2026. The consumption figures are arithmetic from their stated billing models applied to one twelve-step workflow run daily for forty clients, not measured usage.

When to stop copying and build something

Copying is the right move far longer than most vendors will tell you. The honest markers that you have outgrown it:

The registry has outgrown the tool. When per-client configuration stops fitting in a row and starts needing conditional logic per client, you are writing a program in a spreadsheet.

The exception queue has a pattern. One kind of failure making up most of the queue for a month is a design problem, and no amount of retry tuning fixes a design problem.

A step needs judgment rather than rules. Screening on stated criteria is rules. Reading a messy brief and working out what the client actually asked for is not, and that is where an agent earns its place instead of a branch.

You are paying more in platform fees than the work costs. It happens, and it happens quietly. Run the arithmetic above against your real client count before you renew.

If you get to that point and want it built rather than assembled, that is the work we do. A one-off starter build runs 1,500 to 2,500 dollars fixed, a two-week production sprint is 5,000 dollars fixed, and retainers start at 5,000 dollars a month. The whole ladder is on our pricing page, and the first two of those are deliberately cheap enough that you should not need to hire an agency automation consultant on a retainer to find out whether this works for you.

The questions we get about these files

Can I just import these and run them?+

The n8n workflow in section one imports and runs after you attach credentials and set the folder ids. The rest are config and schemas rather than executable workflows, deliberately, because the executable part of a reporting or screening workflow is mostly the connections to your specific stack. The structure is the transferable half, and it is the half that takes the longest to get right.

Do these work in Zapier or Make instead of n8n?+

Yes, with a caveat about cost rather than capability. All three platforms can express every workflow here. The difference is metering: this workload bills as 13,200 tasks on Zapier and 14,400 credits on Make, against 1,200 executions on n8n, because Zapier and Make count steps while n8n counts runs. On a per-step meter, collapse the small steps into code nodes before you import.

What is the single most important part to copy if I only take one thing?+

The idempotency guard. It is about eight lines and it is the difference between a retry being safe and a client receiving two invoices, two reports, or two rejection emails. Everything else on this page is an improvement to a workflow. That one is the thing that stops a workflow from doing damage when it fails.

How do I decide the batch size for the reporting fan-out?+

Set it against the tightest published limit in the run and leave headroom. Airtable allows five requests per second per base and then refuses everything for 30 seconds after a 429, Google Sheets allows 60 writes per minute per user per project, and Slack allows roughly one message per second per channel. Count the requests one client generates, divide the tightest ceiling by that, then halve it, because your run is not the only thing using those credentials.

Why is there no template for the exception queue in any gallery?+

Because it does not demo. A gallery template has to show a result in a screenshot, and an exception queue's best day is an empty one. It is also the piece most tied to how a specific team works, since the schema is easy and the operating habit of actually clearing the queue is the hard part.

Is it cheaper to self-host?+

In platform fees, usually yes. n8n publishes a free self-hosted community edition, and for a workload that fits comfortably on one small server the license cost goes to zero. What you take on instead is uptime, upgrades, backups, and being the person who gets paged when a scheduled run does not fire. That is a real cost, it is just not on an invoice. If nobody on your team wants that pager, the hosted tier is the cheaper option once you price the hour.

How much of this should an agency automation consultant be doing for us versus handing over?+

Handing over. If the workflows, the registry and the credentials do not end up in accounts you own, you have bought a dependency rather than an automation. Every file on this page is written to be readable by somebody who did not build it, which is the same standard to hold an outside builder to.

Copy the files. Call us if the exception queue fills up.

If you take these and they run, that is a good outcome and it cost you nothing. If you get three months in and the queue has a pattern nobody can clear, that is the point where an outside pair of hands is worth paying for.

A one-off starter build is $1,500 to $2,500 fixed. A two-week production sprint is $5,000 fixed.

Share thison Xon LinkedIn

Written by

Sophie Adams · Technical Writer

I turn complex AI concepts into step-by-step guides readers can follow as they work.

Journaling

Written by

Jason Lee · AI Documentation Specialist

I write AI product documentation that tells people what to do next without making the product harder than it is.

Building side projects

Book audit