Skip to content
SheetLink Forms

Building · 2026-08-09 · 9 min read · By Arden Talbot, founder of SheetLink

The case against writing your own form API

It starts as five lines of serverless code. Then it grows a queue, a spam stack, and an OAuth lifecycle. An essay on build vs buy - including the cases where building really is right.

A ledger-styled illustration of a small code block ballooning into a tangle of queues, filters, and token diagrams.

Five lines, honestly

Let us start by conceding the strongest point on the other side: the minimal form handler really is tiny. A serverless function that receives a POST, parses the body, and forwards it somewhere - an email API, a chat webhook, a database insert - is a genuine afternoon project. It deploys with your site, costs nothing at contact-form volume, and puts no third party in the path of your data. Anyone who tells you this is hard is selling something.

We sell something. So this essay is going to be careful. The argument is not that the function is hard to write. The argument is that the function is not the product. The product is everything the function turns into once real traffic touches it, and the interesting question is whether you want to own that evolution.

Week one: it works

Week one is bliss. Submissions arrive, the email fires, you move on to real work. This phase can last months, and for some forms it lasts forever - a fact we will return to, because it is the honest core of the build case.

But notice what week one quietly assumes: every downstream service is up at the exact moment someone submits, every submitter is human, and nobody is watching whether submissions actually arrive. Each assumption fails on its own schedule.

The first lost submission

The first assumption to fail is availability. Your function calls an external API synchronously, inside the request. The day that API times out or returns a 500, the submission it was carrying evaporates - and the visitor may see an error, or worse, may see success while the data goes nowhere. You will not know it happened, because the failure left no trace anywhere you look.

The fix is well understood: accept the submission immediately, persist it, and deliver asynchronously with retries on a backoff schedule. Which means your five-line function now needs a durable store, a queue or scheduled worker, retry logic that distinguishes transient failures from permanent ones, and a delivery log so a human can answer "did Tuesday's submissions arrive?" That is not five lines. That is a small distributed system, and its failure modes are the annoying kind: rare, silent, and discovered by the person whose lead went missing.

Then the spam arrives

The second assumption fails louder. Any endpoint that accepts unauthenticated POSTs will be found by bots - not eventually, but within days. The first wave is crude and a honeypot field catches it. Then comes link-stuffed comment spam, disposable-email signups, and eventually submissions that look almost human.

Now you are building a spam stack: a honeypot, rate limits per IP, content heuristics, maybe a CAPTCHA you did not want. And you inherit the hardest problem in the category, which is not catching spam - it is not catching humans. A filter that silently discards a real lead is worse than no filter, and you will never know it fired. Our spam protection guide runs long for a reason: the layered answer took the industry years to converge on, and the part most homegrown filters skip - holding suspicious submissions for review instead of deleting them - is precisely the part that protects you from your own false positives.

The OAuth lifecycle nobody budgets for

The third escalation arrives when someone asks for the data in a spreadsheet - which they will, because a spreadsheet is where lists live. Writing rows to Google Sheets or Excel Online means OAuth: consent screens, scopes, access tokens, and refresh tokens that must be stored securely and exchanged on a schedule. It means handling revocation, expiry, and re-consent gracefully - because tokens die of natural causes, and when one dies your deliveries stop until a human notices.

None of this is exotic engineering. All of it is undifferentiated engineering: plumbing that is identical for everyone who builds it and visible to no one when it works. It is the least interesting possible use of your maintenance attention, and it never finishes, because token flows and API quotas belong to other companies who change them on their own schedule.

The ledger, totaled

Add it up. The five-line function, matured, contains: a durable submission store; an async delivery worker with backoff and a log; a honeypot, rate limiter, and heuristic filter with a review queue for the near-misses; CORS and an origin allowlist if anything submits cross-origin; an OAuth token store with refresh and revocation handling; and monitoring on all of it, because every one of these fails silently by default.

Each line item is a weekend. The total is a product - specifically, it is this product category, rediscovered one incident at a time. The condensed version of this argument, with a feature-by-feature comparison table, lives in our guide: form endpoint vs writing your own serverless function.

The honest inverse: when building is right

Now the other side of the ledger, stated as plainly. Building your own form handler is the right call more often than a vendor essay usually admits.

