Building DropReady: Next-Drop Signup, Not Pre-Drop Capture (Week 5)
In the previous update I wrote about a self-hosted proof-of-work challenge instead of a third-party CAPTCHA. This time I picked up something that looked simpler on paper: collecting visitor emails so a store can build a list for future campaigns. It ended with the codebase exploration reshaping the feature's scope before I wrote a single line.
The problem it solves
A drop queue isn't just overload protection - it's also a potential marketing tool. My original recommendation, from the app analysis, was ambitious: "sign up before the drop even starts" - a form on the store's regular pages, before the queue is even activated.
The mid-planning pivot
Before writing any code, exploring the architecture surfaced something important: DropReady currently has no mechanism at all for rendering anything on the store's ungated pages. client-backend/php/interceptor.php - the only code that ever runs on the client's server - operates purely at the HTTP header level: it checks a cookie, maybe redirects, sets headers. It never writes HTML or JS into the response. The queue widget only renders after redirecting to queue.<domain>, which only happens once the queue is already active.
I also found dead code: a reference to dropready-sdk in ui/src/composables/useI18n.ts, a defensive read of a data-lang attribute from a script meant to be embedded on the store's page. Nothing ever creates that tag - it looked like a planned but never-finished feature.
The conclusion: a full "pre-drop capture" needs a brand-new integration surface built from scratch - a separate, hosted JS snippet and a new deployment step for the client, comparable in scope to interceptor.php itself. That's its own, sizeable project, on the order of a WooCommerce or PrestaShop integration. I deliberately deferred it.
The simplified version that actually shipped
Instead, I built next-drop signup from inside the existing queue widget, while the visitor is already waiting. Zero new integration surface, zero new deployment step - it runs entirely on the existing widget → backend → admin panel flow.
queue.Store.AddLead uses the exact same primitive as Join - a Redis sorted-set ZAddNX - except the score is a unix timestamp instead of unix nano, because here only the signup date matters, not strict FIFO ordering. Email deduplication is a side effect of NX - Redis won't add the same member twice by definition - not separate logic I had to write.
Email format validation goes through net/mail.ParseAddress from Go's standard library. A battle-tested parser, no need to write my own regex, and no risk of that regex accepting garbage or rejecting a valid address.
HandleSubscribeLead always returns the same generic success ({"status":"ok"}), whether the email was new or a duplicate. The endpoint never reveals whether a given address is already on the list - standard practice for signup forms, so the endpoint can't be used to check who's already subscribed.
On the admin panel side, this is the first list view in the project rather than a configuration form - LeadsView.vue, a paginated table ("Load more") with CSV export built entirely client-side via Blob and a download link, with no new backend endpoint for generating CSVs.
GDPR, no sugarcoating
The widget's form has a required consent checkbox at submit. That's not full legal compliance - that's the store's responsibility - but it's basic UX hygiene so there isn't an obvious gap. Actually sending campaigns from the exported CSV to the store's own ESP is the store's job, deliberately out of scope for DropReady.
Final verification
POST /leads with Visitor@Example.com returns 200 {"status":"ok"}. The same email in lowercase (visitor@example.com) also returns 200, but GET /admin/leads shows one entry, not two - case-insensitive dedup works because the address is normalized to lowercase before it's stored. An invalid email (not-an-email) returns 400 Invalid email address.
The more interesting takeaway this week isn't the code itself: "pre-drop capture" and "next-drop signup" sound similar, but they're completely different projects in terms of complexity. And a well-designed primitive in one part of a system - ZAddNX in Join - can solve the same problem elsewhere without extra code.
FAQ
Does DropReady support signup before the queue even opens?
Not in the current version. That would need a separate, hosted integration surface on the store's regular pages, and it's deferred for now. Today signup works from inside the queue widget once the queue is already live.
How does DropReady prevent duplicate emails on the signup list?
The address is normalized to lowercase and stored with a Redis ZAddNX operation, which by definition won't add the same member to the set twice.
Is the signup form GDPR compliant?
The form has a required consent checkbox at submit, which is basic UX hygiene. Full legal compliance and any campaign sending from the exported list stay the store's responsibility.
DropReady
Want to see how DropReady protects your store during drops and flash sales? Check out the product page.