Build a Fullstack Next.js App, v4

Creating Email Logic

Brian Holt
Databricks
Build a Fullstack Next.js App, v4

Lesson Description

The "Creating Email Logic" Lesson is part of the full, Build a Fullstack Next.js App, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Brian demonstrates how to send celebration emails using Resend, setting up the email client, creating templates, and querying the database for user emails. He explains how to handle page view milestones, send personalized emails, and ensure GDPR compliance by not logging email addresses.

Preview

Transcript from the "Creating Email Logic" Lesson

[00:00:00]
>> Brian Holt: So, yeah, Resend, very cool. And there's lots of other cool competitors in the space. Like I use, I use SendGrid forever. SendGrid is perfectly great. Mailgun is like one of the OGs in the scene here. I think Mailgun is the one that invented Open API spec, which is kind of fun. Yeah, SendGrid got bought by Twilio, so like now if you set up SendGrid, you're kind of setting up Twilio as well, so you can send text messages as well, which is cool.

[00:00:26]
We're not doing that today, but you definitely could. So yeah, let's go. Send some emails, said nobody. Okay. So we're going to send people celebration emails based on page views. I can see nothing wrong with this. So let's go do that. We're going to go to our source directory, and we're going to make another top level directory here in our source directory.

[00:00:52]
And you might guess that we're going to call it email, and you'd be correct. In here, we're going to make a new file, we're going to call it index.ts and we're going to, oh, and the other thing, we got to go install our SDKs. I totally forgot about that. Let's do that now. We're going to say npm install, Resend, and React email slash render.

[00:01:20]
Like, again, when I was preparing for this course, I had not actually used React email directly before. It is like a miracle compared to how painful it used to be to compose emails. We even had Inc for a while, which is like another kind of like, hey, let's make email React-ish, even that had some sharp edges to it.

[00:01:42]
This React Email is just like it's chef's kiss, it's such a good library. And I think they just released a pretty major version of it recently as well. Okay, so now we have those two libraries available to us. So let's go and do in our email index.ts we're going to import Resend from Resend. Like that const Resend equals new Resend.

[00:02:28]
And you're just going to give it process.env.resend API key. And export default Resend. So I mean, you can see I use this pattern like all over my codebase. I like just like, I'm going to have all of my pieces for like email or AI or caching or database, kind of live in one folder and then I have like a top level index that exports like the setup client.

[00:02:58]
This is a pattern I like for organizing code. It does make us a little busy, especially once you get up to having like 30 services or something like that, but at the same time, it makes everything kind of grouped together, so I like it and you can't convince me otherwise. All right. In the same directory here.

[00:03:25]
We're going to make a celebration email, celebration.email.ts. Now, in this one, because we don't have very much stuff in here, I'm just going to put it all in the top level directory. If I ended up having like 30 different emails that we send, I'd probably have like a template directory and like a, I don't know, lib directory in there that actually like invoked the functions, but because I'm again, kind of of the mindset of like, kind of keep it like loose until like an obvious organization pattern shakes out and then reorganize it to be that way.

[00:03:54]
Doesn't always work like that. Sometimes you just end up with like really messy code. We probably mostly do that, but that's fine. It's going to end up that way anyway. Okay, so, we're going to import Resend from at slash email. I also just think this looks really nice, like. It makes these things like really obvious at a glance.

[00:04:35]
Same with DB import DB from at slash DB. Import users sync from Drizzle slash Neon, yep. Import articles from DB schema and import. All right, this is definitely going to get mad at me for not having these two at the top. And now we're going to just write something to send a celebration email every single time that someone hits certain milestones in their page views.

[00:05:15]
So we're going to say export default async function send celebration email. And this is going to have an article ID which is going to be a number, and we're going to have a page views, which is also going to be a number. Okay, and then we're going to query first from the database, const response equals await DB.

[00:05:54]
Let's do select, we're going to grab their email address, seems useful. Email from users sync.email. ID is going to be users sync.ID. It's going to be from articles. When the left join users sync on what articles dot author ID and users sync.ID. Where? Articles but another equals. Articles dot ID is equal to articles.

[00:07:12]
So, again, feels like writing SQL. Const email ID is equal to response.0 and you're going to say, if, if I don't get an email back, if no email. Then console.log, some error message. Let's do this. Come on. Here we go. Let's make this a template string rather. Article ID on page views. Page views. And they could not find.

[00:08:04]
Email and database. Something like that, just so you're like aware at a glance like what what actually went wrong. I'm not, I'm putting this as a log and not an error, because like if you skip sending a celebration email, like, it's okay, not the end of the world. So, yeah, Mark. Someone's saying EQ should have the parameters swapped.

[00:08:41]
You're probably right. Oh, not that, this should be not articles like this, it should be article ID. I'm surprised I didn't get mad about that. Oh yeah, article ID is the second argument. Yeah, yeah, thank you. Yeah, really strange, like, you wouldn't expect an ID to be equal to the table. Yeah. Maybe there's some like cool thing where you could just say like, it's some sort of like natural join where it says like, go find the thing called ID in this table, I don't know.

