Categories
Web App Challenge

My Web App Challenge: From Zero to $5000/month in 5 months

My last project chocolab.com.au was an ecommerce business doing around $500 000 in Revenue a year which I sold earlier this year. The problem – no recurring revenue, big spikes around gift giving times, and a considerable amount of physical energy required to scale and meet orders during these times.

The uncertain and inconsistent nature of my revenue drove me mental. My next project needs to solve these problems. In the seven years of running Chocolab I learnt a huge amount, and I’m fortunate enough to have successfully sold the business, buying me time to work on my next thing. However, that time is quickly running out. It’s time to build a Software-as-a-service app.

The web app challenge

I’ve gone down the road of trying to build a web app in secret before. I spent six months building my dream note-taking app, and guess what – I never launched it. I’m building this in public as quickly as I can. Here’s the challenge:

I have five months to build a web application to $5000 in recurring revenue monthly. Here are the details:

  • I’m making and designing the app myself.
  • I can’t exceed 40 hours per week working on this. I have a two year old who I don’t want to miss growing up.
  • I’m starting with an existing idea that I’ve been building out the MVP for the past month (hence the five not six months)
  • I have to launch the MVP before Christmas.

I’m going to be transparent about every step of the journey, follow along on this blog or on my Twitter, to see how things are going, what I am learning and the mistakes you shouldn’t make. The deadline is 15th April to have $5000 a month worth of paying customers.

The idea

Over time your email inbox accumulates new email subscriptions, whether added by you or illegally added by a marketer. Without consistently staying on top of unsubscribing your inbox quickly becomes filled with subscriptions you don’t want.

My app will attempt to solve this problem by automatically unsubscribing you from subscriptions that you never open, to let your inbox manage itself.

I’m using Ruby on Rails to build the prototype out as quickly as possible. I’m staying away from complex frontend Javascript frameworks and just using the fairly new Stimulus as it is built into rails already and very simple. I didn’t want to waste any time on choosing a framework so I made these choices very quickly.

To be on the waitlist to try it you can sign up here: autounsubscribe.me

Time to get building.

Subscribe to get updates when I post

Categories
Web App Challenge

Can AutoUnsubscribe.me Go from $20 to $5000/month with 1 Month Left?

There is over just one month left for me to get to $5000 monthly recurring revenue in My Web App Challenge: From Zero to $5000/month in 5 months.

The problem? I’m at $20.

The app I’m building lets you automatically unsubscribe from unwanted emails that you never open, so you can focus on the emails that matter, saving yourself time and effort. You can see it here: AutoUnsubscribe.me.

I’ve left launching too late, I feel the idea is valid as I’ve had a lot of interest and have managed to get my first few paying customers who have found my app by googling and finding my Reddit posts. However $5000 with only one month left to market is a tough goal. I’m going to give it my best.

I regret setting a monetary goal. There are a few reasons for this:

  • It focused too much on the destination, rather than enjoying the journey itself.
  • It took away the joy of creating.
  • The goal was not quite immediate enough to spur me into the action I hoped it would
  • I would prefer to focus on building an amazing product, than the outcome.

I didn’t start Indie Hacking to get rich – I started it because I wanted to live a life full of creativity and passion. For me, Indie Hacking is more than just a way to make money – it’s a way to build things that I’m truly passionate about and that bring value to people’s lives. I believe that Indie Hacking can lead to a fulfilling lifestyle that allows you to work on projects you love and make a positive impact on the world.

When I first posted this challenge, I was blown away by the sheer number of people obsessed with validating their ideas. I get it – validation is important. But what many fail to realize is that it’s not enough to just throw up a landing page and call it a day. You need to show something real, something that people can touch, feel, and use. That’s the only way to truly validate an idea. A mere email list won’t cut it. So, while I’m all for validation, I believe it’s time to stop obsessing over it and start focusing on what really matters: creating something of value that people will love and pay for.

I instead used my own experience, gut feeling and intuition. I spent a few months building out an MVP then I am about to fully launch. The biggest mistake I’ve made so far is waiting too long to launch, as perhaps I put too much pressure on it.

Even if this fails and never amounts to anything, I’ll be happy either way. Not everything has to be validated, or for a purpose. No time is truly wasted. I’ve seen people trying to validate ideas for years, where they could have built 10 products in that time. Even if none took off that’s some very valuable experience.

It’s time to launch and do my best for the next month. AutoUnsubscribe.me will be launching in about five hours on Product Hunt on the 9th of March 12:01 PDT time.

