Building a Postcard Vending Machine
This post is a continuation of my design story, an account of how I transformed my concept into a fully-autonomous, postcard-sending vending machine. It’s the tale of wrangling data, crafting interfaces, and orchestrating messages, all in the pursuit of delivering a little kindness by mail.
Thinking
Before a single line of code was written, I spent many hours lost in thought. I would sketch out the user journey, imagining how someone might choose a set of kind words, tweak a design, and pay for the service. I drew out each screen, imagining how the data would morph with every step.

Ideas for the Three Kind Words UI
I’ve learned that stepping away from the computer is crucial for any stage of knowledge work. I’d wander outside, with no music to distract me, letting the simple act of watching leaves rustle churn the ideas in my brain. After these escapades, I’d return to the studio with a clear mental model of the flows and transitions, and give these ideas physical form with minimal effort.
Planning
When it was finally time to start programming, my strategy was to write just enough code to see my mental model take shape. I wasn’t after a perfect first draft; I wanted to build a lean prototype that I could optimize for learning. I started by assembling the core components: a responsive frontend that would look great on both desktop and mobile, a backend to manage orders and schedules, and seamless integration with third-party APIs for payments and mailing.
Instead of reaching for Ruby on Rails (my trusted companion for over a decade), I consciously decided to try something different. I chose Cloudflare’s serverless ecosystem, specifically their Pages and Workers products, to host both my frontend and backend. Compared to what I had been building with Rails, the whole serverless thing felt so weird and interesting. The documentation looked great, it was free to get started, so I jumped in.
Backend
My first technical hurdle was to move from my manual cURL experiments with PostGrid into Cloudflare workers. I wanted to be able to trigger a postcard to be printed by the API with code. PostGrid’s dashboard allowed me to setup a webhook that would ping my backend whenever a postcard is updated, shipped, delivered, etc. PostGrid has great documentation and you can do almost everything you need to do through the UI. It wasn’t long before I had a working system that would print a postcard whenever I wanted.
Next, I needed a way to hold onto data about postcards and orders. I started out using a KV store. This was great, until I started sending more postcards, which pushed more updates faster than the KV store could replicate. I learned that is called eventual consistency, and it’s a thing that happens in distributed systems. These issues nudged me towards using a D1 SQLite database database, which ended up being familiar territory.

I found this between the couch cushions
Next came the integration of payments using Stripe. Whenever an order is placed, Stripe will push the details to my backend app, which saves the order in my database. I can then push three postcard requests to PostGrid, each with a send date 7 days apart. PostGrid then processes the request and generates a PDF of the postcard. All three PDFs get attached to an email and sent to the customer.
sequenceDiagram Stripe->>+Backend: New order Backend->>-PostGrid: Schedule cards PostGrid->>+Backend: Postcards ready Backend->>-MailerSend: Send proof email PostGrid->>+Backend: Postcard shipped Backend->>-MailerSend: Send card shipped email
Whenever PostGrid ships a postcard, it pushes the details to my backend app. The database gets updated with the status of the postcard, and an email is sent to the customer with a PDF of the postcard. A final email is sent after all the cards have been delivered. There are a few other one-off workflows, like order cancellation and address correction. I’ll save those for another post.
All of these tools were free to use, so I was able to get started without any upfront costs. PostGrid’s test mode will generate PDF preview of each postcard. This was useful for verifying that my code worked before spending any money. One of the harder tools to find was a transactional email provder that had a good enough free tier to get started. I ended up choosing MailerSend, which gives you a generous amount of daily emails for free and ala carte pricing for more emails if needed.
Frontend
As the backend started humming along, I shifted my focus to the frontend. I had already sketched the idea of three prominent cards that could flip to reveal their secrets. I began by using Bootstrap to build the skeleton of my site. As I started to come up with more templates, I realized a static site generator would make my life easier, so I tried out Hugo. Using Hugo’s built-in content adapters, I dynamically populated the word sets from my database as static content during Hugo’s build process.

The frontend MVP
Through countless iterations, I eventually arrived at a solution that more or less worked. You could customize the cards, it would take a payment, dispatch the cards, and send emails. All automatically. It was so satisfying to see all the parts work together. I shipped it.
C.R.E.A.M.
As I started to make more changes, I realized just how bonkers the backend code was. It worked, but it did so in a way that made less sense the more you looked at it. My 1,000+ line monolith of LLM-hallucinated JavaScript needed to be split into manageable, logical modules, each handling aspects like checkout, postcard generation, and address management.
I poured myself a cup of tea and embarked on a deep refactoring journey. I reimagined and restructured the code, ensuring that every pathway was understood, coherent, and documented. I added a workflow for correcting address verification failures, modularized templating, and fine-tuned database interactions. I used an LLM differently this time, less for code generation and more for debugging and code review. All of this time was worth it, because the code is now much more understandable and maintainable.
Wait a second, why did I name this section C.R.E.A.M.? Beyond the Wu-Tang track of the same name, it’s an acronym for “Consider Refactoring Everything AI Made.”
Enhancing
With the MVP up and running, I turned my attention back to the frontend, because the UI looked jacked up on a smartphone. I knew that more people would see the site on a mobile device vs desktop, so I needed to ensure the mobile UI looked great and worked just as well. A new UI paradigm emerged: three beautifully rendered cards in a horizontal arrangement on desktop, and a vertical stack on mobile. I even added a feature for future scheduling, perfect for birthdays, graduations, or any special moment you want to celebrate in advance.

Refactoring the frontend
Testing with friends confirmed that the revamped UI was intuitive and fun to use. Around this time I had the same realization I had with the backend… there’s a lot of code in there, some of it I understand, some I don’t. But after extensive testing, it works well enough, so we’re just gonna go with it. I’ll save my bike-shedding for another day.
Reflecting
Shipping ThreeKindWords.com has been an effort in pragmatic iteration. While I’m proud of what I’ve built so far, there’s a lot of room for improvement. This was the first project I used LLMs from idea to launch. I have found them to be an invaluable thinking partner and development companion, a tool I will reach for again. I don’t think I could have put this site together as fast as I did without them. That said, in my experience, LLMs are often wrong, write lazy code, and assert extreme confidence about things they have no idea about. By remaining skeptical about the stuff they generate, you can leverage LLMs to their fullest extent.
Now my focus shifts to bringing visitors into my little world of kind words. SEO stuff, blog posts (like this one!), and getting eyeballs on what I’ve built are the next frontiers. I’m historically not great at this part, so I have a lot to learn on the road ahead.
I hope you enjoyed this behind-the-scenes look at how I built this site. Thanks for reading.