Marketing Automation With Customer.io + Next.js
with Joel Hooks
Joel hooks has been building full-featured marketing automation using Nextjs & Customer.io for some of the most popular dev education sites out there. In this episode, he'll share how it's done!
Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. This is our first episode of 2022. So, I'm very excited to bring back good friend, multiple guest, Joel Hooks. How are you doing, man?
JOEL: Doing great. How are you doing?
JASON: I'm doing great. I'm excited to have you back. We always do something wild whenever you're on the show, so I'm looking forward to it.
JOEL: Which is usually fun, but I try to keep it all business, Jason. (Laughter) There's no whimsy here today, folx.
JASON: We're not here to have fun.
JOEL: That's fun. You really got to define fun in your own way and this is how I entertain myself. I make money on the internet, so that's what we're ‑‑
JASON: Well, actually, I would loving to, you know, I feel like you have a sort of collection of things that you do, so for folx who aren't familiar with your work, do you want to give us a bit of a background?
JASON: Mm‑hmm. Yeah. And Ben, the chat, all I had to do is turn is off and turn it on again and it's back now. Thank you for the sub. Chris, 30 months? Oh, my goodness. It kind of blows my mind I've been doing this show for 30 months. All right.
JOEL: It's just relentless, like, the way you produce shows. It just blows my mind because I've done a podcast and, you know, I made it a year and a half and that was ‑‑ but it's just like you have to ‑‑ the steady habit of the thing is really, really quite interesting.
JASON: It's the ‑‑ I swear, it's the secret to everything, man, is you just keep showing up, right?
JOEL: Keep showing up. Do the thing.
JASON: Keep showing up. Keep doing the thing and also find people to help. Aiden helps me with the scheduling and he runs the website and the Twitter account. I've got Chris helping me with the editing of the shows and getting them posted up on YouTube. Without that stuff, there's no way I'd keep doing it.
JOEL: When you start a thing, you kind of have to do it all because no one knows what you're doing and there's no example of it. You have it in your head, this is what it needs to be, so you're making it. As you figure out what it is, you can bring collaborators in. That's always been a ‑‑ it's a daily challenge for me still but it's like, you know, your show, right, you know and have the vision, you're kind of the CEO of Learn With Jason, so you have to, like, figure that out over time but, you know, when you start bringing in people to help, it becomes more fun and interesting, too, honestly.
JASON: Absolutely. Yeah, and there's different ways to bring in help, right? So, I hired help for the things that were ‑‑ I don't even know how to start automating, but I also automated a bunch of stuff. And that's something that I actually learned from you, was how to make the robots do some of the work around, like, automation and marketing and things like that. So ‑‑ and that's kind of what we're talking about today, is this automation and how to make some things just go away so that you don't have to think about them. So, an example of what I've done on Learn With Jason is I use Calandly to book. I automatically have it set up to detect new calendar events, it automatically invites the captioning service. I've got Jordan on today doing captioning. It also alerts Aiden who does the actual booking part and kind of kicks off this whole initial setup so that all all I have to do is write a description for the episode. That's really, really nice to just have that stuff happen. A few clicks, stuff that I don't have to do, it saves me five minutes an episode but, you know, I'm at close to 300 episodes now and that's a lot of minutes saved.
JOEL: I don't think it's even about the minutes for me because, like, you are saving minutes and you have minutes to spare probably, right? Like there's minutes of your day where you could fit that in, but what it does is removes, like, a layer of tedium from the process to where you'll actually do it, right? Doing that same stuff over and over again ‑‑ when danger is jumping in and, like, I'm gonna automate this right away. Folx get in there, I'm gonna build robots instead of doing the actual work. I think that's an important thing to understand. You do this manually before you ever automated it. Getting in there and figuring out what you actually need. When you're your own user, like, talking to your users, that's a challenge, right? If we can lie to anybody, we can lie to ourselves. You know what I mean? Just as programmers, it's just easy to jump into the code and try to automate stuff really early versus, like, let's come up with an actual process that works and do it long form and then slice the valuable pieces off of that instead of, like, trying to jump to a solution, I guess.
JASON: Yeah, for sure. So, actually, that's a ‑‑ I mean, how do you make that call? Like because there is a spectrum of you got to do it do for yourself. You got to learn how the things works. You can delegate it to a person that you pay and you can try to build an automation. That automation comes with, you know, the upside of the thing just happens automatically and the downside, you got to keep that automation working, maintenance, all those things. What are things you're using making the call do it make it yourself, do I pay somebody else, do you build a robot?
JOEL: Good question. Like if I could give a definitive answer, I'd probably be doing a lot better. I have no idea, right? (Laughter) I'm always just trying to ‑‑ I kind of play it by ear and I do everything long form. I think I over‑index on automation and trying to ‑‑ I've done it a few times, right? Where I'll build a process, I'll build an extensive process. It's based on my experience and assumptions versus, like, going ‑‑ when I say, you know, talking to your users and it's difficult when you are your only user or whatever.
JOEL: If I'm doing it for a business, I know that what I need to do first is, like, find out what problems people actually have before I go in and, like, tell them that here's a solution, which is a very push model, right? Instead of getting in there and talking to people. What sort of problems are you actually having? A lot of my job these days is supporting my team, like what do they need in terms of daily process, what do they need to have what's automated in order for them to do their jobs better? And, you know, the idea would be that they would then support the users and we'd ‑‑ we're building kind of a layered step of that kind of support because, you know, like, when you're managing or leading teams, like, that's your job, right? You are there to support the team so they can better support the end users or customers that they're servicing. Whatever, you know, however those layers might be. Hopefully there's only a few.
JASON: Yeah, well, I've heard this described on, like, big companies. They'll spin up a whole, like, internal tools team, and the whole job of the internal tools team is treat the developers on the team as the customer and just build stuff to make them more effective.
JASON: And, yeah, so ‑‑ it is interesting. For me, at least, I found that I also have the tendency to try to automate early. I think it might just be, like, the toxic trait of developers. Oh, I can automate this task.
JOEL: It's fun.
JASON: XKCD rips this apart pretty handily. The problem that I've run into, for me at least, a lot of things I do aren't repetitive enough to be truly automated. Like, yes, I respond to a lot of emails, but none of my males are copy/paste, right? So, the only thing that really is automatable for me is Learn With Jason because every episode of Learn With Jason, it's on a schedule. It has the same format. You know, we do the same thing with a different guest twice a week. If it wasn't for that very specific, very predictable process, I don't think it would make sense to automate because, you know, the robots can't make a judgement call about, oh, well, we're gonna actually this time it's not gonna be, you know, two guests, it's gonna be four guests. You can't do that. It's got to be, like, very structured and that rigidity comes from, you know, just doing the show enough times that I realize, like, I don't need to make the format of the show interesting. The content is what's interesting.
JOEL: Right. Well, you know, at the end of the day, it's like, okay, you have a checklist and it's a checklist that you use every single time and, you know, the steps that never change, that's the stuff you can automate. You have to run through the checklist and refine that. That's where you can identify, right, parts of the process that now lend themselves well to automation and capturing. You wouldn't want to ‑‑ I don't know, it's like when you have users coming to your website and, like, say you're gathering emails, right? You have a new business and you're gathering some emails and you're getting 10 sign‑ups a week. At that point, I can literally Google each one of those individuals, reach out to them, ask them, you know, like why are you here,? What are you doing? How are you solving this problem currently? Like ask them questions. When you get to 100 users, your ability to do that for each one gets dramatically lowered. Can I automate and have a survey question? Are you a developer or a designer, you know, for instance? And you can ask them that. You can ‑‑ I'm really only trying to talk to developers right now, so I'm going to get 10 of those on the phone every week versus every single person that signs up. Slowly, you know, over 500 people on your list, right? Like as you gain momentum and you kind of hone in the solution or whatever it is you're doing to help those people, that's when you can really start thinking about automation. And I'm talking about, like, automating emails and onboarding sequences and that kind of thing, when I do this, just because we're gonna talk about customer relationship managers and all that fun stuff, right?
JASON: Yeah, yeah, yeah. So, let's talk a little bit about the specifics of today. So, what we have written down is that we're going to do marketing automation with Customer.io and Next.js. I know there's a ton of stuff that can be done. I know that you've shown me some of the systems that you use in terms of personalizing experiences based on what people do in emails. So, today, what did you want to tackle as our project?
JOEL: It's purely technical. I don't have a lot of, like, from the marketing perspective, like, the strategy. One of the things that always aggravates me is when I go to a website that I'm a subscriber or a member of and the homepage is, you know, it looks like I'm a new customer, right? Like you go there and it's like click here for your dashboards in the corner, but it's still the marketing front page. That's how it was on Egghead. It was difficult for us. We actually took the opposite approach where we didn't have a marketing homepage and gave everyone kind of here is the content, like our curated content section. What I would prefer is if you come to our page, like, what can we do at that point to, like, understand who you are? Do we actually know you? Why are you coming to the site? Are you one of our instructors? Are you a customer? All these questions we can use to present a varied homepage and, you know, like, if you're using any modern system, you can do that ‑‑ I use React, so you can have a use effect hook that checks and sees are they logged in and after load, it would load them in. But the problem with that is, like, you get flashing and stuff, right? It's just the user experience isn't that great. You redirect them and the URL changes or something kind of unexpected happens to the user that diminishes their overall experience in what they're doing. But with Next 12, Next.js 12, they introduced middle ware and it actually runs on ‑‑ before the page is served. So, it's at the server level. It's edge distributed, depending on where you're deploying to, and you get, like, this really nice ability to intercept requests, do some sort of work, and then write the page that responds. So, you can pick and choose kind of what page you want to respond or have, like, a fall‑through default that everybody ends up going to if they don't match something.
JOEL: We're using Customer.io, which I like quite a bit. It has good automations and kind of lets you do transactional and marketing emails, meaning you can send receipts and that sort of thing.
JOEL: Or marketing materials. Because legally, there are two different distinctions between the types of emails you're sending and what you need to do to do that. So, we've been using them a lot and I like them a lot. They have a pretty good API. It kind of allows you to just outside of your own user database capture user information and kind of use their kind of existing way that they attach to users in the browser and keep information. And even if they're not logged in, I can, like, know who they are because they clicked on a link in our email, that sort of thing. Which is really ‑‑
JASON: Oh, yeah.
JOEL: That's really cool because then I can capture that and display ‑‑ I know you're a customer. We don't need to sell to you. We can instead, you know, just ask you to log in so you can access your stuff, that sort of thing.
JASON: You're bringing up something that I think is really interesting because I know that data collection makes developers queasy sometimes. They're like, oh, no, no, put everything in private mode. No data collection at all. And that's for good reason. Like a lot of the stuff that companies are doing feels really shady to, like, scrape as much personal data as they can, but I think that, you know, that's the unfortunate, like, far end of that spectrum. Because in the middle, there's a lot of really cool stuff that we can do. I love the fact that when I go to a website, if that website knows who I am, it shows me, like, go to your dashboard instead of sign up now, here's a deal. Well, I already paid you. I can't get that deal anymore. It doesn't apply to me. Show me stuff that's relevant. Or if I have gone through part of the site, show me, like, what's next, you know? Hey, you did, like, step one of our thing. We know that now. And when you go to this part of the docs, we're actually gonna surface this section of the docs that you need or whatever it is. You can actually make a really personalized experience. And that's useful. That's not slimy. Like you can be evil, of course, like any tool can be used for evil, but I really, really like it when it's used to usefully personalize.
JOEL: Yeah, it's because it's manipulation at the end of the day. There is the white hat, gray hat, black hat style approach to thinking about that. I can do this in a nefarious way where I'm trying to manipulate you to some ends that only serves me and doesn't serve you. But the way I look at it ‑‑ and I think where it gets really sketchy is when you start selling and sharing the data across providers.
JOEL: To me ‑‑ they didn't request that. It's all about consent and not tricking people into terms of service. We always double opt in when we want to do that sort of thing for people when we're marketing to them just to, like, verify, you know, we understand what's about to happen. So, you're signing up for this and we're going to send this and try to be useful and not just sell you stuff. I think that's super important, especially the consent part of it, which, you know, just generally ‑‑ oh, my gosh. I started thinking about consent and then I lost my train of thought, J.
JASON: That's a really interesting way of making our apps more useful that is beneficial to the user, you know?
JOEL: Yeah, it's pretty straightforward. It's like they signed up for that. And at the end of the day, and I remembered my previous thought. This is how I think about it. I love state machines. I use X state, every application I write ends up with a state machine. Usually at a low scale. If you think about a user and their overall experience with your product or whatever, even, like, your blog, they're in a state machine at any given time. They are in a state and they want to transition from one state to the next. If you can understand, you know, like, these are the available transitions and present them where it's pretty accurate. It's not ever going to be really perfect, but you can say, well, I know this about you. You like this, this, and this. And this is what you previously have interacted with. Here's the next state or here's the selection of states that you have to choose from. And what we can do to, like, help you get where it is you're trying to go. Because nobody, like, for us, nobody wakes up in the morning like, oh, I need to go watch a course today. Like that isn't what they think. It isn't, right? They use our site because they have something going on in their lives where they need to, like, pick up that ‑‑ and some people do watch it, like Netflix. That's fine. If that's your entertainment, like, please, indulge. For most of us, it's like, oh, I'm trying to solve a specific problem with technology and there is this course, you know, the promise is that it can get me from "A" to "B" and that's what people are doing. Why are they there? Why are they interacting with your site at this time and how can we help them is always an interesting, you know, trying to be useful and trying to, like, present in terms of what they may want versus kind of the universal thing like dropping them into the soup.
JOEL: And that's a really great example, right? You're personalizing but you're really just, like, respecting what they've done and your time and kind of the saying ‑‑ Don't Make Me Think, that book is kind of the quintessential idea. In the title, it carries so much weight in terms of how to interact with users. I also want for say, most of these ideas for me, like, my primary source is Brennan Dunn and he captures like all of this really in a very detailed way in the book "Mastering Convert Kit," which to me is the absolute best kind of really value‑driven marketing automation that I've ever seen. It's Convert Kit, but I apply the principles in Customer.io and Drip. It doesn't matter, like, whatever platform you're on, his stuff is really fantastic. And, you know, like, if you want to ‑‑ oh, that's really interesting to me, dive into his work. Some great ideas in there.
JASON: For sure. Yeah. And those will be linked in the show notes. I just dropped them in the chat as well. All right. So, I'm excited to actually see this in action. So, let's switch over into our pairing view. And before we get started, I'm gonna just take a quick moment to shout out to the sponsors. We've got Jordan from White Coat Captioning right now is here making this whole show more accessible by doing the live captions. You can mind those on the homepage of the site, learnwithJason.dev. That's possible because of our sponsors. We've got Netlify, Fauna and Auth0 all kicking in to cover the cost. That means a lot to me. We're hanging out with Joel today. Make sure you go give him a follow on Twitter. We're going to be using Next.js and Customer.io to build some stuff. So, what's my first step here? Like what's the first thing I should do if we want to get going?
JOEL: So, we didn't set up anything.
JASON: No, we're empty folder.
JOEL: We can use Create Next App. They have starts. One is ‑‑ I always use npx create‑next‑app@latest space. Hyphen, hyphen ts. I use typescript. Do you think it would be useful to use Tailwind? That's all on you. You're in charge of visuals and whimsy.
JASON: Let's see how much time we have to do any visuals and/or whimsy.
JOEL: It doesn't matter for what we're trying to accomplish. We can use the stock page.
JASON: Let's start with the stock page and see how it goes.
JOEL: I'm going to say hyphen, hyphen example with Tailwind CSS because it's the only way I'll accomplish anything. And then space and whatever the folder name is.
JASON: Okay. Let's call this marketing automation.
JOEL: This just goes to their ‑‑
JASON: Okay. And it's creating the folder. Doing all the things. Get all the types. You know, I feel like I fought ‑‑ I fought it for a long time and I do like Typescript. I need to npm ‑‑ it already installed everything so we can just run dev?
JOEL: I think so. Hopefully it worked. I actually tested it to make sure I wouldn't be lying to you.
JASON: All right. So, we have ‑‑ see that opened in the wrong thingy. Here's our site. So, we've got a basic site here. All good.
JASON: And then I'm gonna open this up so that we can look at it. And then I'll start it again. All right. So, we've got a basic site. Here's our pages. We've got our index page. Kind of leave that all as is. But if I want to make a change, I can say, like, hello, chat. Save it. And it's there. So, we're in great shape. We've got ourselves a hot reloading app. Everything is good.
JOEL: Can we make just another page that has a different H1?
JASON: Yeah, let's do it.
JOEL: You can even copy and paste just as a base.
JOEL: Just let's call this one "welcome" or I don't know. It doesn't matter.
JASON: Cool. So, we'll go there. Get this. And for this one, let's just simplify it all the way down to just an H1.
JASON: So, we'll go all the way down to here. And then we'll get rid of all of ‑‑ maybe we'll keep the head. And we can go over to "welcome." Okay. So, now we've got a basic site.
JOEL: So, one of the principles here is that any page we make is a page that you can actually just generally get to, like Google index it, whatever. Exclude it if you wanted to, but it doesn't matter. This is a viable page. We have, like, welcome and we can just have our route for right now. We can extend that if we want to create more pages, we can think about that.
JASON: Okay. We got a simple site. No Bells and whistles on it. Our default style sheet.
JOEL: Did you change the text on them or do they say the same thing?
JASON: Oh, yeah, let's change it. Okay. So, there is our welcome page. Head back. Got our "hello, chat."
JOEL: My to‑do list was create a fun homepage, so I think we've accomplished that one. It's extremely fun. So, we can ‑‑ now we're kind of set up and we can display two pages. In Next.js 12, they introduced the idea of Middle Ware. So, we want to create some middleware in between the request for our homepage, and the way that you do this is inside of pages, you create a file and the name of that file is_middleware.ts.
JOEL: So, one thing I'm not aware of is if middleware runs into a local environment when you run NPM run dev. I'm curious about that. The only way to find out is actually run some middleware. When I do this, I'm going to go to the Next.js docs, specifically the GitHub repo and look at the example and they have a done of emailed middleware example.
JASON: Let's see if it picked it up. It doesn't say anything. If it worked, it's gonna ‑‑ okay, there it is. There is our middleware. This is the interesting thing about this because what we've done here, returning a new response, we're basically saying we don't care what's on the page. Just delete it all. Replace it with this. And so, you know, we can put ‑‑
JOEL: We have officially ruined our fun homepage.
JASON: And does this ‑‑ does that do what we want?
JASON: No, it's plain text. So, we would actually have to set it as text HTML and things like that.
JASON: Mm‑hmm. Yeah, let's see, how would we do that? We can get a new response and then we can ‑‑ create one, though. It's all showing us how to use them.
JOEL: Honestly, we're gonna ‑‑ you also I think ‑‑ and I believe when we imported ‑‑ I don't know, it might just be types. Did it have the ‑‑ in our actual code?
JASON: It did.
JOEL: So, we're not using Next Response. So, they actually have a helper called Next Response. You can use a normal response and return that. We'll get pretty quick into using Next Response. But what we want to do is, like, let's get our page back, is what we want to accomplish. So, inside of the middleware function if you say const response. = next response.next. And what this does ‑‑ I honestly haven't looked up the docs for why this works. You don't need any arguments. You can just call Next.
JOEL: We're gonna return that. So, return that response.
JASON: Why is it yelling at me? It was imported using, oh, import type.
JOEL: Yeah, that needs to be on its own line. The other two are types. Just Next Response needs to be by itself.
JASON: This one by itself. And this one.
JOEL: Totally not used to that error message but I've seen it in the last couple months. What this should do is return us back to normal. Hey, there we are. That's just grabbing the response and saying, like, Next ‑‑ what's the ‑‑ I would read that probably and see what's actually going on. It's just what the example said and it works. It just basically gives you that raw response that we're looking for. So, at this point, like, I'm setting up a switch statement. So, we want to do something ‑‑ and this is an issue. So, if you've done this at the root, if you set a middleware at the route. It would work better if it's broken. We can maybe return a response or something. The problem here is that it is ‑‑ it nests or it's ‑‑ how would you say it? Every page in your entire tree is going to run this middleware. Everything under it. Because this is the top level and we're dealing with our root page, it's actually trickier than if you're in a sub folder because only the stuff in the sub folder would be affected. This is actually gonna affect our entire app, which isn't what we want it we want to only affect the top one. The easy way to do that is gonna be with an if and we're ‑‑ and we have the request, so we have the Next Request there that's coming in. Request.NextURL.pathname = just forward slash. Yeah. You know, you can test this by making a sub folder called "test" and putting index in there and running it and see that this does not affect it. I always try to verify that sort of thing just to make sure because this is ‑‑ we're at the top level, so this can return. That's a good idea.
JASON: So, if we do it like this ‑‑ oh, I got to do a new response. Then this one should give us.
JASON: Nothing, right? But if I go back to the homepage, if I can click this right, we're top level. So, now we've got only one page being affected. That's good.
JOEL: Another problem here ‑‑ and this is strictly because we're in the root and we're trying to affect the homepage. The problem is we show the user the marketing site and not their dashboard, is the core of that. A problem here is that, you know, anything like a fav icon or images folder or essentially anything that's in your public folder, like your site map, like, everything like that is now going to return your ‑‑ whatever it is you set up. You know, previously when we had the response, you'd get that for your site map, for instance.
JASON: That's a good point.
JOEL: So, I'm going to ‑‑ can I just use the chat? How do I do this? I'll send it to you this way.
JOEL: Pasting into your Twitch chat. Up at the top, I'm going to set the const. This is public file. I cut and pasted this originally and it just works. So, there we are. And what this is ‑‑ what we're gonna do is we're gonna do kind of a ‑‑ just a test. So, once we get that under response, we're gonna say if public file.test. And then we're gonna say the same request.nextURL.pathname. Return response.
JOEL: So, like, this is our default, right? If this is some sort of public file, now we can get our fav icon back. That would be any Style Sheets. There is a lot of mischief. We're making a lot of mischief doing this at the top level. Some of this is mitigating. I learned most of this the hard way. (Laughter) I'm just throwing it out there as we go through, because just the nature of this. Like I said, it's a lot simpler if you're somewhere else, but the top level is a great place to start. You can use these techniques elsewhere. If I was in a sub folder, I wouldn't need the public file because I'm not going to have that in those routes. This is only valid because we're at the top level.
JASON: Yes. And for anybody who's not familiar with what this means, basically anything that ends in a .something extension.
JOEL: Thanks for explaining that. I wasn't familiar either.
JASON: This is zero characters and this is an escape period. In this, thing.jpg matches. Thing doesn't. So, this would get passed straight through and this would move on to the next rule.
JOEL: Very impressed with that explanation. (Laughter) All right. So, we've got that. We've got our two safeguards. We aren't gonna serve any public files. We're getting into the path name is. Now is kind of where it gets interesting because we're at the route. And what we want to do, let's just, like, go in and open the browser up In DevTools.
JASON: Browser In DevTools. Let's do it.
JOEL: We're going to go over application and cookies. For this site. So, local host. Let's just create one and we're gonna call it "welcome" and it's just gonna say "true."
JASON: How do you ‑‑ there's a way to do this.
JOEL: There is. Usually, I just click the second line, like, the blank line under the first line.
JASON: Oh, well, that's easier than I thought it was gonna be. Sorry, what did you want it to be?
JOEL: We'll just say welcome. The cookie name will be welcome or true or one or anything literally.
JASON: Okay. So, that should all be good. We'll leave the rest of it out for now. Okay.
JOEL: All right. So, back in our code ‑‑ now we have a cookie, and normally you'd write that from someplace else, right? Users aren't gonna write cookie ‑‑ well, most users. I've wrote a lot of cookies in my personal life, but ‑‑ now we want to check. The way we can do that, the request carries a really nice object called cookies. So request.cookies. This is a Next request. If you're dealing with a browser request object, you're not gonna get this. We're gonna use the square bracket notation to access an object and the text and we're gonna say welcome. For me, I'm gonna console log this just to see if it comes through. That's always my move.
JASON: Okay. So, that will show up. Let's go here. Then it's gonna show up in this console log, right?
JOEL: It should, yeah? Did you refresh?
JASON: Did I refresh? Save.
JOEL: And are you on the route page.
JOEL: It's working as expected. If you're on welcome, this stuff doesn't happen. We're where you want to be. Did it log?
JASON: Yeah, log true down here.
JOEL: Okay. Oh, I'm off screen. That's why. Cool. So, now you can start thinking about this, right? One, we're on the server. When we're setting those cookies, right, you can set them as secure, if you're on, like, locally ‑‑ it doesn't matter, but, like, if we're on a server, we want them to be secure. I'm usually going HTP only. We're not gonna be able to read this cookie necessarily from the clients. I just want to, you know, set it up to where it's ‑‑ you know, a nice safe, secure cookie. We're not sharing it around. We are writing that for our own uses. We didn't do that here, but that's typically what I'm gonna do. What's interesting is if it's a server cookie, you can read that in these requests and then, like, you can intercept that. So you can read and write these cookies and then, you know, like, cookies have been around for a long time and they're pretty well‑documented but they're really useful for this kind of thing. You're storing sessions. You're storing that kind of thing. And we're gonna basically put some data in in the browser cookie that we can read on the server side and then adjust the page accordingly. So, when we get that, we can go in and add another "if" statement and look and see if welcome is true. Then we're gonna change our response.
JASON: Okay. So, I'm gonna do a little bit of, like, so we don't end up in the, like, bracket hell.
JOEL: Gonna refactor a little bit?
JASON: I'm gonna refactor. What we can do here is return the response. That means we can put this one over here and we can say, you know, if ‑‑ and I'm gonna ‑‑
JOEL: So, that's actually gonna go in the if statement above. We're gonna switch it and I'm gonna use a little switch statement trick that Kyle Shevlin taught me. If next URL equals forward slash, that's where we want to check the cookie. We don't really care about that cookie. We can just copy that whole if into of the ‑‑
JASON: Like same place?
JOEL: No, no, in the body of the if statement. We're gonna check to see if it's the route page. Inside of that, we're gonna check and see what our cookie value is.
JASON: Got it. So, I'll undo my refactor then. And let's check if it's the cookie. And then we're here. All right. So, if we're here.
JASON: I'm pretty sure this is a string. You have to convert it from a string to whatever the thing is.
JOEL: So, this is where this gets really cool. Now we can say ‑‑ if this is the case ‑‑ if welcome is true, we want to say "response = NextResponse.rewrite. It's gonna rewrite the page with the response it gets from the following URL and we're gonna say /welcome.
JASON: Oh, okay. Cool things just happened.
JOEL: We can refresh now and see what happens. This is the core of it. At the end of the day, if you can do this, now you can literally do anything that you want. And there's some restrictions there, but, like, you can literally do anything you want. And serve different ‑‑ so, you can think of this ‑‑ and we can just go to the switch statement. I use switch true.
JOEL: I don't know if you've seen this. Just below the if ‑‑
JASON: Below the if?
JOEL: Yeah, yeah, below this one.
JASON: Switch true. Okay.
JOEL: So, the case in this case is we're gonna use the condition above if so the BooleanRequest.cookies is our first case.
JOEL: And then response rewrite. Same body. And then you want to have a break. Always got to remember that or it just falls through. We're gonna ‑‑ no, that one's gonna actually be the ‑‑
JASON: Oh, wait. Break is the word I was looking for. Beark.
JOEL: So, we can encourage them or show them or offer discounts. At this point, you can do all sorts of fun things that are useful, too. So, like, literally you can do things that are fun and also things that are useful and hopefully mutually profitable from the value sense.
JASON: Yeah. Yeah, yeah. Okay. So, now we have the ability to make decisions based on conditions. But right now we are kind of hard coding this stuff. So, we could, you know, we could also check, like, if we wanted to make another page, we could set it to false or whatever, but I'm assuming this is ‑‑
JOEL: Go ahead and set that to some other value, right? I don't know if ‑‑ yeah, like ‑‑ I'm wondering because of the Boolean if it's not un‑defined, would it be true? I'd probably say cookies welcome = = = true.
JASON: Should it be like that?
JOEL: I'm going to do a text search. In my code I'm looking at I'm using ‑‑
JASON: Oh, you're doing it like.
JOEL: I'm parsing it. I'd check and see if that value is the string. Unless I'm parsing it as, like, JSON.
JASON: Okay. So, that does the thing.
JASON: This does the thing.
JASON: Does the thing. Okay. All right. So, we got it working. It's correctly responding to the value of our cookie. And now we want to start setting these cookies, I'm assuming ‑‑
JOEL: So, the cookie is an interesting thing. But then what if it's, like, remote data of some sort? That's where, to me, it gets really interesting. This is where customer ‑‑ I was like, well, I'm using Customer.io, but it doesn't actually matter. What the lesson here is is that we are loading data from some remote source. So, what's your, like, go‑to if you want, like, quick cloud data? Do you have an API that you use? Things I go to like Supabase. I think of Customer.io as a database in the cloud. Supabase, you know, Hasura, it doesn't matter. Where is your place that you go to? Sanity. Netlify CMS.
JASON: Yeah, whatever, right?
JOEL: At that point, it doesn't actually matter. What we want to do is load data from some remote source and use that to check the current condition of the user. Customer.io is something you can sign up for. I don't know, it's pretty easy to ‑‑ honestly, it's not that user friendly when we start using the API. So, that's why it occurred to me that maybe using something else. I love Supabase. I don't know. What do you love right now? What's been fun on the data front for you lately?
JASON: I mean, I kind of bounce around between a bunch of them. I'm trying to think of what I've been using most recently. I actually haven't used Supabase because I've been waiting for John Meyers to come on the show. He and I got one coming up soon.
JOEL: If you have some way you get data currently.
JASON: Let's do Fauna because they're a sponsor.
JOEL: Oh, yeah, good idea.
JASON: I got ‑‑ I have an account. I know that all works. And that way we don't have to think too hard about Fauna stuff. All right. So, I'm logged in. I'm gonna create a database. And we'll call this one "marketing automation"
JASON: Put it in the U.S. All right.
JOEL: This is the move over Customer.io. Because it doesn't matter. That's the whole point here. You can literally, like, use the Twitter API, I don't know. You can use all sorts of data sources at this point.
JASON: All right. So, let me do a ‑‑ I'll just create a new file. We'll do this db/schema.GraphQL. And we'll put a ‑‑ what do we want, like, a user type? Give them an ID. Give them an email. And what else do you need?
JOEL: Um, I don't know. That would work. You're gonna be the user. So we don't we don't need ‑‑ at this point ‑‑
JASON: Oh, okay, I'm the user. Yeah, so what do you want to switch on here?
JOEL: Um, let's see. We use, like, technology ‑‑ customer. We can have ‑‑ well, user's fine. I'm just saying ‑‑
JASON: Oh, okay.
JOEL: Is customer, basically.
JASON: Oh, gotcha, gotcha, gotcha. We'll do customer, whether or not they're a customer.
JOEL: One of the cool things about, like, tying the email automation to this process is ‑‑ and, like, it's really ‑‑ and I think we've talked about this, actually. If I recall, the previous time I was on here, we were talking about Convert Kit and, like, setting cookies and loading data from Convert Kit. And what's cool when I do that is when I send email ‑‑ automated emails from a service, most of them have the ability to attach a user identifier to the link as a query parameter. So, what's neat is, like, if I send out emails and they click that, when they come to the site, it's actually carrying that with them. That's, like, a mode of persistence beyond the cookie which is browser session. If they click on the phone, I know who it is. If they click on their desktop, I know who it is. If they click on a computer at the public library I know who it is and can use that information accordingly. We're not going to get that here. It's almost like creating a session and having a logged‑in user. This is more useful versus dashboard versus straight marketing page kind of pitch. What we're effectively creating is a user. And then don't model your user sessions this way, please. Use some other way to do this. But that's what we're ultimately doing, I think, is that ‑‑ mimicking that approach.
JOEL: Versus the email cycle. Which I love, but is more complicated.
JASON: So I'm just gonna take a couple of notes here. That's gonna be our endpoint. I need to set up some API keys. We're gonna give this a server key and keel make sure that it's ‑‑ and this is gonna share the key. So, nobody mess with this right now. I think I can make it ‑‑ can I make it read only? Whatever. We're not gonna worry about it too much because, you know, it is what it is.
JOEL: A lot of hacksters. That's good.
JASON: We're probably gonna get hacked. Give the chat five minutes. Can you put in a .env and it will work or do you have to do it a certain way?
JOEL: It just works. .env.local is I think.
JASON: Okay. And then we'll do, like, Fauna API key. And stick that in. I still can't figure out how to turn that thing off. Okay. So, now we should be able to do ‑‑ assuming I have access to Fetch, I can go to my Fauna endpoint. And we can hit that as POST. We can give it headers of authorization and that gets a ‑‑ do I remember this off the top of my head? Bearer. And then we put in the process.env.FAUNA_API_KEY. Then the body is gonna be a JSON Stringify of a query and variables. Which variables is gonna be an empty object and the query can be ‑‑ we want to get the user. So, let's actually go over here and build this out. We can ‑‑
JOEL: Yeah, and at the end of the day, you know,, like, you have to make some assumptions about the user. We store is in a cookie, right? Whatever the ID is, so the user ID is what we've have in the cookie and we'd say, like, Next User maybe is the cookie name. Something like that.
JASON: What did I do wrong here? What? Oh, it's this ID. Is it? Hold on. I think I did a ‑‑ I think I did a bad thing. Okay. You know what? I'm not gonna fix that. I missed something in the GraphQL schema for Fauna that would, like, identify this as the actual ID field.
JOEL: Personally, I like the other one better because sequential numerical IDs are guessable and are prone to more mischief than other types. So, that's fine. And ultimately, we're gonna want this in some sort of function like load user or whatever, and that's gonna be async. And we also want to make sure that our middleware function that we turn is also async so we can do that type of thing in there.
JASON: Okay. So, we'll go async. That means I need this to be ‑‑ let's get a response. That's gonna be will await Fetch. And then down here, we can do an if ‑‑
JOEL: You got to ‑‑ oh, yeah, you got it.
JASON: Response okay. And then we can throw new error. Very helpful error. And I need this one. And then down here, if that works, we'll ‑‑
JOEL: Fetch, you don't have to ‑‑ did you to the to JSON?
JASON: Yeah, that's this one here. This is a thing I picked ‑‑ I don't remember who I saw this from
JOEL: I didn't know okay was a thing, but that's cool.
JASON: It's helpful for the quickest of error handling. Then I'm gonna do one more thing here, which is to add the ID as a variable.
JOEL: Do that for sure.
JASON: So that we can pass that in. Pass in the ID like so. And then down here ‑‑ so, now what'll happen is we have to pass an ID into this function and that will send it back. So, to use it in here, we can do something like customer = await. Console log it. That will let us know whether or not we did the thing. That should happen on any request. So, let's see if it worked. Where's my ‑‑ this one? This one? This one? Here.
JASON: Refreshing. It did the thing. Invalid database secret. I did something wrong. Oh, maybe I need to stop and restart.
JOEL: Yeah, it doesn't pick those up. I'm guessing.
JASON: Okay. Good. And now, variable ID, expected type of ID. All right. I can write valid GraphQL. There is our user.
JOEL: Oh, nice. So, now you can take that instead of hard coding that, we can move that to our cookie, the ID that we're looking up. We're gonna assume we have the user's ID in a cookie versus in the application.
JASON: Okay. So, let's add one. We'll say user ID. We'll put this in. We'll click all those same boxes. Good? Yes. All right. So, now we have user ID. And so in here, we can get ‑‑ gonna be req.cookies user ID. And then I'll drop this in here. And theoretically speaking, when I refresh the page, we get ‑‑ there it is. Okay. So, then if I go and I switch this out to our other one. Let's go to our collections. And here is ‑‑ what? Okay. Whatever. Go back out here. Change our user ID. Then when I refresh the page, we see our other customer. So, there we go. We're now looking up from a database as part of our middleware. That's pretty slick. All right. So, now that a have this, what should I do with it?
JOEL: So, we're actually gonna move that into that switch statement, right? We want to switch based on ‑‑ like results, we can load that before the switch statement. It's part of our cookie ‑‑ basically if you have a customer then you want to rewrite ‑‑ if you don't, I'm probably gonna, you know, check the cookie first. And if we have that cookie, if that cookie exists, so, if it, you know, you can do what you did before where you just drop the Boolean in there. I don't think we really need that welcome anymore either but we can keep it. They're gonna stack, right? The first one that's true is gonna fall through. That was just a test.
JASON: We'll check if the user ID.
JOEL: Just exists at all. So I think you can remove that whole thing. If it exists then we want to load it. We want to remove that const customer. Just the customer part. Not the user ID part. Into ‑‑ because the user ID, we want it external because we need that before we started looking at cases. But if that's true, then we want to load the customer. We don't want to do superfluous activity. If it is a customer and the current customer is ‑‑ so, at this point, you'd also check to see if they are a customer. This is where I'm also gonna start thinking about how do I extract this. Something to note about middleware while we're talking to different things. You have access to some APIs, but it's a restricted environment for, like, Cloud Flare, people have these Node run times, right? That are reduced Node run times in these environments to be safer. So you can't just import everything under the sun and get it done. You have to have, like, a constraint. I know Fetch is okay and there are some things. There's just limits.
JASON: Yeah, because it would be, like, pretty destructive if we were trying to do, like, a bunch of files, like, FS, file system access.
JOEL: You might even have that. I don't know exactly what it is, but there are some ‑‑ just something to be aware of that can be gotchas. If you start importing stuff and throws weird errors, it's usually your imports. Like they're doing something, browser access. I don't fully understand it. I know you're in a limited run time. We can say user.customer but you can keep = true if that's what you like. At that point, we can write the response. We can say response = rewrite welcome because we're welcoming them if they are a customer.
JASON: Okay. And otherwise ‑‑
JOEL: Otherwise, it's not gonna hit the default at that point, but it's not gonna change it either because we have a default default that we've done before. So, we should be good. And at this point, because our customer is true, and you can switch them out. Now you'll see if your customer ‑‑ if you are a customer, it's switching it out.
JASON: I think the one that we are now is true. So, this should give us welcome.
JASON: Uh‑oh, I'm doing something wrong. Let's see. It says our customer is true. Oh, oh, oh, I know what I'm doing wrong. We got to get the data. Yeah. So, let me actually return that out of our customer. And we will return data.find ‑‑ what was it? Data findUserByID and that will give us our actual customer. So, now ‑‑ what?
JASON: Oh, boy. Oh, boy, what have I done? I returned response. Cannot read properties of un‑defined. It's no longer doing the thing, which means it is ‑‑
JOEL: Yeah, what is data?
JASON: Data, data? Oh.
JOEL: Data, data.
JASON: Data, data. And now we've got a customer. Okay. So, let me ‑‑
JOEL: If you switch that out, we'll see the ‑‑
JASON: All right. So, I'm gonna go grab my other user. Which is this one. Drop it in here. Reload. And we go to the homepage. Okay!
JOEL: Yeah. And, you know, like, that's ‑‑ I mean, it's great. What could go wrong here, do you think?
JASON: Like infinite things, I would assume. Well, actually, let me think.
JOEL: Top handful of things that could go wrong at this point? Now that we're doing async stuff. That's a can of worms, right? You start doing that.
JASON: Fortunately, we've got good safeguards here.
JOEL: I skipped some of the stuff, like, we would have been here for three hours like I was or losing full days of work and sleep because we couldn't figure out what's going wrong and affecting our customers. We solved some of those already but there's more.
JASON: So, there are a couple of things I'm seeing here. Like if our request share fails, we don't have a catch. We don't have any way to catch that. So it would just blow up the whole page instead of sending a default response. If our user ‑‑ we're not doing any checking to make sure that, like, this ‑‑ I guess this would ‑‑ this would just return un‑defined so it would fail and break and hit our return response.
JOEL: The tri‑catch is a big one. Generally speaking, I'm gonna wrap all of this in a tri‑catch because, like, if everything fails, I want to log it and send it to Century or whatever.
JASON: Yeah. So, let's do a big tri‑catch. Just take all of this. If we get an error for now, we'll just console log it.
JOEL: I would just return response.
JASON: So that way our app doesn't fail but our customization fails, and that is okay.
JOEL: And technically, we'd keep our single return response. If something bad happens, we just need one of them. We don't need two of them.
JASON: I see what you mean. We can do that.
JOEL: Because we're setting response, we only need a single return response. We don't need to stack them up like that.
JASON: Yeah, I gotcha. Yeah. So, that gets us ‑‑ that's looking okay. I mean, this is at least safe enough that, like, if we had done something silly, we at least and unfortunate do nothing. It just returns the page.
JOEL: Here's something that I learned when I was doing this. Is that when you ‑‑ I got to ‑‑ hold on just a second. All right. So, if you're doing this locally, you're in a specific run time environment.
JOEL: If you do this remotely, you're in a different run time environment with new restrictions. Locally, this could take three minutes for a response to happen.
JOEL: But when you deploy this ‑‑ and it really depends on the environment where you're deploying it so I can't tell you specifically. I assumed it was, like, a serverless function and read the docs. You do have a limit on your serverless functions. It goes to Cloud Fair or AWS. Or whatever if you're using Lambda. You have a run time limit how long these processes can spin. Some of them are pretty big. For this one, for middleware, as it turns out, it took me longer than I care to admit. On Twitter, support was helping me and everyone's doing it. As it turns out, unlike serverless functions where it's, like, an eight‑second time‑out or 15‑second or whatever, you have exactly 1.5 seconds to return a response from this function or it's going to time out. Because this is a middleware and because you are running an edge function just for a specific purpose, the response time‑out is 1.5 milliseconds. The actual function itself after you return the response, you can actually still do work. So, you can return the response and be processing data and doing other things. That can take up to 15 or 30 seconds. You have this good amount of time. But if you do not return a response within 1.5 seconds, it throws an error and it logs nothing and you get, like, I got no clues as to what was going on.
JASON: Oh, okay.
JOEL: I'm calling the Customer.io API, which unlike modern, you know, edge functions where it's distributed and it's close to the user and this is actually part of the issue, those functions are getting distributed. Folx in India and Africa were having a really hard time because the Customer.io response took a while to return. Over 1.5 seconds. So, what I needed to do was build a safeguard around the Fetch call, which in my case is ‑‑ I should probably just share a link, huh?
JASON: Yeah, yeah, let's do it.
JOEL: Paste it in chat. So, this is how I load the customer. Where do I give links to you? Is it Zoom?
JASON: No, I'm grabbing it right out of the chat there. Okay.
JOEL: So, here's my full, like, loader for Customer.io. At the top, you have the fetch, which is kind of what you wrote, but you'll notice there is some interesting work here. One, I have a time‑out const that I'm setting. After I set a time‑out variable ‑‑ I think that's what ‑‑ I should look at mine. Is it timed out? False. That's how we start. We go in and we look and there's a ‑‑ 1.25 milliseconds basically. I add some padding. This is when I love to use comments. I'm not a big commenter in my code, but this one I'm commenting because, like, how would anybody know otherwise? Should probably have a comment link to the docs as well.
JOEL: And then I have a set time‑out. So, I run the set time‑out and capture the ID of the set time‑out. What that allows me to do is cancel the set time‑out later. It says timed out equals true and actually just rejects the promise. You'll notice it's wrapped in a promise up top. Which is an async function wrapped in a promise. That gives me the result reject. Then we fetch. And we have ‑‑ I used Fetch. I'm capturing it like a promise so I have ‑‑ return the response with normal. If it's timed out, then we skip it because we've already sent a response at that point anyway. So, there's no point. Finally, we clear the time‑out ID regardless of what happens.
JASON: The .finally is one that I feel like I almost never need it, and then when I need it, I'm so happy that it exists.
JOEL: Yeah, I mean, like, in this case, you could have that same thing in both of them, but it's just nice, right? To me, it reads really well because promises can get kind of bulky. Reduce repetition in a way that makes things clearer not in a really abstract way where I'm moving it out to some other location or whatever.
JASON: Yeah. I mean, this is useful. Probably ‑‑ we got, like, 15‑ish minutes left. So, we probably won't implement that here. We'll put a link to this in the show notes for anybody who wants to look at setting up the time‑out.
JOEL: This is critical. Like if you're actually gonna do this, this was one of the hardest to figure out. And you can look, the link that I pasted is, like, our entire website so you can literally see ‑‑ this is our production website. We work with visible source. No support implied or given. (Laughter) That's the way this is. But this approach works really good. And after, you know, like, I got it dialed in and the little failsafe here, I can do this and it's great. And the next thing I want to do, and we can do this now for what we're working on. When we get that response, you know, like, what do you do with that information? And what I want to do is once I have that user data, I don't have to load it on every single page load. Every time they come here, I don't have to load that. Instead, we can write a cookie, too. I can load the user data and write ‑‑ so we have the ID, but we can actually write the user to a cookie in this response. It's basically cached at that point. If it already has the data and the data matches what's in the cookie, it lets you return that instead of doing anything async, making sure to give the user the fastest response, which is what they do deserve, I think.
JASON: Yeah. So, is there a way I can ‑‑ do I, like ‑‑
JOEL: So, basically, if you go to the ‑‑ we have a little case there where we know we have a customer.
JOEL: And we have if user.customer = true ‑‑ that doesn't even matter. Even blow that. If customer ‑‑ if user.customer. Or if user, right? So, we can say if we have a user at all ‑‑
JOEL: ‑‑ we can then call response.cookie. And we're gonna say with the string user, that's the cookie name. So, you can call response.cookie is a function that actually writes ‑‑
JASON: Oh, it's a function.
JOEL: Yeah, response.cookie is a function call. You can call it a couple of ways. It's also an object. I don't know why or how that works. I'm going to say JSON.stringifyUser.
JASON: Oh, okay. All right. Okay. So, then that means that up here I would want to clerk ‑‑
JOEL: Well, I don't even know if I need to do that. I would do it, like, personally, I'm gonna do it so where we have "user" here, that's where I'm doing it, right?
JOEL: The way I actually did it is passed it into my load function. I passed cookies customer ‑‑ or cookies user in my load function as a second argument and then just handled all the, like, if it exists stuff in that particular. Instead of just, yeah, just like that. If it's blank then we handle it in there. We're checking it later, too, because we'll write it back regardless. But then, like, our caching is handled in our load function versus in our middleware. Usually, like, this load function's gonna be off somewhere else for me. You can use this inside ‑‑ with Next if you have a get server‑side props. You can use a load function to do similar work in other pages. Or in React, you can use React Query or SWR or whatever and use the same exact function to load data at run time. Not only on the server. There might be some issues having server cookies and loading some of this data from the client side. If it's server ‑‑ if it's HTTP only, I'm pretty sure you can only load that from the server.
JOEL: You can pass it through.
JASON: Give me somebody's email address because it won't run. Your serverless functions, that's your code. You know what you're doing. Assuming you are not doing evil, which you shouldn't be, chat.
JOEL: Yeah, definitely.
JOEL: Usually git server‑side props is similar. They're all in the same chain. I can run that and pass data from here to there. Rewrite with query parameters and that sort of thing. Really have a good amount of data that you can pass along to the final request or whatever. I can load that user in a secure cookie, which is better. You know, especially when you're talking about developers and the space that we're in, they're often looking at what you're writing in the cookies about them. So, it's just good practice.
JASON: Yeah. So, one question I have about this. So, first, this is what I just implemented, which is I'm checking if the cookie exists. I'm parsing it if it exists. Otherwise, I'm setting it to false. And then if I have a user, I'm just returning that.
JOEL: Yeah, just bail.
JASON: Do I need to manually expire this? Or it will expire once they leave the session, right?
JOEL: By default, yeah, it's session. If session works for you, that's fine. Or you can set it to a time. Which ‑‑ whatever, you'd have to go look up the API.
JASON: So, like, in a ‑‑ thinking for what I would probably do if I was building this in production, just, you know, if any of you are thinking about implementing this yourselves, I would probably set the cookie to live, like, a week or 30 days or something, but then you have to go into your user management stuff, and if somebody cancels their account, if my unsubscribe, if they do whatever user actions that would affect their user, you then also have to manage the cookie as a part of that process, instead of letting it expire. Which means the next time they come back, they'll pull the fresh data. All right. So, this is cool. This means then ‑‑
JOEL: A lot of times, especially with this one, right, I don't mind if sometimes I get this particular one wrong. Because I'm applying middleware to the very root of my site, sometimes if that misses the mark a little bit, I'm not as worried. In my apps, I'm going to, like, load and write that cookie into other places also. So, I'm going to still use git server‑side props on some pages, and maybe even the welcome page where the welcome page is gonna use the same call. If I've already been through this flow, that cookie's gonna be cached, right? It's not gonna affect git server‑side props. For some reason. It's cached at this point. We have to invalidate it and have a policy in place that makes sure that our data is as fresh as it needs to be to make sure the user experience isn't broken. So, that's kind of ‑‑ you know, it depends on the data at the end of the day, and what it is you're loading and who you're loading it for, I think, matters a lot also.
JASON: Screwed that up. Here we go. That should actually do the thing now. Yeah, then we get back a cached user.
JOEL: And you should check and see, you know, does the ID match? Is another one. So, like, in your Fetch, because that's another thing that could happen. It could be two different users. So if it's not the same user, you need to load. And cachedUser.id will do the trick for sure.
JASON: Okay. So, that does the thing. And then ‑‑ so, that's our user that is not a customer, right? So, if I go in then ‑‑ I mean, let's force the failure case. I'm gonna ‑‑ I'm gonna pretend I'm a hacker and I'm going to ‑‑
JASON: ‑‑ modify my cookie here. Reload. And it did not hit the cache. And it's showing me the true thing. So, that was a good check. I wouldn't have actually thought of that one until somebody told me that they were getting an error ‑‑
JOEL: So, we do that a lot. Customer.io is fantastic for this because you can dynamically update properties and it keeps a really nice database. We take that across our topics. We tag everything at the topic level. And then are increasing scores. We're not using it very well. We've been doing this. It's called, like, lead scoring is the marketing term.
JOEL: We want to actually help you, like, learn and do what you want to do not, like, force you into, like, an anxiety tunnel so you can buy, buy, buy or whatever. Long term, it works out better, the more helpful we are as an organization. That's a great approach. Customer.io does a really ‑‑ that's the reason I actually started using the tool. It has some great stuff going on, but their ability to use it as a very lightweight database with a decent API, it's just ‑‑ it's ‑‑ and then it ends up being friendly to members of your team that aren't developers, right? Sending somebody necessarily to a Fauna or a Supabase or whatever, they're great applications but they're really made for developers. They're, like, maybe even out of the low code territory. And this is ‑‑ Customer.io, once it's set up, is very in the no code territory.
JOEL: For other folx who aren't developers on your team, which is very nice.
JASON: Yeah, I do like kind of the effectiveness of these tools that let you go, you know, no code if you get into the UI and then low code if you want to kind of integrate. You can get pretty wild with API customization if you want to really build out some complex stuff. It's a hard abstraction to get right but it's really exciting when it works. I'm also really glad that Chris picked up on NSYNC reference because I very much wanted to start singing when you did the bye, bye, bye.
JOEL: I mean, if you feel that, always feel free. For me, anyway, I'm here for it. For sure. You know, like, there's a lots of tools that do this. There's a lot of different ways to approach this. I think this core of what we talked about today is really powerful, though, and I got so excited when I saw middleware get announced. This was, like, the first thing that jumped into my head because it's a problem that we've had on our site and hated across the internet. Why don't ‑‑ if you know I'm a customer, show me the dang dashboard. Don't show me the marketing page every time. It's annoying time. I'm easily annoyed so I don't take it to personally when sites do that to me. At the same time, it always felt like it could be better. I feel like we lost a lot of that in our client side and static side and all this stuff. You can still get the benefit of that. Static site generation, it doesn't affect that. You can do that all day long. Keep it simple, though. That's probably ‑‑ having a good segmentation strategy and understanding the, like, one to five different segments that you are ‑‑
JOEL: ‑‑ actually servicing. Something like this, I want to keep that very simple and not, you know, try to do, I don't know, like, you can ‑‑ I get ‑‑ you could probably abstract it and make it less complex and not so if statement‑y with larger segments and do fancier stuff. At this point, because of that 1.5 seconds anyway, you got to keep it lean.
JASON: For sure. We got a question from Tony. What's the customerless size that's the sweet spot to start paying for this kind of automation or is it more ROI‑based for you?
JOEL: When would I sign up for Customer.io?
JOEL: Gosh, I don't know. You need to have it, right? How are you gonna get by otherwise? As a developer, like, you can go to something like Supabase that is probably gonna be less expensive to get into. And Customer.io is kind of pricey. And something like Convert Kit with their free tier is ‑‑ I don't know if you get API access, though. They know how to get the money from you. I'm kind of spoiled because I have revenue and I'm able to fund some of this stuff. If you're talking about, like, I'm bootstrapping a business and I need to know when I should start paying, gosh, that's a hard question to answer.
JASON: Yeah, I mean, I think it's how serious are you about it, too? Because I have things that I would love to go in and do more automation on, like, you know Learn With Jason, but it's not my full‑time job. It's not something that I'm gonna put tons and tons of effort into. And so spending a lot of money on automation for something I don't actually have the time or intention to fully support right now is not a good use of my time. If something changes and I go full time on Learn With Jason, I'm suddenly going to be pay for a lot of tools like this to try to make the site more useful to people and make that stuff really work.
JOEL: So, I'm gonna paste this in chat just to talk about it. I think it circles back to what we were talking about at the beginning, when do you automate? And this little workshop that Brennan gave me ‑‑ he gave me permission to essentially spill the tea on the entire thing with this sum rip. He gets in there. It's automation recommendations and best practices. The number one thing he's saying is get in there, do it the hard way, do it long form, like, have a simple form that feeds a Google Doc and captures emails and talk to those people. Don't worry about any of this splitting when you're first doing it and talking to people in your design solution and start to gain momentum as you proceed. And that is how you understand. It's like, okay, well, I'm actually servicing this. People are succeeding with what I'm offering. People are actually signing up. I'm actually helping them and guiding them along their journey. If I did this, I could do that better, right? Not really thinking about yourself and your life, but their lives and the value you're bringing to their lives instead of how you are improving your own through automation. Your life is important but, like, I feel like your ability to automate some of Learn With Jason actually improves all of our lives because our show is better. You get to think about higher‑level things. And you actually do it.
JOEL: So, that is important and that's thinking about the users. If this didn't exist at all then that would be bad, right? And because you automate, you're able to do it and you're able to do it longer and more consistently. So, that's good for your users. So, really, like, I think at the end of the day, you have to think about them instead of you, and is that, you know, is this something that you're doing for yourself or is it something that you're doing for the customers that you serve? And if you take that kind of service attitude towards a thing, I think you're gonna find that it's better in general and run into a lot less ethical problems. Like it's just generally, like, the way I want to think about this sort of thing versus, like, what's in it for me.
JASON: For sure.
JOEL: I found that it follows pretty well. If you work like that and really work on trying to provide value and bring value to their lives, your own success kind of can trail that fairly predictably, you know, like, in terms of building a little base of luck to stand on. That's a good way to think about it.
JASON: For sure. Nice. So, that is all the time we have today, so let me do a quick tear‑down of everything here. We've had Jordan with us from White Coat Captioning all day doing the live captioning. That's at the home state, learnwithjason.dev. That's made possible by our sponsors, Netlify, Fauna, and Auth0, all kicking in to make the show a little more accessible. While you're checking things on the show, make sure you check the schedule. A lots of good stuff coming up. It's a new year. We're going to be talking to Alex about tRPC, which is something that I don't understand at all and I'm really excited to learn more. We're going to get into Supabase next week. We're going to get into automation with n8n and a whole bunch of things I haven't posted yet. Lots of good stuff. Make sure you get over there. You can subscribe on Twitch. A follow is free and you'll get notified when I go live. Add this Google calendar link and it will show up in your Google calendar. Make sure you go and follow Joel on Twitter. That is a good time. It's just grumpy posts all day in the most pleasant way possible. (Laughter) And, yeah, any final words for everybody, Joel?
JOEL: Nope. I really appreciate it. I'm looking forward ‑‑ if you do use this stuff, I'd love to see it, too. If you make use of this thing, send it to me. I'd like to check it out. If you have any questions, too, or, you know, like you're digging into the code base, feel free to reach out as well.
JASON: Absolutely. All right, y'all, we're gonna call this one done. So, stay tuned. We're gonna go find somebody to raid. Joel, thank you so much for your time. We will see you all next time.