A few weeks ago, I added “Email Verification” to the FindCoffeeMate 2.0 backlog.
The plan in my head was simple.
User signs up.
Send an OTP.
User enters it.
Mark the account as verified.
Move on to the next feature.
As a frontend developer, I’d integrated email verification plenty of times before. The backend always looked straightforward from the outside.
Turns out, there’s a big difference between integrating a feature and designing one.
I got the happy path working in about an hour.
Sign up.
Receive the OTP.
Enter the code.
Account verified.
Everything worked exactly as expected.
I closed my laptop that evening thinking, “Nice. I’ll polish the UI tomorrow and call this feature done.”
Then, while I was lying in bed, one question popped into my head.
“What happens if someone clicks Resend OTP five times?”
Not because they’re trying to break the system.
Just because they’re impatient.
Honestly, I’d probably do the same thing.
Then another question followed.
If five OTPs get generated…
…which one should actually work?
The first one?
The latest one?
All of them?
I realized I hadn’t made that decision at all.
Whatever happened was happening by accident, not by design.
That bothered me more than the bug itself.
So I did what every developer promises themselves they won’t do.
I got out of bed.
Opened my laptop again.
(My family was not impressed.)
Instead of writing code, I opened a notes file and started writing questions.
What happens if someone keeps requesting new OTPs?
Should older codes still be valid?
How long should an OTP live?
What if Gmail delivers an older email after the newer one?
What happens if someone clicks a verification link after they’re already verified?
Is there anything stopping someone from spamming the resend button and burning through my email quota?
At some point I looked at that list and laughed.
My “simple” feature now had more edge cases than implementation.
That’s when something clicked.
I wasn’t building an OTP feature anymore.
I was designing a system.
Once I accepted that, the implementation became much clearer.
The first rule was easy.
Only one OTP should ever be valid.
If a user requests a new one, the previous code becomes useless immediately.
No guessing.
Become a Medium member
No multiple valid codes floating around.
Then I added an expiration time.
I settled on ten minutes.
Not because I found the perfect research paper proving ten minutes is ideal.
I literally opened a few apps, timed how long it took me to get distracted, come back, find the email, and enter the code.
Sometimes engineering is data.
Sometimes it’s just common sense.
The resend button needed attention too.
At first I almost ignored rate limiting.
I remember thinking,
“Who’s going to spam OTP requests on a coffee networking app?”
Then I caught myself.
That’s exactly how technical debt starts.
It’s much easier to add guardrails while the feature is small than after thousands of people start using it.
So resend requests are now rate-limited.
Finally, I handled already-verified accounts.
If someone clicks an old verification email after they’ve already verified their account, the system simply responds that the email is already verified.
No unnecessary database updates.
No confusing errors.
Just a predictable outcome.
None of these decisions were particularly difficult.
Figuring out that they needed to exist was the difficult part.
Working on FindCoffeeMate has changed how I think about software.
When I was mostly working as a frontend developer, my focus was naturally on the user interface.
Does the form validate?
Does the loading state look good?
Does the button work?
Building the backend has forced me to think differently.
As a full stack developer, I’ve started realizing that the experience users have is shaped just as much by API design, database rules, and edge cases as it is by the UI itself.
Somewhere along the way, I also found myself asking questions that I’d normally expect a Product Owner to ask.
“What happens if users do something unexpected?”
“What experience do we actually want them to have?”
“Can we make this impossible to misuse instead of documenting how to use it correctly?”
That’s probably the biggest lesson FindCoffeeMate has taught me so far.
Product Engineering isn’t just about implementing features.
It’s about thinking through all the situations your users should never have to think about.
The funny thing is that nobody using FindCoffeeMate will ever notice any of this.
And honestly, that’s exactly the outcome I want.
If everything works, users will sign up, verify their email in a few seconds, and immediately forget the feature even exists.
They’ll never know about OTP invalidation.
Or expiration times.
Or rate limiting.
Or the evenings I spent asking myself, “Wait… what happens if…”
All of that thinking stays behind the scenes.
That’s what good software should do.
It hides complexity instead of exposing it.
Dark mode is next on my backlog.
I’m telling myself it’ll only take an afternoon.
Let’s see if future me agrees.
P.S. This email verification system will be part of FindCoffeeMate 2.0. Building this product continues to teach me that the hardest part of software engineering usually isn’t writing the code — it’s asking the questions you didn’t know you needed to ask.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)