Norma’s Ice Cream is a local business that once a week “drops” a limited quantity flavor that sells out within a couple minutes. To fill orders they initially tried using a complicated system of Instagram DMs, manually created payment links, and pasting spreadsheets of data into bulk text messaging services. Needless to say, this was very labor intensive, and I sensed an opportunity to practice solving real world business problems (in trade for some tasty ice cream!). This post covers (briefly) what tools I used to build the site, some lessons learned, features of the site, and some thoughts on what has become my most widely used real-world project to date.

Technical Approach
I replaced the manual process with a custom server that automates the entire lottery workflow. The frontend is React and the backend is Python/Django. The backend uses Twilio for text messages and Square for payments. The server is hosted on a Digital Ocean Droplet with CI/CD hooks so I can easily push updates. At the time I’m writing this the site has successfully been used to fill orders for 26 weeks of lotteries!
Blog Your Failures
Inspired by Emma Humphries‘ motto “blog your failures” here are some key lessons I learned from this project.
Colors are useless
I was very proud of my color coded status bars until I learned the owner is color blind and the colors meant nothing to him. So I changed to using more Font Awesome Icons to indicate status. And any place an icon is used, a mouse hover tooltip is used to indicate the icon’s meaning in case it isn’t immediately obvious.
Don’t put your security in the frontend
An early version of the entry form used logic in React to decide if a submission was valid or not. After a few weeks, someone crashed the site by successfully submitting several invalid entries directly to the backend api. The backend had accepted them because there was no logic guards in the backend against bad requests (I imagine they just did curl requests or something). I’m impressed someone wanted ice cream bad enough to find the sloppy exploit I had created. The next week I did what I should have done originally and implemented the validation logic again on the backend so that only valid entries were accepted.
It’s always CORS
I’ve thought about writing a whole blog post about “what to check when React sites are broken” but in a preview of one thing from that list, it’s always CORS. I had an issue where the backend API wouldn’t respond when users accessed the site via the mobile browser built into instagram. It turned out to be a CORS whitelist problem (I forget exactly what but it was something like whitelisting www.normasicecream.com vs normasicecream.com).
Scale Beyond One
The site code is weakest when I scaled from single isolated tasks to larger batches of tasks. For example, a bug caused the entire order status update to fail if one Square API transaction check failed. In hindsight, I should have planned better for managing things in larger groups. Rushing to ship led to cutting corners on error handling, something seasoned developers likely anticipate early on—it’s a lesson I’ll be keeping in mind moving forward.
Think About States and Flow
Another planning shortcoming was thinking about all the different conditions an order could be in and how it gets from one to the next. The site doesn’t have a concept of an enumerated order status that is set and updated and instead uses a common set of functions on demand to determine if an order is in a certain state based on certain properties about the order. In some ways I like this because it aligns with “don’t create junk state that is just derived from other state” but it also doesn’t adapt to change well and seems error prone (especially logic that determines what something is based on what it is not). I think if I were to start over I’d first diagram the whole thing and then think about how to tell where an order is and how resilient any solution is to changes in the order process the business might make or add.
A Tour of Norma’s Ice Cream
Each week the owner logs into the frontend (Djoser is used for JWT authentication) and sets the flavor, price, and when the lottery entry window should open and close. There are also settings to easily update pickup information that is displayed on the site and sent in text messages to winning purchasers. When the time comes for the lottery to open, the site homepage updates and allows users to fill out an entry form.

After the lottery closes, the owner uses the main dashboard to monitor and conduct the notification, payment collection, and order fulfillment process. If a regular employee logs in, the dashboard is simplified and can be used on a tablet to check off orders as they are picked up at the store.

Each entry is displayed on a selectable status bar. There is a history graphic that shows the result of the previous three weeks (i.e. did the person win, lose, or not enter the lottery) and the contestant’s win rate for all time. There is a status icon area that shows the progress of the entry as it goes from being designated a winner, messages are sent requesting payment, payment is received, and marked off for delivery. There is also a dedicated message history box for what messages were sent with Twilio (like if a user was sent a reminder text message for non-payment).

Clicking details brings up an additional actions modal. This contains detailed order and payment status and various messaging and order management actions. The payments are processed through Square and the backend uses their python api to generate payment links and check payment status.

At the main dashboard, multiple entries can be selected and then have an action be taken (like sending a message or marking them as canceled). Selection is done either by clicking on the bars or through selection tools in the bulk actions area. The lottery part–actually picking winners at random–is done via a random selection button.

When designing the site, we discussed how much should be automated and ultimately decided that most actions to advance the orders would be manual. For example, rather than picking a certain number of random winners at the close time of the lottery and then automatically sending a text message, a person would pick the winners, and then manually select them for a notification message. We decided to do this because a human would need to validate and adjust most steps week-to-week, like if more pints than usual were available or a certain person needed to be designated a winner for a promotional give away.
Future features for the site include integration with email messaging, flagging users who habitually pay late or no-show, and a better interface for managing flavors stored to the database.
Thoughts
Looking back, this project was a milestone for me as a developer. It was the first time I wrote something from scratch that other people use in real-world conditions (the software I create at work, while used by many, is mostly expanding existing code). From colorblindness to backend security, every issue taught me something valuable, and I now have a better understanding of what it takes to build software that withstands the unpredictability of real users.
This experience reinforced the idea that coding is more than just writing functional code; it’s about thinking through user experience, understanding business needs, and building systems that can scale and evolve over time. The site when it first launched was fairly basic but in the last year I’ve continued to add new features based on what we learn about how the site fits into the business.
Ultimately, working on a project that directly helps a local business and its customers has been both challenging and rewarding. It has given me a deeper appreciation for the curveballs that happen in the real-world I’m eager to continue learning, refining, and building more tools that make life easier for others—whether that’s streamlining a process, fixing bugs, or, in this case, helping people get their ice cream faster!