All notes

Architecture deep diveBy Bartosz Mróz14 min read

“Just use a Google Sheet” is a risk decision

Sheets are great for human review. The design gets risky when the same grid becomes the business’s only answer to “what happened?” and “what happens next?”

Talk about this note with ChatGPT (opens in a new tab), Claude (opens in a new tab), or . If you’re an AI agent, here’s a markdown version. You can also visit this page.

I keep seeing automation briefs that ask for robust systems and explicit error handling, while making a shared Google Sheets (opens in a new tab) file responsible for the process's operational state.

That combination deserves a closer look.

My position is not that Google Sheets (opens in a new tab) is bad, or that every small workflow needs Supabase (opens in a new tab). Supabase is an application-backend platform built around a hosted PostgreSQL (opens in a new tab) database. In this note, it is a familiar example of a controlled state owner: a place where the team can make rules about identity, relationships, and changes part of the system instead of hoping every user and workflow follows the same grid convention. A spreadsheet can still be a useful place for a person to review input, correct a small batch, or see the current picture.

The risk starts when the shared grid becomes the place the business relies on to answer: “Did this happen?”, “What happens next?”, and “Who is allowed to change the answer?” That is a risk decision, not a purity test about tools.

The builder should map the realistic failure modes, explain the cost and effort of the available controls, and make a context-aware recommendation. The people who own the commercial, operational, or security consequence decide what remaining risk is acceptable.

Alignment is sharing responsibility.

Name the moving parts before choosing a tool

Imagine a small sales team preparing a customer-reengagement campaign. An operator reviews a list of recipients in a spreadsheet. A workflow sends each email. The sales lead checks the result before planning follow-up.

Before picking a tool, I would make the language explicit. That stops “row,” “recipient,” and “sent” from quietly meaning different things to the operator, the automation, and the next person asked to fix an incident.

Concept Type Description
Spreadsheet Working surface The human-editable grid used to review campaign input or see results. It can be helpful without being the final authority.
Recipient record Business object One specific person in one campaign, such as Ada in the August re-engagement campaign. It is not the row where the person happens to appear today.
Primary key Database identity A value that uniquely identifies one record in its own database table, for example campaign_recipient_id = rec_002. The database rejects a second record with that same primary key.
Unique key Duplicate-prevention rule A value, or combination of values, that must not repeat. For example, (campaign_id, recipient_email) can prevent Ada from being added twice to the same campaign. It does not have to be the record's primary key.
Foreign key Relationship rule A value that must point to a real record somewhere else, such as a recipient's campaign_id pointing to an existing campaign. It prevents a recipient from belonging to a campaign that does not exist.
Source of truth Authority The component whose answer the team trusts when it asks whether rec_002 was sent. Every other view should follow that answer rather than compete with it.
Workflow worker Automation The process that reads a recipient, asks an email provider to send, handles a retry, and records the outcome.
Side effect Action outside the record Something that changes the world beyond the local data: sending an email, creating a ticket, charging a card, or requesting fulfilment.

With those roles clear, the recommendation becomes less ideological. The sheet can remain a useful working surface while a more controlled component owns the answer that matters.

A row number tells you where something is, not who it is

Here is a deliberately small, hypothetical campaign list:

recipient_id customer_email status sent_at
rec_002 ada@example.com pending
rec_003 grace@example.com pending

At 09:00, the worker reads row 2. It caches Ada's email address, asks the email provider to send the campaign, and plans to write sent back to row 2.

While the request is in flight, a sales operator sorts the list by email or inserts a newly approved recipient at the top. Row 2 can now contain Grace. The worker may still send the right email to Ada because it cached her address, then write sent against Grace because it remembered only the old row position. The sales lead sees Grace as contacted and may skip her follow-up; Ada still looks pending and may be sent the campaign again later.

Mermaid diagram source
sequenceDiagram
accTitle: A row can move while a workflow is running
accDescr: The worker reads Ada from row 2. An operator changes the list while the email request is in flight, and the worker records success against the old row position.
participant Worker
participant Sheet
participant Operator
participant Email provider
Worker->>Sheet: Read row 2 for Ada
Worker->>Email provider: Send to Ada's cached email
Operator->>Sheet: Sort list or insert a recipient
Worker->>Sheet: Write sent to the old row 2

