This is the second post in a series of posts documenting my journey building Botler - your personal AI butler. In this post, I will kickstart the process of selling access to the bot and creating user accounts in the database whenever a user makes a purchase.
Before we start, let's take a look at what we have so far. In the first post, we scaffolded our NextJS app, connected it to our database, and set up authentication. When we set up authentication, we only did the routes and logic for logging in but not for signing up. That's because we will be selling access to the bot and whenever a user buys a membership, we will dynamically create an account for them.
This process involves several moving parts, so let's take a moment to look at everything we will cover in this post:
- Selling a membership: We will be using Gumroad for this. Gumroad makes selling digital products super easy, provides sales analytics, has a well-documented API, and comes with a widget that we could simply embed on our website.
- Creating user accounts: Once a user makes a purchase, we want to create an account for them in our database. To do this, we will have Gumroad send us a webhook and then receive the webhook using the custom API feature provided by Nhost. Upon receiving the webhook, we will create an account for the user.
- Notifying users on account creation: Once the user account is created, we will send an email to the user asking them to set their password. Upon doing that, they should simply be able to visit the login URL and access their dashboard.
A small note on using the Nhost API over the one provided by NextJS. Both services are very similar in this aspect, what made me go for the Nhost API was the fact that all my environment variables will already be available in the Nhost API and if I ever change them, they'll be updated automatically and that's a good thing to not have to worry about.
Selling a membership
So I gave this a thought and Botler will probably be a subscription-based product. As it will mostly revolve around alerts, notifications, and task automation, there are bound to be ongoing costs, and to cover ongoing costs, I am going to need an ongoing income. But, before we launch, I am going to sell a limited number of lifetime memberships on pre-order at an extremely discounted price of $10.
This serves two purposes. People who've been reading about and believing in the product from the beginning get access for a very low price and I as a creator get validation for my idea and find out whether it is something people even want.
I was thinking 25 sounds like a good number of memberships to sell on pre-order. It's high enough that a decent number of users get access and low enough that I am not losing money from day one. With that out of the way, let's set up that pre-order on Gumroad.
In the GIF above, we go through the process of creating and publishing a new pre-order product on Gumroad. Yep, that's how easy it is (well, once you link your account and everything). You can check it out here and maybe even make a purchase? (Seriously though, $10 for a lifetime membership? And you don't even get charged till the product actually gets released!)
Creating user accounts
Now that we've set the product available for sale, we can look at what happens after the user makes a purchase. This section will be broken down into two pieces. First, we'll have Gumroad send us a webhook and then, we will consume that webhook and create an account for the user on our platform.
Adding a webhook on Gumroad
To add a webhook on Gumroad, you simply need to go to settings and then advanced settings. There, you'll see an option to add a
Ping. That's where we want to add the URL that we want to send our webhook to.
In the GIF above, you can see that I added a
localtunnel URL. That's because we first want to test this locally. Once we're sure that it works, we will replace that with a production URL.
Receiving the webhook and creating a user account
Now that we've added our webhook URL, we can use the events Gumroad sends and create accounts for our users. You can look at the documentation for the webhook Gumroad sends here. We only care about a few of those fields:
short_product_id: A unique identifier for our product. If we're selling multiple products, we don't want to be creating accounts on Botler for all of them but just for the ones specifically paying for Botler.
test: If it is a test purchase, we don't want to be creating production accounts. Maybe we can have this work based on the environment we're in where we create accounts from test purchases in development but not in production.
A small note here. These are the only fields I am looking at for now but that might change later if I add tiers and want to create different sorts of accounts for different users. I also know that some people store all the information payment gateways send in their own database but we won't be covering that in this post.
Okay, so the webhook is ready and we've decided what fields we want to read the data from. Now, we're going to make use of the Custom API feature that Nhost has so we can receive the event and register the user.
At the root of our project, we will create a folder called
api and within that folder, we will create another folder called
webhooks which contains a single file called
gumroad-ping.ts. This will create
/webhooks/gumroad-ping/ as an API route for us.
Within that file, we will write all the logic for creating the user account in our database.
As mentioned above, we first make sure that this isn't a test purchase and verify that the purchase is indeed for the right product. Once that's done, we create the user and trigger a reset password request for them.
Another note here, we're sort of handling errors but not really? That's because we will eventually (before launching) set up Sentry for both the bot and the dashboard and that's where all this semi-handling will come in handy as all the
console.error calls will be replaced by a function call to send the errors to Sentry.
Notifying the user on account creation
Remember the reset password request we just triggered? That's actually also going to work as the method to set a password the first time around. Nhost doesn't have an invite flow built yet but I think they're working on it. So for now, we'll just use this workaround.
We can adjust the copy of the email so that it works in both situations and build the reset password page so that the users can actually submit a new password.
Here's what the email will look like:
Allowing the user to set a password
We've created the user's account and we've sent them an email asking them to set their password. So let's create a page where they can do so.
Again, a big snippet because I didn't want to share an incomplete example but we'll break it down and discuss the logic. Also, I moved to Formik because I realized we'll be using a lot of forms, and having something that helps with error handling, validations, and just cleans up the code seemed like a good step.
So the way Nhost works when you request a password reset for a user is that it sends them an email with the password reset URL and a
ticket. So the first thing we do in this component is, we check if the
ticket is present in the URL and if it isn't, we simply show a
401 because the user isn't supposed to be here.
If the URL has a ticket, we show the user our password reset form and ask them to enter their new password.
Once the user submits a new password, we make the API request to update their password along with the
ticket. If the
ticket is invalid, the API throws an error and we show it to the user. If everything works fine, we simply redirect the user to the login screen. Now that I am writing this, it seems like showing a toast here would be a good idea so I am just going to go ahead and do that. My go-to library for showing toasts is CogoToast.
The GIF above shows the UI along with the toast and redirection. And with that, we're done with part 2 of our journey building Botler!
Another pretty long post but again, we did so much. We created and published a product for sale on Gumroad, then we set up a webhook that gets triggered whenever a user makes a purchase, and then we created an account for that user in our database along with sending them an email to set their password!
Let me know what you think about this article and if you have any feedback at all here. Also, feel free to tweet at me if you're following along or building something similar yourself and get stuck somewhere. I'll try and respond as soon as possible.