惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
爱范儿
爱范儿
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
C
Check Point Blog
F
Fortinet All Blogs
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
博客园 - 【当耐特】
Jina AI
Jina AI
T
The Blog of Author Tim Ferriss
P
Palo Alto Networks Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 热门话题
M
MIT News - Artificial intelligence
Vercel News
Vercel News
博客园 - 司徒正美
Recorded Future
Recorded Future
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
P
Privacy & Cybersecurity Law Blog
Webroot Blog
Webroot Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
云风的 BLOG
云风的 BLOG
D
DataBreaches.Net
Y
Y Combinator Blog
J
Java Code Geeks
B
Blog
A
About on SuperTechFans
O
OpenAI News
aimingoo的专栏
aimingoo的专栏
T
Tor Project blog
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
AWS News Blog
AWS News Blog
GbyAI
GbyAI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
IT之家
IT之家
V
V2EX
量子位
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
大猫的无限游戏
大猫的无限游戏
Help Net Security
Help Net Security
W
WeLiveSecurity
C
Cyber Attacks, Cyber Crime and Cyber Security

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your contact form is the only page that touches money
Łukasz Blani · 2026-05-18 · via DEV Community

In March 2025 a startup founder filled out my contact form to ask about a six-month consulting engagement. I never got the email. He moved on.

Four months later I bumped into him on a different thread and he replied with "thought you weren't interested."

I never figured out exactly what broke. SMTP credentials had rotated three weeks earlier and my Nodemailer wrapper was eating the auth error. My /api/contact endpoint returned 200. My uptime monitor stayed green. My error tracker had nothing to log. Twenty-eight submissions vanished into the void before I noticed.

That single missed submission was worth more than my AWS bill for the entire year.

TLDR

Your contact form is the only page on your site that directly touches revenue. The rest is content. The form is a cash register, and if you wrote your own /api/contact handler, there is a real chance it is leaking right now and you have no way to know.

This post is about why that happens, what a real contact endpoint needs, and a 5-minute test you can run before lunch.

Why devs misclassify the form

Most contact forms are written something like this:

app.post('/api/contact', async (req, res) => {
  const { name, email, message } = req.body;
  await transporter.sendMail({
    from: 'site@mysite.com',
    to: 'me@mysite.com',
    subject: `Contact from ${name}`,
    text: message,
  });
  res.status(200).json({ ok: true });
});

Enter fullscreen mode Exit fullscreen mode

That code looks fine. It works on your machine. It works in staging. It works the first time you ship it.

Then it sits untouched for years, because you treat it like another endpoint. Just another POST handler. Same priority as /api/health or /api/status.

It is not the same. /api/health failing wakes you up. /api/contact failing is invisible. The user calling it does not refresh the page. They send the message, see the success animation, and assume you got it. Nobody DMs you on Twitter to say "hey, your contact form ate my message, you might want to check it."

A broken /api/contact is the worst kind of bug, because the only person who knows it broke is the person you most needed to hear from.

The silent failure list

Here is a partial inventory of ways my contact handler has actually broken in production across half a dozen projects:

  • SMTP credentials rotated by the email provider. The handler returns 200, the email never sends. No exception, because Nodemailer logs the auth failure to stderr by default and your serverless platform discards stderr
  • Resend free tier hits 3,000 emails. Submissions 3,001 through whenever-you-notice silently drop with a quota error you never read
  • A dependency upgrade changes how multipart/form-data parses. iPhone Safari submits return 415, every other device works fine, you only test on Chrome
  • DNS MX record swap during an infra migration. Mail delivered straight to spam for 11 days before anyone checks the recipient inbox
  • A scraping bot fires the endpoint 4,000 times overnight. Real submissions get buried under spam. You stop opening the inbox because it is mostly junk
  • Vercel cold start times out the first submission of the morning. User retries, gives up after the second try

Each of these felt instantaneous when it broke. Each took me days or weeks to spot.

The common thread: there is no error. Just an absence of an expected signal. And nobody monitors for absence.

Why error tracking cannot catch this

Sentry catches exceptions. A silent 200 with a missing email is not an exception. The handler did its job, by the strictest reading of the code. It returned a status code. The bug is what your handler did not do, and the absence of an action is invisible to a stack trace.

Your uptime monitor catches downtime. The endpoint responds 200, the page loads, the dashboard stays green. Green dashboard, broken revenue.

The only signal that exists is a real human sending a real message and noticing nothing came back. That signal is one customer follow-up away from you noticing. Which means you only notice when the customer cares enough to follow up. Most do not.

What a production contact endpoint actually needs

