Lars Winstand 修正 Google Sheets AI 自動化,不再把它當成 CRM
I fixed my Google Sheets AI automation by stopping it from pretending to be a CRM
Lars Winstand 主張將 Google Sheets 用作 AI 自動化的資料接收與審核介面,而非 CRM、工作流程引擎或客戶生命週期資料庫。他建議讓 LLM 輸出經 Schema 驗證的 JSON,再由 n8n、Zapier 或自建服務負責路由與重試。
I knew the spreadsheet was cooked when somebody added a column named last_followup_status_v2_final.
That was the moment it stopped being a Google Sheet and started becoming a hostage situation.
The original workflow was fine:
- Google Form submits a row
- Google Sheets stores it
- AI cleans up the text
- Slack gets a notification
Totally reasonable.
A month later, it had turned into a fake CRM with 37 columns, three Apps Script triggers, conditional formatting everywhere, and enough hidden logic that nobody wanted to touch it.
The annoying part: the idea was still good.
The implementation was the problem.
The real fix
Use Google Sheets as:
- intake
- review queue
- lightweight ops UI
Do not use Google Sheets as:
- your system of record
- your workflow engine
- your customer lifecycle database
- your AI state machine
That one decision fixes most spreadsheet AI automations.
Google Sheets is not "too small"
I keep hearing this take:
Google Sheets can't handle real workflows.
I don't buy it.
Google Sheets can handle a lot more than people think. Google documents spreadsheet limits up to 20 million cells or 100 MB for spreadsheets created in or converted to Sheets.
For a lot of teams, that's plenty.
The failure mode usually isn't size.
It's architecture.
People start with a simple queue, then keep stuffing more state into the sheet:
- ownership
- retry status
- AI summary
- enrichment fields
- approval state
- follow-up history
- routing decisions
- dedupe markers
Now the sheet isn't a sheet anymore. It's a badly disguised application.
And spreadsheets are terrible places to hide application state.
Where these automations actually break
The breaking point is usually orchestration, not the model.
People blame GPT-5 or Claude when the real problem is this pattern:
- New row arrives
- Apps Script fires
- One API call classifies the text
- Another enriches it
- Another summarizes it
- Another checks duplicates
- Another sends a notification
- Somebody edits a cell
- Trigger fires again
- Now you have retries and partial state everywhere
That is how a "simple sheet automation" becomes flaky.
And Google Apps Script quotas make this worse fast.
The quotas matter more than row count
This is the part people miss.
Your automation can fail long before the spreadsheet gets large.
Apps Script quotas are the real constraint for row-by-row AI workflows:
- URL Fetch calls/day: 20,000 for consumer accounts, 100,000 for Google Workspace
- Trigger runtime/day: 90 minutes for consumer accounts, 6 hours for Workspace
- quotas are per-user
- quotas reset 24 hours after the first request
If every row kicks off multiple external API calls, your practical limit is not rows.
It's calls per row.
That changes how you should design the workflow.
What AI should actually do in Sheets
My opinionated answer:
triage, not everything.
AI is great at the fuzzy front end:
- classify messy text
- normalize names
- extract structured fields
- summarize long responses
- decide where a row should go next
AI is bad at being your spreadsheet-native state manager.
A good pattern looks like this:
- Row lands in Google Sheets
- LLM reads the unstructured text
- LLM returns structured JSON
- Something else routes the result
That "something else" might be:
- n8n
- Zapier
- HubSpot
- Gmail
- Slack
- Zendesk
- your own HTTP service
The sheet should keep:
- raw input
- normalized output
- maybe a review status
It should not run the whole business process.
The pattern that keeps working
A lead intake sheet is the easiest example.
Each row has some ugly real-world input:
- a form response
- a pasted email
- a support request
- a free-text inquiry
The AI step should return a small structured object like:
categoryurgencysummaryrecommended_destinationconfidence
Then your automation layer routes it:
- sales lead -> HubSpot
- urgent support -> Slack or Zendesk
- simple inquiry -> Gmail
- low-confidence case -> manual review queue in Sheets
That works.
Trying to manage the full customer lifecycle in the sheet does not.
The highest-ROI change: force structured output
This is the change that removes half the weirdness.
Stop asking the model to write prose into cells.
Ask for structured output.
If your workflow depends on parsing text like this:
Priority: High
Category: Billing
Summary: User was charged twice and is upset.
...you've already made your automation more fragile than it needs to be.
Use schema-validated JSON instead.
Here's a Node example using the OpenAI SDK with Zod.
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const RowTriage = z.object({
category: z.string(),
priority: z.enum(["low", "medium", "high"]),
summary: z.string(),
confidence: z.number().min(0).max(1),
destination: z.string(),
});
const completion = await client.chat.completions.parse({
model: "gpt-4.1",
messages: [
{
role: "system",
content: "Classify this spreadsheet row and return only structured output.",
},
{
role: "user",
content: "Customer says they were charged twice and need help today.",
},
],
response_format: zodResponseFormat(RowTriage, "row_triage"),
});
console.log(completion.choices[0].message.parsed);
That gives you stable fields you can map straight back into columns.
No regex cleanup.
No splitting strings.
No random extra sentence because the model felt chatty.
If you're using an OpenAI-compatible endpoint
This is also where an OpenAI-compatible API helps.
If your workflow already uses the OpenAI SDK, you can point it at a compatible provider without rewriting the rest of the app.
Example:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.STANDARD_COMPUTE_API_KEY,
baseURL: "https://api.standardcompute.com/v1",
});
That matters for spreadsheet automations because these workflows are usually boring in the worst way:
- lots of repetitive classification
- lots of summaries
- lots of routing decisions
- lots of small calls triggered by forms, rows, and webhooks
This is exactly where per-token billing gets annoying.
If you're running AI triage all day from n8n, Zapier, Make, or custom scripts, predictable flat-rate compute is a lot easier to live with than babysitting token usage.
That's the appeal of Standard Compute: same OpenAI-style API shape, but designed for automations and agents that need to run constantly without cost anxiety.
Sheets vs Apps Script vs n8n
Here's the blunt version.
| Option | Best use |
|---|---|
| Google Sheets | Intake queue, review surface, manual ops UI |
| Apps Script | Small native automations close to Sheets with low volume |
| n8n + OpenAI-compatible API | External orchestration, batching, routing, retries, and AI steps |
If I'm building this today, I want Google Sheets at the edge.
Not in the center.
A sane architecture
This is the setup I'd recommend for most teams.
1. Keep Sheets as the intake layer
Rows come from:
- Google Forms
- manual entry
- CSV imports
- another automation
2. Batch when possible
Don't fire one LLM request for every tiny field if you can avoid it.
Bad:
- classify row
- summarize row
- detect sentiment
- assign destination
Four calls per row adds up fast.
Better:
- one call returns all required fields in one structured object
3. Route outside the sheet
Use n8n, Zapier, Make, or your own worker to handle:
- retries
- branching logic
- notifications
- CRM updates
- help desk tickets
- email responses
4. Keep manual review in the sheet
This is where Sheets is actually great.
Humans can review rows, sort by confidence, fix edge cases, and move on.
Example: support triage flow
Let's make it concrete.
Input columns
message_rawemailsubmitted_at
AI output columns
issue_typesentimentsummaryconfidencedestination
Routing logic
-
billing+urgent-> Slack support channel -
bug-> Jira ticket -
sales-> HubSpot -
confidence < 0.7-> manual review in Sheets - simple question -> Gmail autoresponse
Now the spreadsheet is doing a job it can handle:
- visible queue
- audit trail
- review surface
Jira holds engineering work.
HubSpot holds customer records.
Slack handles escalation.
Gmail handles replies.
That is a much healthier system.
Minimal worker example
Here's a tiny Node script that reads one row payload, sends it to an LLM, and returns structured triage.
import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI({
apiKey: process.env.STANDARD_COMPUTE_API_KEY,
baseURL: "https://api.standardcompute.com/v1",
});
const SupportTriage = z.object({
issue_type: z.enum(["billing", "bug", "feature_request", "sales", "other"]),
sentiment: z.enum(["calm", "frustrated", "urgent"]),
summary: z.string(),
confidence: z.number().min(0).max(1),
destination: z.enum(["slack", "gmail", "jira", "hubspot", "manual_review"]),
});
async function triageMessage(message: string) {
const result = await client.chat.completions.parse({
model: "gpt-4.1",
messages: [
{
role: "system",
content: "Classify support messages for routing. Return only schema-valid JSON.",
},
{
role: "user",
content: message,
},
],
response_format: zodResponseFormat(SupportTriage, "support_triage"),
});
return result.choices[0].message.parsed;
}
const output = await triageMessage(
"I was charged twice this month and need someone to fix it today."
);
console.log(output);
Run it like this:
npm install openai zod
node triage.js
If you want this behind a webhook for n8n or Zapier, wrap it in Express or Fastify and post rows to it.
When Apps Script is still fine
To be fair, Apps Script is still good for small deterministic workflows.
If you have:
- low volume
- one simple action per row
- a small team
- no complicated retries
...then Apps Script is often the fastest thing you can ship.
I'm not anti-Apps Script.
I'm anti pretending it has infinite headroom because the spreadsheet still opens quickly.
The rule I use now
When I look at a Google Sheets AI automation, I ask one question:
Is the sheet helping humans see and steer work, or is it storing the entire state machine of the business?
If it's the second one, the design is already drifting into trouble.
Practical takeaways
If you want the short version, here it is:
- keep Google Sheets as intake and review
- don't use it as your CRM or workflow engine
- reduce API calls per row
- batch work where possible
- force structured JSON output from the model
- route real actions through n8n, Zapier, Make, or your own backend
- use an OpenAI-compatible API so you can swap providers without rewriting your automation
- if you're running AI automations all day, predictable flat-rate compute is way nicer than per-token billing
That last point matters more than people admit.
A lot of spreadsheet AI workflows are not technically hard. They're operationally annoying.
The model call is cheap in isolation, then suddenly you have:
- hundreds of rows
- multiple automations
- retries
- background agents
- cost monitoring nobody wants to do
That's where something like Standard Compute makes sense: same API pattern developers already know, but built for always-on automations instead of making you think about token spend every time a workflow runs.
Final opinion
The best Google Sheets AI automations are boring.
A row comes in.
AI cleans it up.
Structured data comes back.
Another system does the real work.
Humans review exceptions.
That's the whole game.
Not more AI.
Not more columns.
Not more hidden triggers.
Just less pretending a spreadsheet is a database.
來源:DEV Community · dev.to