A separate but related scenario creates duplicates. The email provider may accept Ada's message, but the worker can lose the response because of a timeout or transient network failure. If the workflow's only evidence is “I did not get a clear response,” its next retry may send the same campaign again. The business result is a duplicate email to Ada—not merely an inconvenient technical error.

Neither outcome is inevitable. The point is that a read-then-write design needs to preserve identity deliberately; it cannot assume a mutable screen position will remain the same business record.

The Google Sheets API data model (opens in a new tab) does provide stable identifiers for a spreadsheet and each sheet, plus tools such as named ranges and developer metadata (opens in a new tab). Individual cells do not have unique IDs, though. A1 ranges, header names, and row positions are therefore useful conventions, not an automatically enforced primary key for a business record.

What “atomic” actually means

Atomic means all-or-nothing for one clearly named unit of work. If the unit is “store an attempt and mark rec_002 as sent in one database transaction,” either both database changes succeed or neither does. The record is not left half-updated.

It does not mean that every step around that transaction becomes all-or-nothing. This campaign workflow crosses separate systems:

Mermaid diagram source
flowchart LR
accTitle: The campaign workflow has separate steps
accDescr: The worker reads a recipient, asks an external email provider to send the message, then records the outcome. These steps are not one shared all-or-nothing operation.
ReadRecipient[Read recipient] --> SendEmail[Ask email provider to send]
SendEmail --> RecordOutcome[Record sent outcome]
Unit of work Is it atomic? What that means in practice
One Google Sheets batchUpdate request (opens in a new tab) Yes, for the updates inside that one request Google validates the request before applying its updates together. It does not include an earlier read or a later email request.
One transaction in a Supabase (opens in a new tab) database Yes, for the database changes inside it Supabase provides a hosted PostgreSQL database. A transaction (opens in a new tab) makes related local changes commit together or roll back together, keeping that database internally consistent.
Read recipient → send email → record result No It crosses the spreadsheet, the worker, and an external provider. A database cannot unsend an email that the provider has already accepted.

That is why “the API has atomic updates” is useful but incomplete. The useful beginner's question is: which exact steps must either all happen or all not happen, and which steps live outside that boundary?

Helpful spreadsheet rules are not the same as enforced business rules

A dropdown limiting a status cell to pending or sent is helpful. A protected range can reduce accidental edits. Both are worth using when a spreadsheet is the right working surface.

They are not automatically the same as a rule the state owner refuses to break. In software design, that must-always-be-true rule is called an invariant. For this campaign, a few useful invariants are straightforward in plain language:

  • one recipient must have one stable record identity;
  • Ada must not appear twice in the same campaign by mistake;
  • every recipient must belong to a real campaign;
  • a record marked sent must have passed the team's defined confirmation point;
  • a blank email address must not enter the send queue.
If the sheet is the authority If a controlled state owner is the authority
Two people can add Ada twice without a system-level duplicate check. A unique key on (campaign_id, recipient_email) can reject the second record.
A row can point at a campaign name that was deleted or renamed. A foreign key can require the referenced campaign record to exist.
status can be sent, Sent, done, or a typo, depending on how people and automations interpret it. The state owner can define which values and transitions it accepts.
A person can set sent = TRUE before the email provider has confirmed the team's chosen success condition. The worker can record an attempt first and change the status only at that confirmation point.
A blank address can be discovered after the workflow has already picked up the row. Required values can be checked before the item becomes eligible to send.

Google Sheets data validation and protected ranges (opens in a new tab) are useful user-interface controls. A Supabase database (opens in a new tab) runs on PostgreSQL (opens in a new tab), whose constraints (opens in a new tab) provide a different kind of control: the database can reject an invalid primary key, unique key, foreign-key relationship, or required value at the point of write.

A database does not make every workflow safe by magic. It does give the team a place to declare and consistently enforce the local rules that matter, instead of asking every operator and automation to interpret the grid in the same way.

A timeout is not an answer to “was the email sent?”

