10 days. 30 commits. Zero new visible features.
While TAMSIV users were waiting for a new button, a new color or a new voice command, I spent every day rewriting something nobody will ever see: how the app talks to people through email. And I realized the invisible part might be 80% of what makes a product stay installed.
Why build a full emailing system when Supabase Auth already sends verification emails
It's a fair question. Supabase in its default config already sends a verification email on signup. Many products stop there and add nothing else. That's enough to validate an email, not to build a relationship with the user.
In production since April 4, I ended up with dozens of accounts created but not verified. No follow-up. No day-7 feedback to know if the app was useful. No clean way for someone to say "that email wasn't for me." And no admin-side visibility on what was being sent, delivered, bounced or marked as spam.
A homegrown transactional emailing system is exactly what separates a "technically functional" product from one that actually feels serious. Big apps hide it behind polish. Small apps neglect it and lose users without understanding why.
The daily cron that sends one reminder, and only one
The first brick is a daily cron running at a fixed time on Vercel. It queries the database, selects accounts created exactly 3 days ago and still unverified, and triggers a single reminder per user.
The "one reminder only" rule is intentional. I've received more aggressive follow-up emails than I can count. Three in 48h, five in a week, ten over a month. That's the best way to get people to unsubscribe before they've even tried the product.
The cron writes to a dedicated table so it knows who received what and when. If a user was already reminded, they're skipped. If the Resend send fails, the error is logged but the cron doesn't auto-retry: I prefer visibility on failures to silent saturation.
The day-7 feedback loop: 4 buttons, 4 truths
Seven days after signup, each new user receives an email with 4 clickable buttons: love, hate, suggestion, bug. Each button points to a dedicated page that stores the response and offers a free-form comment area.
Why this exact format? Because a Google Play rating is filtered by whoever has the most energy to leave one. An email with 4 buttons captures the silent truth. The truth of people who will never go to the store to write a comment, but can say "I didn't like it" in one click.
Technically, each button carries a signed token scoped to the user and the feedback type. The GET route on the site validates the token, stores the answer with user_id and type, and renders the matching page with a free-form comment area. No extra auth to leave feedback: that's intentional. Friction kills responses.
The anti-impersonation flow: GDPR without drama
Concrete case: someone creates a TAMSIV account with email alice@example.com. But Alice never signed up. She receives a welcome email for an account she never created. What does she do?
Without this flow, she files the email under spam or reports the sender. Either way, it's a loss: loss for me on sending reputation, loss for her since she has no clean way to close the matter, loss for the person who typo'd and will never get communication on the right address again.
In the new version, every welcome email contains an "I didn't create this account" link. Click, dedicated page, confirmation. The signup is immediately deleted from the database, a log is stored for audit, and a closing message confirms to Alice that her address won't be used again. GDPR without drama. One action, three seconds, done.
The page is translated into all 6 languages of the app (French, English, German, Spanish, Italian, Portuguese). Accented characters were broken in some translations due to an encoding issue: fix in commit 12e61a7.
The Resend webhook: know before the user writes
All sends go through Resend, a developer-first transactional email provider. Resend exposes webhooks for every lifecycle event: email.sent, email.delivered, email.opened, email.clicked, email.bounced, email.complained, email.delivery_delayed.
All of them are listened to and stored in a dedicated table with the user reference, email type, timestamp and associated metadata. This lets me aggregate: how many emails arrived today, how many were opened, which ones generated a click, how many bounced.
The admin dashboard shows these stats in real time with badges that increment on every send. A bounce appears on a user? I see it immediately, I can check whether it's a temporary issue or a dead email, and adjust. A complaint (spam mark)? High priority, immediate investigation.
The per-user grouped send history, with resend and dedup
The admin sees all recent sends, but grouped by user. If Alice received her verification email then her day-3 reminder then her day-7 feedback, I see one "Alice" row with three sub-rows that expand. More readable than a raw chronological feed.
Each send has a "source" badge (auto / manual). Automatic sends (cron, triggers) are separated from manual ones (a "resend" button in the admin). A "resend" button exists on every row for the cases where an email landed in spam, or to force a retry on a different alias, or to test a template change.
A dedup system prevents re-spamming: if I sent a welcome email 2 hours ago and I want to do a bulk "all today's signups" send, Alice is excluded automatically because she already received one. An "eligible" badge shows on every user to indicate whether they can receive the ongoing send or not.
Two mobile hotfixes in parallel: v1.07 and v1.08
While the email backend was moving forward on Vercel, the mobile app needed air. Two releases went out on Play Store Alpha, then production.
v1.07 (7d65fd0): 8 targeted bug fixes, plus a new compact view inside folders. When you have 15 sub-folders in a project, you want to see the whole tree at a glance without scrolling. A toggle switches between detailed view (full cards with thumbnails and previews) and compact view (dense rows with just name and count). Zero new visible feature, but a shift in usage for heavy users.
v1.08 (a494e7e): full gesture-handler audit. TAMSIV uses react-native-gesture-handler everywhere, but several screens still had TouchableOpacity or FlatList imported directly from react-native. Result: intermittent taps that didn't register, impossible to reproduce, reported by users who eventually uninstalled without understanding why.
The audit touched 25+ files. Every TouchableOpacity, every FlatList, every ScrollView was switched to the gesture-handler import. The ghost tap bug is fixed. While we were at it, we redesigned the Feed UI and stabilized all modals: in the process, polish takes a clear jump.
What I take away after 10 days
What struck me is that every line of code written during these 10 days will stay invisible as long as it works. Nobody says "wow, your verification email arrived at the right time, once, in the right language." Nobody notices that a bounce was automatically detected and the admin saw the signal before the user even wrote.
The invisible only gets noticed when it breaks. And at that moment, the user doesn't understand what failed: they just feel the app "isn't working well." They uninstall. They don't write. They don't say why.
So the invisible might be 80% of what makes a product feel finished. Not the buttons you ship in feature releases, not the colors you repaint, not the animations. Just the fact that when something should reach the user, it reaches them. At the right time. Once. In the right language.
Not sexy to post on LinkedIn. But that's what keeps a product installed.
Want to see how it looks from the user side? TAMSIV is on Play Store and live at tamsiv.com. If you're building your own emailing stack, I'd love to hear how you handle the invisible part.
Top comments (0)