I wrote this list on a napkin in 2024 after another silent failure. It was embarrassing how short it was, and how much of it my homegrown handler did not have.

  1. A delivery receipt. Did the email actually leave the server? Not "did the SMTP transaction return 200", but "did the message hit the recipient mailbox". Without this you are flying blind.

  2. A dashboard showing every submission. Regardless of whether the email arrived. The submission and the notification email are two separate concerns. Treating them as one is how silent failures happen.

  3. Spam protection that does not show CAPTCHA. Honeypot fields, timing checks, and rate limits handle 95% of bot traffic without ever interrupting a human. CAPTCHA on a contact form kills conversion. Do not ship it unless you have run out of other options.

  4. Per-IP rate limiting on the endpoint. Bots flood. Without this, your inbox becomes useless and your real submissions get triaged into the trash by your own pattern-matching.

  5. Notification redundancy. Email plus Slack, or email plus Telegram. If one channel breaks, the other still pings you. I learned this the hard way.

  6. Audit log with timestamps, IP, and user agent. When something looks fishy (a submission that mentions a feature you do not ship, or a contact at 4 AM their local time), you want the metadata. When something looks lost, you want a record that proves the submission existed.

  7. Replay capability. When a notification email goes missing, you should be able to forward it to yourself or to the right teammate from a dashboard. Not by writing a SQL query.

  8. Auto-responder for the submitter. A short "we got your message, here is what happens next" email. Proves to the customer that the form worked, which means if they do not hear back from you they will follow up instead of assuming you ghosted them.

You can build all 8 yourself. I have. It is somewhere between 40 and 80 hours of work, depending on how careful you are about edge cases. Then you maintain it for the life of the project.

The build vs buy math

Here is the math I run every time someone asks why I do not just write my own:

Initial build: 40 to 80 hours, depending on how thorough you are
Ongoing maintenance: 10 to 20 hours a year for dependency upgrades, infra changes, and email provider migrations
Hidden cost: every silent failure costs you the value of a missed inbound lead, and you cannot measure this until after it happens

Against that:

Free tier of any decent form service: $0, up to roughly 50 submissions a month
Paid tier: $5 to $15 a month, unlimited
Time to first working submission: under five minutes

I have spent days arguing with engineers who insist they can do it in an afternoon. They are right, they can. The first time. The cost is not the first time. The cost is years 2 through 5 of every project they ship, multiplied by the fact that they will never spot the silent failures.

The stack choice

There is no single right answer. The right hosted form service depends on your stack and your budget.

  • Formspree has been around since 2017, well-tested, decent free tier
  • Basin is the same shape with simpler pricing
  • Web3Forms is the cheapest option I know if you just need an inbox
  • Getform has the best file upload support I have seen
  • FormTo is the one I built (formto.dev), because I wanted self-host plus custom SMTP plus a dashboard I actually wanted to open every morning

The brand matters less than the fact that you stop trusting your own /api/contact and start trusting a service whose only job is to not lose your submissions. That single change moves the failure mode from "invisible" to "someone else's dashboard with a status indicator."

When NOT to use a hosted form service

Three real cases where rolling your own makes sense:

You have hard data residency requirements. If your industry forbids submission data crossing into US-based SaaS, you either self-host an open-source option (FormTo has a self-host build, Formspree does not) or you build your own. The decision then becomes self-host vs DIY, and self-host still wins on time.

Your form is one input to a complex pipeline. If submissions trigger a workflow that touches your auth, your billing, your CRM, and your internal Slack in real time, a hosted form adds a hop you have to coordinate. At that point your form is part of your product, not a marketing surface, and you should treat it like product code with the same rigor as your billing path.

You are at zero visitors and learning. If you are building your first SaaS and the contact form sees three submissions a year, the failure cost is small enough that the learning value of building it yourself wins. Build it badly, watch it break, then switch to a hosted service the day you actually start caring about leads.

If none of those describe you, the math is not close. Use a hosted service.

The 5-minute test you should run right now

Stop reading and do this:

  1. Open your live site in an incognito window
  2. Fill out your contact form with a Gmail address you do not normally check
  3. Submit it
  4. Open the Gmail inbox

Now verify four things:

  • Did the email arrive at all?
  • Did it land in inbox, not spam?
  • Did it arrive in under 60 seconds?
  • Is the from-address sane, or does it look like a default noreply you forgot to configure?

If you cannot confidently say yes to all four, your form is leaking. Maybe a little, maybe a lot. You will not know until you look.

I run this test on every project I own once a quarter. It takes five minutes. It has surfaced two silent failures so far this year.

Back to the founder from March 2025

He never came back. I built him a perfectly normal contact form in 30 minutes one weekend in 2021 and assumed it would keep working. It did, until it did not, and then it lied to me about whether it was working.

The cost of a working contact form is between $0 and $15 a month. The cost of a broken contact form is every inbound lead you miss until the day a customer pings you on Twitter to ask why you ghosted them. Those numbers are not close.

Treat the form like the cash register it is. Use a service. Run the test.

When was the last time you tested your contact form in production, not in your dev environment? Be honest.