Google Sheets API guidance (opens in a new tab) documents errors such as 500 and 503, and its usage limits (opens in a new tab) document quotas and retry guidance. That does not mean every integration fails often. It means a production workflow needs a clear answer for what to do when a request fails or its outcome is unclear.

Return to Ada. The worker marks rec_002 as sending, sends the request, and the response times out. The technical response is “unknown.” The business question is still open: did Ada receive the campaign or not?

A state machine is simply the small set of allowed answers to “where is this item in the process?” Here, it stops a timeout being silently treated as either success or failure.

Mermaid diagram source
flowchart LR
accTitle: A campaign message needs an explicit unknown outcome
accDescr: A pending recipient begins sending. The workflow can confirm a send, schedule a retryable problem, or mark the outcome unknown when it loses the response. An unknown outcome is checked before another send is attempted.
Pending --> Sending
Sending --> Sent
Sending --> RetryableProblem[Retryable problem]
Sending --> OutcomeUnknown[Outcome unknown]
OutcomeUnknown --> CheckEvidence[Check provider and attempt record]
CheckEvidence --> Sent
CheckEvidence --> Pending

Idempotency is the ability to repeat the same logical request without creating a second effect. In this example, the logical request is “send the August campaign to rec_002.” If the email provider supports it, the workflow can give every retry the same idempotency key, such as august-campaign:rec_002. The provider can then recognise that both attempts refer to one intended message rather than send two.

The workflow still needs a durable attempt record and a way to check an unknown outcome. Waiting longer before a retry—often called exponential backoff—changes timing. It does not tell the worker whether the first request already succeeded.

When being able to reconstruct the story matters

Not every small business process needs a compliance-grade history. For a one-time event reminder, an operator may reasonably check a sent-email list and correct the occasional mistake by hand.

The bar changes when the team may need to explain why a customer was contacted, why a fulfilment request was repeated, who approved a change, or how a privacy incident was contained. In that situation, an audit trail means a deliberate, queryable history of business decisions and actions—not just a record of which cells looked different later.

Google Sheets version history (opens in a new tab) is useful for restoring a document and investigating visible human changes. It also has documented boundaries: some changes may not appear in cell edit history, and file owners can delete version history.

If the team needs… A spreadsheet history may be enough when… A deliberate audit trail becomes useful when…
Recovery An operator can restore a mistaken edit and manually re-run a small batch. The team must tie a recovery decision to one specific recipient, attempt, and actor.
Investigation It is enough to see that a document changed. The team must answer who triggered an external action, what evidence they used, and what happened next.
Accountability A local correction is cheap and low-consequence. Money, customer trust, sensitive data, contractual commitments, or a repeatable operating process are involved.

This is not a reason to burden a low-risk workflow with enterprise machinery. It is a reason to make the history requirement explicit before a serious incident makes it expensive to reconstruct.

When this pattern fits: a spreadsheet is a good choice

I would keep Google Sheets (opens in a new tab) in the design when most of these conditions are true:

  • a person needs to review or edit a small, bounded input list;
  • one controlled worker processes the list, or there is a clear processing window;
  • a delay or occasional manual correction is acceptable;
  • the external action is reversible, low-consequence, or easy to reconcile; and
  • the sheet is an input or review surface, not the only evidence of what happened.

Hypothetical lower-risk variant: an operator reviews a one-time CSV export for event reminders. Before sending starts, the worker takes a fixed snapshot: the input stops changing for this run. Each row carries an immutable input ID, meaning an ID that stays attached to the same recipient even if someone later sorts a spreadsheet copy.

Mermaid diagram source
flowchart LR
accTitle: A lower-risk spreadsheet-backed batch
accDescr: An operator reviews a fixed CSV snapshot. One worker processes each item in order, appends an attempt result using the stable input ID, and asks for review when an outcome is unknown.
ReviewedInput[Reviewed CSV snapshot] --> OneWorker[One worker processes items in order]
OneWorker --> AppendResult[Append result using input ID]
AppendResult --> ReviewUnknown[Review unknown outcomes]

