+
+
+
+
diff --git a/feed.xml b/feed.xml
index 311d97e..2540aab 100644
--- a/feed.xml
+++ b/feed.xml
@@ -5,12 +5,81 @@
https://frantic.im/favicon.pngOccasional posts on technology and stuff
- 2023-07-10T03:49:50.344Z
+ 2023-10-24T05:25:11.143ZAlex Kotliarskyi
+
+ https://frantic.im/hacker-gifts
+ A side project story: Hacker Gifts (2018-2024)
+ 2023-10-29T12:00:00+00:00
+
+
+ This is a story about a side project that I started in 2018, and the reasons I'm shuting it down.
+ This is a story about a side project, Hacker Gifts, that I started in 2018, and the reasons I’m shutting it down.
+
February 2018. London, 9 pm. It’s dark outside and raining. Vlad is holding a flashlight and is looking for… something. One hour ago a girl he didn’t know gave him two numbers over the phone and hung up. The numbers looked like GPS coordinates not too far from his house, so Vlad decided to check it out. The people on the street are gazing at Vlad suspiciously, and all he can think about is “How did I get into all of this?”
+
He got into all of that because of me.
+
Vlad is my close friend. He has everything (or means of getting everything) that most people would consider a “birthday gift”. I wanted to give him something special that he’ll remember for the rest of his life.
+
At first, the idea was simple — write a greetings message, encode it as a QR code and send it via mail. But that would have been too easy for Vlad. So, inspired by great movies like The Game (1997), Ready Player One (2018) and Mr. Robot, I dialed it up.
+
In the QR-encoded letter I had a link to a file with a private SSH key and an IP address (encoded as an obscure Japanese poem). The server had a Tetris game running on it (Vlad loves Tetris), and once he got through that he saw instructions to send 1 satoshi to a Bitcoin address. After verifying the transaction a script gave him the next SSH key, but this time it was encrypted with a password, and the password was inside of an armv6 binary hosted on IPFS that required a PIN. Bruteforcing the PIN took more than 4 hours on a Raspberry Pi. This time the note instructed Vlad to wait for a phone call. I put the third key on a flash drive and hid it under a bench in a park in London, and asked my cousin to give Vlad a call and tell him the GPS coordinates. The password to the last key was encoded as a Branf*ck program in a BMP file. Using the third key Vlad got to a VNC session on a virtual machine running Windows 3.1, where he had to find a file hidden on a virtual floppy drive. The file had a link to 90s-style webpage with a birthday greeting on it. Happy Birthday, Vlad!
+
+
It took me more than 2 months to set this all up. Vlad really liked the experience. I was proud.
+
Into production
+
If Vlad liked it, maybe others will like it too?
+
I’ve decided to productionalize my learning and make it so anyone could get a similar puzzle. I really wanted to make the puzzle feel personal: it should know the name of the person who’s solving it, and at the end should reveal a secret message.
+
At first, I wanted to send physical postcards, but I stumbled on way too many issues. The printing quality was always a problem. During the delivery, USPS scratched the postcard with the QR code and it didn’t always read correctly. Delivery sometimes took more than a week. In addition, many people didn’t want to leave their physical address, and the recipient could mistake the postcard for spam.
+
I decided to shift the focus exclusively to the digital aspect. Buyers can print it themselves, transfer it to their phone, or hide it on their desktop.
+
+
Tech stack
+
I remember wasting a lot of time on which framework to use to build the website. The classic software engineering side project dilemmas. I was agonizing about this until one night I forked $32 for a Shopify store. It wasn’t perfect by my standards, but it definitely unlocked everything else.
+
At first, I processed all orders manually. Shopify sends me a push notification, I run to the computer and quickly enter the data into a local script, then send the result by mail.
+
In early days the adrenaline from every $20 brought a thrill. But I quickly got tired of getting up in the middle of the night. In addition, I wanted to create a pleasant impression and instant free delivery.
+
It was time for Automation. Shopify can call a webhook for each order, and there I can generate a new postcard and send it by mail.
+
I started with a microservice zoo — a script in Ruby, an AWS lambda in Node.js, order data in DynamoDB, files in S3, Mailchimp, all seasoned with a bit of Zapier. I tried to keep each individual thing very simple, but the result turned out to be a complexity monster. Debugging stuck orders was hell.
+
When the COVID hit, I rewrote everything in Ruby on Rails. Because Rails is an established framework, I didn’t need to reinvent the wheel at every step. I’ve got proper tests, queues, database, caching — all working in a nice consistent setup that didn’t rely on 3rd party services.
+
+
Growth and marketing
+
When I started, I knew absolutely nothing about growth and marketing.
+
I remember spending my first $250 on ads — it got me ~200 likes on Facebook and zero orders. I tuned creative, targeting, copy — all with virtually no results. The problem was that I didn’t know how to position and explain my product. But I also didn’t know I didn’t know that. Frustrating times.
+
I found my first customers in a local Bay Area group for spouses of software engineers who worked for tech companies. Posting there felt very uncomfortable, but 30 minutes later I sold my first item!
+
Since then, the product has grown mostly through word-of-mouth and organic Google search.
+
I struggled with positioning the product until I read The Mom Test. That book really opened my eyes to what questions to ask, and I’ve made many improvements to the website since then.
+
After much trial and error, I have identified two target audiences:
+
+
Nerds who love side projects and fun puzzles. My product resonated with them once they dug deeper, but getting them to notice it was a challenge — these folks are completely blind to advertising and popular social media.
+
Regular people who don’t understand the technology, but who want a thoughtful gift for a programmer. They look at Facebook and click on ads, but it’s difficult to convey the value of such a gift.
+
+
+
Costs and profits
+
Technically the project is profitable. Here are some numbers from 2020, recent years were in the same ballpark.
+
Typically I charged $19.99 for a puzzle, sometimes giving a discount.
+
For the period from September 1, 2019 to September 1, 2020, there were 170 orders, with a total revenue of $3,162.50.
+
+
Shopify eats ~4% from each transaction, charges $32/mo for the service and $36 for the domain.
+
The backend needs a server on Heroku ($7/mo), Postgres DB ($10/mo) and one VPS ($10/mo) for the quest + domain ($13/y).
+
Total expenses: $757.
+
Profit $3,162.50 - 4% - $757 = $2,279.
+
On the one hand, it’s very good for a pet project. In 2019, the project barely broke even. 2021-2023 were similar to 2020.
+
But there’s one set of numbers that I’m really proud of.
+
+
At the end of the puzzle I have a small form for feedback. Not everyone fills it out, but this is what the statistics look like. Reading the feedback always made me smile. I’ve collected some on the testimonials page.
+
Shutting It Down
+
For the last few years, I haven’t touched the project much. But there’s no such thing as a truly passive side project.
+
I’m always anxious something is going to break and users won’t get their order. I have alerts for errors and watchdogs, but still feel the responsibility to deliver a great experience hanging over my shoulders.
+
The tech is also not helping: every bit of my stack is aging fast, and without constant support the cost of any change grows. “The Heroku-18 stack is end-of-life” is April next year, but I know if I start updating it, something else will pop up — new Rails version, DB upgrade, certificates, etc. Even with fewer moving pieces than before, maintenance is not trivial.
+
But the most important reason I’m shutting it down is to give space for new ideas to grow. In its current state, Hacker Gifts still occupies my headspace that I could use for other things.
+
Looking back, this project definitely fits the category of “a solution in search of a problem”. But I don’t blame myself. IMHO the solution is really clever, I haven’t seen anything like this anywhere before. I also really enjoyed building it, and I learned a ton about marketing, customer interviews, ads, and many other things.
+
If you read this far and would like to try it out for free, use code FRANTIC at checkout here. I’ll keep the servers up until Jan 2024. Also check out Roberto Vaccari’s review (spoilers!)
Some scripts still use watchman. Gotta remember to not use symlinks because it doesn’t support them (and the issue has been open since 2015). There’s also this gulp-based script that nobody has the guts to replace with anything else that’s considered more modern. You think that there’s actually no modern version of gulp but it feels outdated and you definitely want to get rid of it. The pains spreads from your head into your neck and shoulders.
The pain is barely tolerable when you reach dependencies. So, so many of them. There’s left-pad, the legendary tiny package that broke all internet, collectively causing the amount of pain and drama comparable to the destruction of Alderaan.
Every time you modify dependency list, some of the dependencies print out screens-worth of messages to your console, asking for donations, warning about breaking changes. You gave up trying to understand these. You only hope none of them are malicious enough to steal your secrets or ruin your computer. The threat of potential pain of that magnitute is frighting.
-
There’s also moment.js. You love that library, it has a really pleasent API. But the internet decided it’s too “mutable”, too fat, it doesn’t support treeshaking and now you have to migrate to date-fns. You haven’t started yet, but you already feel the painful refactoring in your bones.
+
There’s also moment.js. You love that library, it has a really pleasant API. But the internet decided it’s too “mutable”, too fat, it doesn’t support treeshaking and now you have to migrate to date-fns. You haven’t started yet, but you already feel the painful refactoring in your bones.
Looking at every package in that list causes some amount of trauma recall. But what’s even more concerning is that the version of these packages are way behind what’s considered “current”. You know that you should upgrade them. But you also have tried that before and you know how much suffering it brings. Things will break in so many ways, big and loud ways, small and subtle ways.
The next thing in this damn file is resolutions. Yes, you remember this one. It’s a suffering you choose to avoid dealing with package upgrades.
You scroll down to devDependencies. You can’t remember the time when you only needed non-dev dependencies. Why do we have this split? Yes, right, to cause more pain.
@@ -625,74 +694,4 @@ Things you can do:
]]>
-
- https://frantic.im/back-to-rails
- Moving my serverless project to Ruby on Rails
- 2020-11-14T12:00:00+00:00
-
-
- Serverless is like a black hole. It promised exciting adventures, but the gravity sucked me in and I spend most of my efforts dealing with its complexity, instead of focusing on my product.
- I have a small side project: digital gift cards for hackers. It uses Shopify for all the store-related stuff: frontend, payments, refunds, reports, etc.
-
But unlike regular digital products (ebooks, videos) I wanted each card that the user purchases from the store to be unique. So I made a script that generates personalized images and ran it manually for every order.
-
The next logical step was automating this process. I started with serverless AWS Lambda. At the time it was the hot new tech and I wanted to learn more. It seemed very fitting for my use-case: single-responsibility functions that can run at any time and don’t require server maintenance.
-
-
It was super easy to get started. I built a JavaScript function and deployed it to AWS Lambda, added Shopify web hook and it all worked!
-
Early benefits of serverless (for hobby projects):
-
-
Easy to get started
-
Don’t have to configure or maintain servers
-
Free for small loads
-
-
In reality, writing the simple Lambda functions turned out to be only 10% of the work.
-
Time passed and my backend started getting more complex: I needed to store some state for each puzzle, send confirmation emails, show an order details page. What started as a simple function, grew into a bunch of serverless functions, SNS topics, S3 buckets, DynamoDB tables. All bound together with plenty of YAML glue, schema-less JSON objects passed around and random hardcoded configs in AWS console.
-
I think it’s just a typical software development lifecycle: things grow organically, become a mess, and require some refactoring. Make it run first (discover market fit), then make it right (refactor to integrate the new discoveries).
-
But this time it was different. I couldn’t refactor things as easily as I used to in traditional monolithic apps. Here’s why:
-
When the building blocks are too simple, the complexity moves into the interaction between the blocks.
-
And the interactions between the serverless blocks happen outside my application. A lambda publishes a message to SNS, another one picks it up and writes something to DynamoDB, the third one takes that new record and sends an email…
-
I could test every single block in that flow, but I didn’t have confidence in the overall process. What if publishing fails, how would I know that? How would system recover? Can I rollback and try again? Where do the logs go?
-
Another swarm of problems was hiding in my configuration: bad Route 53 record, typos in SNS topics, wrong S3 bucket region. Tracing errors was a challenge, there’s no single log output I can look into.
-
With serverless, I was no longer dealing with my project's domain, I was dealing with the distributed system's domain.
-
At this point I felt fooled.
-
I came for the easy way to deploy code and not think about servers, but in the end had to design my system around the platform’s limitations.
-
Drawbacks of serverless (for hobby projects):
-
-
Hard to follow information flow
-
Impossible to replicate production environment locally
-
Slow iteration speed
-
Lack of end-to-end testing
-
Immature documentation (dominated by often outdated Medium posts)
-
No conventions (have to make hundreds of unessential decisions)
-
-
—
-
I was clearly not enjoying the serverless. So I decided to rewrite it. After all, it is a side project I’m doing for fun. The tech stack of choice — Ruby on Rails.
-
I haven’t used Rails since 2013, and for the last 8 years at Facebook I’ve been mostly doing JavaScript.
-
-
The experience of picking Rails back up was really nice but… uneventful. Not much had really changed. A few things got added, a few small things moved around.
-
Of course I did hit some magical Ruby issues. But unlike my typical experience with JavaScript, I was quickly able to find the solution.
-
Rails comes with so many things built-in and configured. Over the years, without Rails, I used to gluing random JavaScript libraries together to roll my own routing, file storage wrappers, email preview pipeline, managing secrets, test setup with fixtures, database migrations, logging, performance reporting, deployment scripts. With Rails I didn’t have to think about all these details and could simply focus on making product-visible changes.
-
It was like driving a Tesla after years of making my own scrappy cars. Similar components, but all configured and aligned to work well together.
-
Benefits of Rails (for hobby projects):
-
-
Conventions
-
Tooling, libraries
-
Documentation
-
Monolith is easy to understand and test
-
-
Drawbacks of Rails (for hobby projects):
-
-
Feels heavyweight in the beginning
-
Hurts if your opinions differ from Rails conventions
-
Have to host on a server
-
Doesn’t sound cool in 2020 (anymore and maybe yet)
-
-
—
-
Serverless is like a black hole. It promised exciting adventures, but gravity sucked me in and I spent most of my efforts dealing with its complexity, instead of focusing on my product.
-
- ]]>
-
-
\ No newline at end of file
diff --git a/figma/og_hacker_gifts.png b/figma/og_hacker_gifts.png
new file mode 100644
index 0000000..ffadf58
Binary files /dev/null and b/figma/og_hacker_gifts.png differ
diff --git a/good-errors-leave-trace/index.html b/good-errors-leave-trace/index.html
index 4364f0e..5e74d03 100644
--- a/good-errors-leave-trace/index.html
+++ b/good-errors-leave-trace/index.html
@@ -266,6 +266,18 @@
This is a story about a side project, Hacker Gifts, that I started in 2018, and the reasons I’m shutting it down.
+
February 2018. London, 9 pm. It’s dark outside and raining. Vlad is holding a flashlight and is looking for… something. One hour ago a girl he didn’t know gave him two numbers over the phone and hung up. The numbers looked like GPS coordinates not too far from his house, so Vlad decided to check it out. The people on the street are gazing at Vlad suspiciously, and all he can think about is “How did I get into all of this?”
+
He got into all of that because of me.
+
Vlad is my close friend. He has everything (or means of getting everything) that most people would consider a “birthday gift”. I wanted to give him something special that he’ll remember for the rest of his life.
+
At first, the idea was simple — write a greetings message, encode it as a QR code and send it via mail. But that would have been too easy for Vlad. So, inspired by great movies like The Game (1997), Ready Player One (2018) and Mr. Robot, I dialed it up.
+
In the QR-encoded letter I had a link to a file with a private SSH key and an IP address (encoded as an obscure Japanese poem). The server had a Tetris game running on it (Vlad loves Tetris), and once he got through that he saw instructions to send 1 satoshi to a Bitcoin address. After verifying the transaction a script gave him the next SSH key, but this time it was encrypted with a password, and the password was inside of an armv6 binary hosted on IPFS that required a PIN. Bruteforcing the PIN took more than 4 hours on a Raspberry Pi. This time the note instructed Vlad to wait for a phone call. I put the third key on a flash drive and hid it under a bench in a park in London, and asked my cousin to give Vlad a call and tell him the GPS coordinates. The password to the last key was encoded as a Branf*ck program in a BMP file. Using the third key Vlad got to a VNC session on a virtual machine running Windows 3.1, where he had to find a file hidden on a virtual floppy drive. The file had a link to 90s-style webpage with a birthday greeting on it. Happy Birthday, Vlad!
+
+
It took me more than 2 months to set this all up. Vlad really liked the experience. I was proud.
+
Into production
+
If Vlad liked it, maybe others will like it too?
+
I’ve decided to productionalize my learning and make it so anyone could get a similar puzzle. I really wanted to make the puzzle feel personal: it should know the name of the person who’s solving it, and at the end should reveal a secret message.
+
At first, I wanted to send physical postcards, but I stumbled on way too many issues. The printing quality was always a problem. During the delivery, USPS scratched the postcard with the QR code and it didn’t always read correctly. Delivery sometimes took more than a week. In addition, many people didn’t want to leave their physical address, and the recipient could mistake the postcard for spam.
+
I decided to shift the focus exclusively to the digital aspect. Buyers can print it themselves, transfer it to their phone, or hide it on their desktop.
+
+
Tech stack
+
I remember wasting a lot of time on which framework to use to build the website. The classic software engineering side project dilemmas. I was agonizing about this until one night I forked $32 for a Shopify store. It wasn’t perfect by my standards, but it definitely unlocked everything else.
+
At first, I processed all orders manually. Shopify sends me a push notification, I run to the computer and quickly enter the data into a local script, then send the result by mail.
+
In early days the adrenaline from every $20 brought a thrill. But I quickly got tired of getting up in the middle of the night. In addition, I wanted to create a pleasant impression and instant free delivery.
+
It was time for Automation. Shopify can call a webhook for each order, and there I can generate a new postcard and send it by mail.
+
I started with a microservice zoo — a script in Ruby, an AWS lambda in Node.js, order data in DynamoDB, files in S3, Mailchimp, all seasoned with a bit of Zapier. I tried to keep each individual thing very simple, but the result turned out to be a complexity monster. Debugging stuck orders was hell.
+
When the COVID hit, I rewrote everything in Ruby on Rails. Because Rails is an established framework, I didn’t need to reinvent the wheel at every step. I’ve got proper tests, queues, database, caching — all working in a nice consistent setup that didn’t rely on 3rd party services.
+
+
Growth and marketing
+
When I started, I knew absolutely nothing about growth and marketing.
+
I remember spending my first $250 on ads — it got me ~200 likes on Facebook and zero orders. I tuned creative, targeting, copy — all with virtually no results. The problem was that I didn’t know how to position and explain my product. But I also didn’t know I didn’t know that. Frustrating times.
+
I found my first customers in a local Bay Area group for spouses of software engineers who worked for tech companies. Posting there felt very uncomfortable, but 30 minutes later I sold my first item!
+
Since then, the product has grown mostly through word-of-mouth and organic Google search.
+
I struggled with positioning the product until I read The Mom Test. That book really opened my eyes to what questions to ask, and I’ve made many improvements to the website since then.
+
After much trial and error, I have identified two target audiences:
+
+
Nerds who love side projects and fun puzzles. My product resonated with them once they dug deeper, but getting them to notice it was a challenge — these folks are completely blind to advertising and popular social media.
+
Regular people who don’t understand the technology, but who want a thoughtful gift for a programmer. They look at Facebook and click on ads, but it’s difficult to convey the value of such a gift.
+
+
+
Costs and profits
+
Technically the project is profitable. Here are some numbers from 2020, recent years were in the same ballpark.
+
Typically I charged $19.99 for a puzzle, sometimes giving a discount.
+
For the period from September 1, 2019 to September 1, 2020, there were 170 orders, with a total revenue of $3,162.50.
+
+
Shopify eats ~4% from each transaction, charges $32/mo for the service and $36 for the domain.
+
The backend needs a server on Heroku ($7/mo), Postgres DB ($10/mo) and one VPS ($10/mo) for the quest + domain ($13/y).
+
Total expenses: $757.
+
Profit $3,162.50 - 4% - $757 = $2,279.
+
On the one hand, it’s very good for a pet project. In 2019, the project barely broke even. 2021-2023 were similar to 2020.
+
But there’s one set of numbers that I’m really proud of.
+
+
At the end of the puzzle I have a small form for feedback. Not everyone fills it out, but this is what the statistics look like. Reading the feedback always made me smile. I’ve collected some on the testimonials page.
+
Shutting It Down
+
For the last few years, I haven’t touched the project much. But there’s no such thing as a truly passive side project.
+
I’m always anxious something is going to break and users won’t get their order. I have alerts for errors and watchdogs, but still feel the responsibility to deliver a great experience hanging over my shoulders.
+
The tech is also not helping: every bit of my stack is aging fast, and without constant support the cost of any change grows. “The Heroku-18 stack is end-of-life” is April next year, but I know if I start updating it, something else will pop up — new Rails version, DB upgrade, certificates, etc. Even with fewer moving pieces than before, maintenance is not trivial.
+
But the most important reason I’m shutting it down is to give space for new ideas to grow. In its current state, Hacker Gifts still occupies my headspace that I could use for other things.
+
Looking back, this project definitely fits the category of “a solution in search of a problem”. But I don’t blame myself. IMHO the solution is really clever, I haven’t seen anything like this anywhere before. I also really enjoyed building it, and I learned a ton about marketing, customer interviews, ads, and many other things.
+
If you read this far and would like to try it out for free, use code FRANTIC at checkout here. I’ll keep the servers up until Jan 2024. Also check out Roberto Vaccari’s review (spoilers!)
Some scripts still use watchman. Gotta remember to not use symlinks because it doesn’t support them (and the issue has been open since 2015). There’s also this gulp-based script that nobody has the guts to replace with anything else that’s considered more modern. You think that there’s actually no modern version of gulp but it feels outdated and you definitely want to get rid of it. The pains spreads from your head into your neck and shoulders.
The pain is barely tolerable when you reach dependencies. So, so many of them. There’s left-pad, the legendary tiny package that broke all internet, collectively causing the amount of pain and drama comparable to the destruction of Alderaan.
Every time you modify dependency list, some of the dependencies print out screens-worth of messages to your console, asking for donations, warning about breaking changes. You gave up trying to understand these. You only hope none of them are malicious enough to steal your secrets or ruin your computer. The threat of potential pain of that magnitute is frighting.
-
There’s also moment.js. You love that library, it has a really pleasent API. But the internet decided it’s too “mutable”, too fat, it doesn’t support treeshaking and now you have to migrate to date-fns. You haven’t started yet, but you already feel the painful refactoring in your bones.
+
There’s also moment.js. You love that library, it has a really pleasant API. But the internet decided it’s too “mutable”, too fat, it doesn’t support treeshaking and now you have to migrate to date-fns. You haven’t started yet, but you already feel the painful refactoring in your bones.
Looking at every package in that list causes some amount of trauma recall. But what’s even more concerning is that the version of these packages are way behind what’s considered “current”. You know that you should upgrade them. But you also have tried that before and you know how much suffering it brings. Things will break in so many ways, big and loud ways, small and subtle ways.
The next thing in this damn file is resolutions. Yes, you remember this one. It’s a suffering you choose to avoid dealing with package upgrades.
You scroll down to devDependencies. You can’t remember the time when you only needed non-dev dependencies. Why do we have this split? Yes, right, to cause more pain.