Build when the destination is a system you already operate - if submissions go into your own Postgres database next to your own app, a hosted endpoint adds a hop for nothing. Build when the form logic is genuinely custom: multi-step server-side validation, conditional routing, pricing calculations. Code expresses that better than any configuration UI. Build when volume is tiny and the cost of a lost submission is low - a personal site's contact form can fail once a year without consequence, and the queue you did not build was the correct queue to not build. And build when you want to learn; a form handler is a legitimately good first serverless project.

The pattern in the exceptions: build when the hidden systems either do not apply or are already yours. The calculus flips when spreadsheets, spam volume, or the cost of a lost lead enter the picture.

The opportunity cost is the real argument

The strongest case against building is not that you cannot. It is that the time goes somewhere, and form plumbing competes with the thing your project actually exists to do. Every hour spent debugging a token refresh is an hour not spent on the product the form is collecting leads for. Infrastructure you write is infrastructure you read forever - and form infrastructure, done right, is invisible. Nobody has ever chosen a product because its contact form retried elegantly. They have only ever left because their message vanished.

Buy the invisible thing. Build the visible one.

What buying actually gets you

Concretely, in our case: a permanent endpoint per form that accepts urlencoded, JSON, or multipart POSTs. Delivery straight to the Google Sheets API or Microsoft Graph - no automation middleware in the path - with retries at 5 minutes, 30 minutes, and 2 hours and a delivery log you can read. The full spam stack with a strictness dial, where suspicious submissions are quarantined for one-click review, never silently dropped. Ad attribution - UTM parameters and click IDs like gclid and fbclid - captured and landed in columns. And no per-submission fees, ever, because metering the thing you exist to collect is a perverse incentive.

The mechanics are on how it works, the security posture is documented on the security page, and the API surface is in the docs.

A test you can apply in one minute

Ask three questions. Does anyone downstream want this data in a spreadsheet? Would a silently lost submission cost real money or a real relationship? Is the maintenance attention this form will consume better spent elsewhere? Zero yeses: build it, enjoy it, mean it. One yes: build it, but add the retry queue now, not after the incident. Two or three: buy it - from us or from anyone in the category - and go work on your actual product.

SheetLink Forms is free to start - no card, no invite. Start free, and the demo shows a real form landing rows in a public sheet.

FAQ

Is a serverless function ever the better choice than a form endpoint?

Yes, genuinely. When the destination is a system you already run, when the logic is custom enough that code beats configuration, or when volume and stakes are both low. The full comparison is in our endpoint-vs-function guide.

What actually breaks first in a homegrown form handler?

Usually silent delivery failure: the downstream API has a bad minute, the synchronous call fails, and the submission is gone with no record. Spam is the second failure, and it is louder but less costly - you see spam, you do not see the lead a naive filter discarded.

Why is writing to Google Sheets from my own code such a hassle?

The write itself is one API call. The hassle is the OAuth lifecycle around it: storing refresh tokens securely, exchanging them before expiry, and handling revocation and re-consent when tokens die. See the refresh token glossary entry for what that maintenance actually involves.

Can I start with my own function and switch to an endpoint later?

Easily - that migration is one attribute change. Point the form's action at the endpoint URL, or have your existing function forward the payload as JSON during a transition. Nothing about the choice is permanent, which is a good reason not to over-invest in the homegrown version early.

Does a hosted endpoint put a third party in the path of my data?

Yes, and you should weigh that honestly. The submission passes through the service before landing in your spreadsheet. In exchange you get delivery guarantees and spam handling; the mitigations - what is stored, for how long, and how - are documented on our security page. For some data classifications, building stays the right answer.

What does asynchronous delivery mean for my visitors?

The endpoint accepts and persists the submission immediately, so the visitor gets a fast success response even if Google or Microsoft is having a slow moment. A worker then delivers the row, retrying at 5 minutes, 30 minutes, and 2 hours if needed, with every attempt visible in the delivery log.

How do endpoints avoid dropping real submissions as spam?

The design principle is quarantine, never drop. Only the honeypot marks a submission as outright spam; everything else the heuristics flag goes to a review queue where one click approves and delivers it. A false positive costs seconds of review, not a lead. The quarantine entry explains the model.

What does SheetLink Forms cost compared to running my own function?

The free tier covers 100 submissions a month; Pro is $19 a month or $190 a year, unmetered - no per-submission fees. Details on the pricing page. Your own function is nearly free to run; the cost comparison is really about maintenance time.

Spend the weekend on your product instead

The queue, the spam stack, and the OAuth lifecycle are already built - free to start.

Start freeSee the live demo

Static sites are back. Forms were the missing piece.Progressive enhancement is a form feature