Our signup form was being used as a weapon

Thirteen overnight signups looked like growth. It was bots using Billable's signup form to email strangers, and it cost us a spam complaint and our real numbers.

By Ajay Gupta4 min read

We woke up to about 13 new signups on Billable. For a product a few weeks past launch, that’s a good night. For about a minute we let ourselves believe it.

Then we looked closer. Only 2 of the 13 had confirmed their email.

It looked like a growth spike. It wasn’t.

The tell was in the addresses

A low confirmation rate on its own isn’t alarming. People sign up and forget. What gave it away was the email addresses themselves.

Several were Gmail “dot trick” variants: strings like w.i.sa.m.a.l...@gmail.com. Gmail ignores dots in the local part, so every one of those lands in the same inbox, but to a signup form they each look like a different person. No human types their own email like that.

The rest were worse, because they looked real. A county government address in the US. A Comcast address. Ordinary people with ordinary inboxes, who had never heard of Billable and certainly hadn’t signed up for it.

Then it stung

One of those real people did the reasonable thing when an unexpected email from a product they’d never heard of showed up: they marked the confirmation email as spam.

That complaint doesn’t land on some throwaway address. It lands on our sending domain, the same one Billable’s invoice reminders go out on. Those reminders are how freelancers using Billable get paid. Every complaint makes it a little more likely that the next reminder ends up in a client’s spam folder instead of their inbox.

So bots we’d never met had spent a bit of our domain’s reputation, and one of our users could be the one who pays for it.

What this looked like: subscription bombing

The pattern matches something called subscription bombing. An attacker takes a victim’s email address and submits it to signup forms on hundreds of sites at once. The victim’s inbox fills up with confirmation emails, welcome emails and newsletters.

The point isn’t the signups. It’s the noise. Buried somewhere in that flood is the one email that matters: a fraud alert, a password reset, a “new login from another country” warning. The attacker is betting the victim won’t find it in time.

We can’t prove that’s what was happening here. We’re inferring the motive from the pattern. But real addresses that belong to strangers, mixed with generated Gmail variants, in a burst overnight, fits it closely.

The uncomfortable part is this: we weren’t the target. Our signup form was the weapon. Any site with an open form that sends email can be used this way, and the site owner is usually the last to notice.

The fix

The fix was a CAPTCHA, but where you enforce it matters more than which one you pick.

We added Cloudflare Turnstile to the signup form. Putting the widget in the UI is the easy part, and on its own it does almost nothing. Bots don’t use your frontend. They read your network requests once and then call your auth API directly, skipping your form, your JavaScript and your widget entirely.

So the check has to live on the server, at the layer that actually creates the account and sends the email. Billable uses Supabase for auth, which supports CAPTCHA protection natively: you enable it in the auth settings with your Turnstile secret key, and from then on Supabase itself rejects any signup request that doesn’t carry a valid token. No token, no account, no email. It doesn’t matter whether the request came from our form or from a script.

Then we cleaned up. We deleted the junk accounts directly, without sending any of them another email. The people behind those addresses had already received one unwanted message from us. A “your account has been removed” notice would have been a second.

Next on the list: moving auth email and invoice email onto separate sending subdomains. Signup confirmations are the email most exposed to abuse. Invoice reminders are the email that matters most to users. They shouldn’t share a reputation.

What we took away from it

Any public form that sends email is a free cannon for someone. Signup, waitlist, “email me a copy”, password reset, contact forms with auto-replies. If an anonymous request can make your server send an email to an address of the requester’s choosing, someone can point it at a person who didn’t ask for it.

A frontend-only check is decoration. It stops the laziest bots and nobody else. If the check isn’t enforced by the server that does the work, it isn’t really a check.

Our numbers had been lying to us. This is the one that hurt. When we went back through the historical signups to clean up, the same patterns were everywhere. Billable had about 40 signups. After removing the junk, 19 were real.

More than half of our user base was never real. Every conversion rate we’d calculated had been diluted by accounts that were never going to convert, because nobody was ever behind them. The product wasn’t converting worse than we thought. It was converting better, from a much smaller number of actual people.

We’d rather know that than keep reporting 40.

If you run something on your own

Check your signup form tonight. Look at the last few weeks of signups and ask how many confirmed their email, and whether the addresses look like people. Then check whether your bot protection is enforced on the server or only drawn on the page.

It takes ten minutes, and it’s much cheaper than finding out from a spam complaint.

Ship your product
in weeks, not months.

Tell us what you're building. We'll tell you what's possible, what it'll take, and whether we're the right people to own it — on a free 30-minute call.

Book a discovery call