[00:09:14]
But I don't like that. I don't like that that didn't yell at me for that. That feels like something TypeScript should have caught. Okay. Thank you, thank you, chat. You're the real heroes. Okay. So, I'm going to write this first as if I as if I had the custom domain set up, cause I do, and then I'll show you how to modify it for if you don't have the custom domain set up.

[00:09:52]
So I'm going to say const email res equals await Resend dot emails.send. And then it really just reads like an email from. We'll send it from Wiki Masters. And then I'll put no reply at mail.holt.courses. This is kind of like it needs to be structured a certain way, and I'll be honest with you, I just copied and pasted it out of the docs, so this will send it from the name Wiki Masters from this email address.

[00:10:21]
And again, this will restrict you to whatever custom domain. I can't just put any custom domain here, it has to be one that I have specifically set up with them. You're going to send it to email. So that's going to be the one that came from the database. And subject is going to be. You know, whatever magical email message you want to send.

[00:11:02]
Something very annoying, your article on Wikimasters got. Page views. And again, I, we should have done a template string here. Page views, views. And then here, I'm just going to give it just straight up HTML and it's just going to say. H1. Congrats. You're an amazing author. And people like you. You know, it's always just trying to give people some affirmations.

[00:12:03]
Okay, so this will work if you have a custom domain set up. Let's just like I'll do a second version of this. This is going to come from. Onboarding at Resend.dev. At Resend.dev like that, and it has to go to. Your email that. You signed up with. Okay, so mine would be whatever my Gmail accounts or not my Gmail, my GitHub, my GitHub email, because that's what I signed up.

[00:12:26]
I signed up using GitHub. And if you don't do this, it's going to yell at you and say like, you're trying to send an email to someone, but you haven't set up a custom domain, and then you get like a forbidden error. Okay, I'll just leave this commented in here. I imagine most of you probably have to do that, but I have already set up mine, so I'm going to leave with this one.

[00:13:31]
Okay, if email rez.error. Then we're going to say console.log. And we're going to say. Well, this is my favorite emoji, the literal email. Sent. ID a celebration email for getting. Page views. On article. Article ID. You might be wondering why don't I put email here, having done so many GDPR compliance things over the years, scrubbing emails out of logs is nothing you want to do.

[00:13:54]
Because to be GDPR compliant, we could not log this, right? Because that would be, if someone came in with a GDPR request saying like, I would like to be scrubbed from your system, you then have to go back through your logs and scrub it of all of all your logs of that. It's way easier to just never log emails.

[00:14:14]
And again, I'm not a person for premature optimization, but if you have any sort of scale, which means you're going to end up in Europe and you don't want to get locked in Europe. And I think the California law has something fairly similar as well, so you'd end up with problems in California as well. Compliance, man, it's like half of my life these days.

[00:15:07]
Okay. Otherwise we're going to say console.log. Something like this. Error. We could probably just be mostly the same as this and just modify some of the error sending. All right so. And this one you can just put, oh yeah, right there, cool. Okay, so this will, this function now is available. Not organized.

[00:15:39]
Oh my gosh. Okay, fine, you wanna be there? I hate these stupid things of like. What do you want from me? Okay, you know what, we're just going to run lint and let you fix it. Npm run lint da da da dash fix. It's getting mad at me. Don't say, that's fine, and then we're going to say celebration email. I don't even understand what changed, but you know what, I don't care, because it understands what changed and it's not yelling at me anymore.

[00:16:37]
Yeah, Mark, could you explain the DB query? Sure. Let's look at the database query. So we are selecting. I mean, with the ORMs here, it's sometimes you will just write out what the SQL would be select. You. It'd be like articles.ID email. User sync dot email from articles left join. Users sync on articles.auth ID equals users sync.ID where articles.ID equals ID whatever the ID that the user input there.

[00:17:09]
So it's really like we're just getting out of the articles, table, the ID of the, as this is an article that's user sync. They're both coming from user sync, sorry. We are given an article ID and the amount of page views that it's gotten. We're not, we're going to use the page views just for the email, we're not going to use it for the select query, but we have to go get out of the table, the user's ID and the user's email.

[00:17:42]
And we're going to left join on the user sync tables, so we can find out this author wrote this email, but we don't have their email because the email is in the user sync table. We're joining that on what the author ID is to the user sync table and that's where we're able to pull out both the email and the ID.

[00:18:14]
So we're going from, I have an article ID to I need the person that wrote its email. That's what that query does. Hopefully that makes sense. Yep, so we start with, again, I'll just walk through one more time, article ID. And we need these two pieces of information from user sync. So we're starting from the articles table, we're selecting for that particular article, that's what this equals article ID is.

[00:00:00]
And then we're joining to the user sync table, which is where the email actually lives, and we're saying where the author ID is equal to the user sync ID, that's like, yeah the author ID from the article is matching to the ID of the user in the user's table. And we use that to get their email from the user sync table.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now