Product Hunt works by having a leaderboard, that ranks products higher that get more upvotes in the first 24 hours.

Please consider supporting me on Product Hunt when it goes live. To help me achieve my impossible goal of $5000 MMR by April 14th.

I don’t have a very large audience, however, I’ve been notifying everyone I can about the launch over the last two days via email, social media etc. The only way the launch will go well is if the greater Product Hunt community likes the app.

Edit: Launch is now live, see below.

AutoUnsubscribe - Automatically unsubscribe from emails that you never open. | Product Hunt

Categories
Web App Challenge

Web App Challenge: Progress Report, Beta Discount & MVP Soon

It’s been a while since my last update due to some life circumstances and the interruption of the Christmas break. I’m looking to end the beta soon and attempt to get my first paying customer. I’m going to have a special deal for beta users soon, so now is your last chance to try the service (https://autounsubscribe.me/). Since my last update here’s what I have been doing:

Pricing

Pricing can be a hard thing to determine upfront, especially as it is harder to forecast my costs. I decided to go with three different things:

  • A trial with five free email unsubscribes to get a feel of the service.
  • a $10 seven-day single purchase for people who just want to quickly get on top of their email subscriptions.
  • A $20 monthly subscription for people who want to constantly keep on top of their email subscriptions.

I realised a lot of people would get the most value out of AutoUnsubscribe very quickly, so I wanted to provide an option for them, opposed to just subscribing for a month then cancelling.

Design

I’ve spent some time working on the homepage, with the focus of building more trust when people initially land, as trusting AutoUnsubscribe with their data is a big step. The general dashboard has been improved as well toward this goal. There is a balance between taking too much time to design things vs getting something launched quickly.

Development

  • A lot of bug fixes based on the beta
  • Added official iCloud support
  • Improved onboarding flow
  • Better more efficient unsubscribing by also using mailto unsubscribe links
  • Stripe integration
  • Implementing Ruby on Rails turbo so everything is much smoother

What’s next

  • The next step is to switch from the beta to the MVP launch, where I can attempt to get my first paying customer which would be a huge milestone.
  • I imagine I will be changing the pricing a lot, and pivoting things a bit until I can get AutoUnsubscribe to where I want to be.
  • I’m going to reach out to more users, and potentially try to interview some so I can get a stronger sense of what product market fit would look like.

The beta is ending very soon, however, if you still want to try AutoUnsubscribe for free you can, just head over to the website: https://autounsubscribe.me/

I’m going to be doing a very good deal for beta users, where they can get AutoUnsubscribe at a large discount, so if you want to be involved sign up asap.

Categories
Web App Challenge

Startups are Calculated Bets

This is part two of my From Zero to $5000/month in 5 months web app challenge, see part one here.

A lot of advice out there talks about validating ideas before you build. The truth is, you can run around collecting emails on a landing page like crazy, and you can talk to thousands of people, but you will never know if an idea is ‘validated’ until people pay for a product.

I could spend hours, weeks, days, months thinking about a solution to a problem; too afraid to build something in case I waste time building on something not “validated”. This is why solving your own problems is a good idea, if something solves a painful problem you have, you can almost guarantee other people out there will have the same problem and be interested in your solution.

Rather than waste hours, weeks days, and months thinking, I choose to waste time building the smallest product I can. The best ideas are ones you can build a working solution within a few days and get this in front of your target audience with a way of taking payment. Then you can validate this quickly. Worst case scenario you have lost a few days building something you will use yourself.

Every time you build something new, you learn new ways to build, and you learn new things about startups. So stop overthinking everything, get out there and build. You never know, something might take off. You learn by doing, not by thinking.

Build your intuition and then take a calculated bet. Startups are simply calculated bets, nothing more.

What to bet on?

There are 107 ideas in my new project ideas note. I’ve added to this list over the last 10 or so years, it’s been through four different note taking apps.

They are usually problems I experience and potential solutions to them. It’s much easier to solve your own problems and a lot less work than finding problems.

How do I choose which to build?

The smaller the better, if I can build a working version in a week or less, I can then try to validate the product with a live version that can accept payment.

Audience > problem > solution

Since the ideas are normally solutions to problems I have, this means it is likely other people out there have the same problem. It’s important to not go blindly here however, you need to define the audience that has this problem. This can be challenging as you have to separate your sense of self.

The main problem with solving your own problems is you can’t use yourself as an example to validate whether another person would pay for the product.

So what am I currently betting on?

For my $5000 in five months web app challenge I am building an app called AutoUnsubscribe.me

Here is the current audience > problem > solution:

Audience: People who live and make money out of their inboxes (freelancers, small business owners etc)

Problem: Inboxes are overwhelmed with marketing subscriptions and spam. They are too busy to keep on top of them, or they hesitate from unsubscribing in case they actually do want the subscription.

Solution: Automatically unsubscribe them from emails they don’t read.

Weekly summary

This week I spent most of my time getting the beta of AutoUnsubscribe.me ready for public release, so I can start truly validating it and whether I should keep pursuing the idea. I also spent a lot of term working on my approach to security and how I keep private data safe. You can read the current version of my security page here https://autounsubscribe.me/security

The biggest challenge is going to be building enough trust with users, they basically have to trust my service as much as they trust their mail provider.

The next goal is to get my first beta user to sign up and then set up pricing and try and get my first sale.

The beta is now live and you can hit the sign-up link by going to https://autounsubscribe.me 🙂

I’m going to be transparent about every step of the journey, follow along on my blog or on my Twitter, to see how things are going, what I am learning and the mistakes you shouldn’t make. The deadline is 15th April to have $5000 a month worth of paying customers.

Subscribe to get updates when I post

Categories
Uncategorized

SwiftUI Label with icon trailing

A quick snippet of code to show you how to create a SwiftUI Label() that has the icon at the end instead of in front.

// Make a struct that conforms to the LabelStyle protocol,
//and return a view that has the title and icon switched in a HStack
struct TrailingIconLabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.title
            configuration.icon
        }
    }
}