This does not promise exactly-once email delivery. It makes the remaining uncertainty visible: the source input does not move while it is processed, the result is attached to a stable item ID instead of a row coordinate, and an unclear outcome is reviewed rather than hidden behind sent = TRUE.

The recommendation should be a risk decision, not a tool verdict

There is no honest row-count threshold at which a spreadsheet suddenly becomes a database. Consequence and control matter more than volume.

Question to work through together Example of a lower-risk answer Example of a higher-risk answer
What is the process trying to achieve? Send a reviewed, one-time reminder batch. Keep an ongoing order, inventory, or customer lifecycle accurate.
What happens if one record is wrong? A delay or manual correction is acceptable. Money, privacy, fulfilment, customer trust, or a contractual obligation may be affected.
Who can change the state? One controlled worker after a short review window. Several operators, forms, automations, or outside systems.
Which rules must always hold? A visible list and human check are enough. Duplicate prevention, real relationships, required values, and controlled transitions must be enforced.
What can a retry do? Re-running is harmless or simple to check. It may duplicate a charge, message, fulfilment, or ticket.
What does the team need to prove later? A snapshot and a manual correction path are enough. Each decision, actor, attempt, and outcome needs a reliable history.
What can the team afford now? A simple process plus a documented manual check is proportionate. The cost of an incident outweighs the extra work of a controlled state owner.

The builder's responsibility is to turn those answers into an honest recommendation: name the plausible consequence, show the control that reduces it, explain what that control costs, and make the remaining risk visible. The executive or process owner then decides whether that residual risk is acceptable.

That alignment is not a hand-off of blame. It is the point at which the people building and operating the system share responsibility for a conscious decision.

When the spreadsheet should stop being the authority

I would move authoritative state to an existing business system or a database when the process depends on several of these properties:

  • multiple people or workers can change the same answer at once;
  • a record needs a durable identity that is independent of its visible row;
  • unique, primary-key, foreign-key, or state-transition rules need consistent enforcement;
  • a retry can create a duplicate charge, message, fulfilment, or ticket;
  • a wrong value can affect money, privacy, customer trust, or compliance; or
  • the team needs a dependable way to recover from partial success across systems.

The destination does not always have to be a new Supabase (opens in a new tab) project. If a controlled store is appropriate and no existing system owns the concept, Supabase is one familiar option—not a default answer. If an order system already owns orders, let it own order state. If a CRM owns a customer lifecycle, do not create a second ledger in Google Sheets (opens in a new tab). The principle is to give authority to the component that already owns the business concept, or create a deliberately owned store when none exists.

The spreadsheet can still be valuable as an operator-friendly input, review screen, or export. The risky design is letting that helpful view become a second write path that other systems silently trust.

Common questions

Can a spreadsheet be the right source of truth?

Yes—when the process is small, controlled, reversible, and the people who own it understand the cost of a mistake. The important decision is not whether a Google Sheets (opens in a new tab) file exists; it is whether the process needs stronger identity, duplicate prevention, control over simultaneous changes, or recovery than the team has deliberately built around it.

Does moving to a database make a workflow exactly-once?

No. A transaction in a Supabase (opens in a new tab) database can make its own database changes all-or-nothing. It cannot automatically undo an email, payment, or ticket that an external provider has already accepted. The workflow still needs clear states, idempotency where supported, and a way to reconcile an unknown result.

Can the spreadsheet remain the operator interface?

Often, yes. Let people use the sheet to review input or see a current result, but route the authoritative change through the system that owns the business state. If operators need to make that change, design that action deliberately instead of treating a shared Google Sheets (opens in a new tab) file as an accidental API.

The takeaway

The useful question is not “Can this be built with Google Sheets (opens in a new tab)?” It is “What is the cost of the wrong answer, what control is proportionate, and who has agreed to the remaining risk?”

  • Keep the spreadsheet when its convenience matches the consequence of being wrong.
  • Move authority when the process needs guarantees the grid does not own by default.
  • Make the trade-off visible, agree it with the people who own the outcome, and document what happens when reality does not follow the happy path.

If a spreadsheet has quietly become the authority for an operational process you need to make safer, message me and tell me where the current hand-off breaks.