//Usage
Label("Lightning", systemImage: "bolt.fill").labelStyle(TrailingIconLabelStyle())

Categories
Uncategorized

navigationBarTitle Display Inline when sharing code between iOS and watchOS

I came across an issue when building a SwiftUI cross platform app recently. I did not want a large navigationBarTitle on iOS, I wanted it in the toolbar, but if I set displayMode to inline the watchOS version will not compile as it is not supported there. Basically the following code won’t work on watchOS.

NavigationView {
.navigationBarTitle("Title", displayMode: .inline)
}

I did not want to have to have a lot of platform specific code so a workaround I found is to declare navigationBarTitle without the displayMode set to inline and then add iOS #if code to add to the toolbar, then extend the View object and navigationBarTitle on iOS which will hide navigationBarTitle and hide the padding it creates at the top. Like below.

#if os(iOS)
 extension View {
     func navigationBarTitle(_ title: String) -> some View {
         self.navigationBarTitleDisplayMode(.inline)
     }
 }
 #endif 

// Then you can just do this anywhere for iOS and watchOS.
.navigationBarTitle("Title")

//and then in your view, to show a title in the toolbar for iOS  

#if os(iOS)
ToolbarItem(placement: .principal) {
    Text("Title")
}
#endif 
Categories
coding

A stopwatch timer class for SwiftUI

I spent sometime recently for Beep Test Watch creating a timer class in swift that publishes a nicely formatted string with the timer as “00:00” being mm:ss. Figured I would share it here if anyone stumbled upon it and found it useful.

import Combine
import Foundation

class TimerManager: NSObject, ObservableObject {
    @Published var elapsedSeconds: Int16 = 0
    @Published var elapsedSecondsFormatted: String = "00:00"

    var start: Date = Date()
    // Holds timer
    var cancellable: Cancellable?
    var accumulatedTime: Int16 = 0

    func setUpTimer() {
        start = Date()
        cancellable = Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.elapsedSeconds = self.incrementElapsedTime()
                self.elapsedSecondsFormatted = self.getFormatedTime(seconds: self.elapsedSeconds)
            }
    }

    // Calculate the elapsed time.
    func incrementElapsedTime() -> Int16 {
        let runningTime: Int16 = Int16(-1 * (self.start.timeIntervalSinceNow))
        return self.accumulatedTime + runningTime
    }

    func endTimer() {
        // Cancel timer and set elapsedSeconds Publisher to 0
        cancellable?.cancel()
        elapsedSeconds = 0
    }

    // Creates spotwatch formatted version of time
    func getFormatedTime(seconds: Int16) -> String {
        return self.elapsedTimeString(elapsed: self.secondsToHoursMinutesSeconds(seconds: seconds))
    }

    // Convert the seconds into seconds, minutes, hours.
    func secondsToHoursMinutesSeconds (seconds: Int16) -> (Int16, Int16, Int16) {
        return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
    }

    // Convert the seconds, minutes, hours into a string.
    func elapsedTimeString(elapsed: (h: Int16, m: Int16, s: Int16)) -> String {
        return String(format: "%02d:%02d", elapsed.m, elapsed.s)
    }
}

In order to use it add an environmental object to your ContentView() in your main app file.

import SwiftUI
var timerManager = TimerManager()

@main
struct Your_App: App {
    var body: some Scene {
        WindowGroup {
                ContentView()
                    .environmentObject(timerManager)
        }
    }
}


struct ContentView: View {
    @EnvironmentObject var timerManager: TimerManage
    var body: some View { ... }
}

I’ve made a playground file as well that works standalone that shows you how to use it below.

import Combine
import Foundation
import SwiftUI
import PlaygroundSupport


// A timer class that publishes elapsedSeconds since setUpTimer was called as in integer as well as elapsedSecondsFormatted a formatted version that is "mm:ss", note the hour is dropped. Must call endTimer when timer is no longer needed to cancel timer.
class TimerManager: NSObject, ObservableObject {
    @Published var elapsedSeconds: Int16 = 0
    @Published var elapsedSecondsFormatted: String = "00:00"

    var start: Date = Date()
    // Holds timer
    var cancellable: Cancellable?
    var accumulatedTime: Int16 = 0

    func setUpTimer() {
        start = Date()
        cancellable = Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.elapsedSeconds = self.incrementElapsedTime()
                self.elapsedSecondsFormatted = self.getFormatedTime(seconds: self.elapsedSeconds)
            }
    }

    // Calculate the elapsed time.
    func incrementElapsedTime() -> Int16 {
        let runningTime: Int16 = Int16(-1 * (self.start.timeIntervalSinceNow))
        return self.accumulatedTime + runningTime
    }

    func endTimer() {
        // Cancel timer and set elapsedSeconds Publisher to 0
        cancellable?.cancel()
        elapsedSeconds = 0
    }

    // Creates spotwatch formatted version of time
    func getFormatedTime(seconds: Int16) -> String {
        return self.elapsedTimeString(elapsed: self.secondsToHoursMinutesSeconds(seconds: seconds))
    }

    // Convert the seconds into seconds, minutes, hours.
    func secondsToHoursMinutesSeconds (seconds: Int16) -> (Int16, Int16, Int16) {
        return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
    }

    // Convert the seconds, minutes, hours into a string.
    func elapsedTimeString(elapsed: (h: Int16, m: Int16, s: Int16)) -> String {
        return String(format: "%02d:%02d", elapsed.m, elapsed.s)
    }
}

// Maybe publish the formatted version as well to remove redundant code.

var timerManager = TimerManager()

struct ContentView: View {

    @EnvironmentObject var timerManager: TimerManager
    var body: some View {
        Text("TIME")
            .font(.system(size: 12))
            .foregroundColor(.red)
        Text("\(timerManager.elapsedSecondsFormatted)")
            .padding(.top, -8.0)
            .font(Font.system(size: 16, weight: .semibold, design: .default).monospacedDigit())
        Button("start", action: startTimer)
        Button("end", action: endTimer)
    }
    func startTimer() {
        timerManager.setUpTimer()
    }
    func endTimer() {
        timerManager.endTimer()
    }

}

PlaygroundPage.current.setLiveView(
    ContentView()
        .environmentObject(timerManager)
)

Categories
Uncategorized

How to get data from Woolworths Online

I get a lot of questions about how I built discountkit.com.au from people looking to build apps to use data from Woolworths Online. I don’t have any official API access to the Woolworths Online API, I basically just consume the endpoints that their public facing website does.

The Woolworths Online website is AngularJS based and has public facing json data that you can find the endpoints to using Chrome DevTools.

Basically head to a product or page you want to get data from such as Almond Kernals. Open DevTools, go to the ‘Network’ tab and click ‘XHR’ as shown in the image below. Then refresh the page.

If you then hover over the items in the Name column, you will see a URL for the endpoint of that data. Right click > Copy > Copy Link Address.

In this case right clicking ‘176111’ the product id gives me the following url: https://www.woolworths.com.au/api/v3/ui/schemaorg/product/176411

If you head to that URL now you can see JSON data which you can then use to easily get what data you need.

Hopefully this helps!