Email SaaS Course – Build and Deploy with Next JS 14, Prisma, OpenAI, Stripe, TypeScript, Tailwind

The Developer's Journey: From Function Execution to Email Syncing with Verile and Next.js

As I sat down to start this tutorial, I knew that I had come a long way since my last project. I had changed the function execution region to Singapore, which meant that my server was now near my database, ensuring fast communication between the two. This change would ultimately make the app faster, and I wanted to make sure that my servers weren't timing out right.

I began by redeploying my deployment, waiting for it to complete before proceeding. Once it was ready, I checked the app again, but unfortunately, I couldn't see any accounts linked. It turned out that I had logged into the wrong email account earlier, and now I needed to log in with my Google account instead.

I decided to test out the email linking functionality, adding a new account and hoping that it would work smoothly. To my relief, the app allowed me to link the account, but there was a catch - it hadn't verified my app yet. I continued through the process, hoping that if I went back into my deployment, I'd see my logs and live data.

I navigated to deployments, taking note of my next public setting, which was set to 3,000. This seemed silly at the time, but I made a mental note to revisit it later. With that out of the way, I saved the changes and redeployed the app once more.

This time, when I added an account, I expected the app to redirect me to normal human-yt.verel.app/SL/mail. And indeed, it did - I had successfully linked my account, and if I went into my lockbox, I could start seeing emails coming in one by one. It was a thrilling moment, and I couldn't wait to see how the syncing worked.

As I watched, the app attempted to sync 23 emails, and slowly but surely, they started appearing in my inbox. It was a beautiful sight, and I felt a sense of pride knowing that I had created this system from scratch using Verile and Next.js.

But I didn't stop there - I wanted to test the app further. So, I opened up Airworks and gave it a try, hoping that everything would still be working as expected. And to my delight, it was - my email syncing was seamless, and I could even see the access grin appearing in one of the emails.

I couldn't help but feel a sense of accomplishment as I watched this all unfold. From function execution to email syncing, we had covered it all. And as I sat back and reflected on what we'd achieved, I realized just how much knowledge we'd gained along the way.

We had learned about TR T3 stack, prpc, OpenAPI, database management with web hooks, and Tech search - not to mention co-pilot and Strip. It was a veritable crash course in modern web development, and I couldn't be prouder of what we'd accomplished.

As I wrapped up this tutorial, I knew that there were many more adventures waiting for us. But for now, let's take a moment to appreciate the hard work and dedication that had gone into creating this email syncing system. It was a true collaboration between me and my team, and I'm honored to have shared it with all of you.

So thank you, dear reader, for joining me on this journey. If you found something helpful in this tutorial, please don't hesitate to give it a thumbs up and subscribe to our channel. Share this video with anyone who might benefit from it - after all, that's what we're here for. And as always, I'm Elliot, signing off.

"WEBVTTKind: captionsLanguage: enin this full stack tutorial you'll learn how to build and deploy a feature-packed AI powered email client using NEX js14 Elliot Chong will teach you how to create everything from scratch integrating powerful tools like open AI API for Aid driven email composition and stripe for seamless Payment Processing along the way you'll learn to style with Tailwind CSS leverage the nextjs app router and use Prisma for database management by the end you'll have a fully functional email client deployed to vercel ready for real world use hey what's popping I'm Elliot a software engineer from Singapore and today I am so absolutely excited to Showcase what I have kind of built for the past few days which is a minimalistic AI powered email clients built on shet CN and open Ai and nextjs and all the good stuff right so I took heavy inspiration from two two ideas one is one is superhuman which is another kind of minimalistic lightweight email client right that has like shortcuts you get to have command bars and like it's a fully functional email client right so I I wanted like experience like that uh to replace my current Gmail client and I took the UI inspiration from the homepage of shet cn's kind of uh email showcase which is this right so I just took the UI and just put in the real data which is the email client data right so let me showcase what I've built so let's press get started here and it's going to lead us to the log in page right so then we're using clck you can log in with any kind of method I'm going to log in to my Google account here and once you log in you get to be in the email dashboard and you can see I already linked three of my email personal email accounts to this kind of platform this normal human platform right so the first thing I want to showcase is this a fully functional email client right so I can see that um I receive an email from my other email account called aliot atuse incubate tocom and what I'm going to do is I'm going to reply to him I'm going to say U dear other Elliot right and this is a fully featured like text editor I can say like I can bold it I say um Love from YouTube exclamation mark right I'm going to say best Elliot right I'm going to press say send right so it's going to ship out this email and so there's a lot of stuff happening on the background Shing out to a dedicated email server and it's going to Ping a web hook to kind of synchronize all the emails and in a few seconds you can see that we have received this email and in my sent inbox I can also see uh the email that has been sent out actually no it's just in this tread so all the emails are being logically kind of separated into threads right so a thread is just a collection of email messages right so I can see that I send this out to to add in incubate and if I go to my superum app right I can see that I received this email from my other email account official not @gmail.com right so we can see that it's fully featured like email client and there's also this chatbot thing right where we can kind of it will take context of all the information here I can say draft a reply uh to thank him for help me for this demo so I'm going press generate and open is going to use this right to automat generate this stuff I mean it's a little janky like but it's usable it's usable and to Showcase it let's say we want to reply to this email uh actually this email maybe I can press this I can say reply to this email right so this will take in the context of the entire email threat and generate me a nice email for the person and I can obviously press send to send this out but I'm going just ignore that for a while okay and obviously it has a fully featured CC and kind of tool format right and you can edit the edit the headings okay but that's not the coolest part right so we have established that it's a fully featured working email client right but the most important part and most magical part is that we have build an entire rack model which stands for retrieval augmented generation model into the email client so so I can ask the AI anything I want about my email inbox and is able to reply to me with the context so I can ask like tell me about the email from insta field. I'm going to send this over and hopefully so you can see the email you receive is from insi AI announcing a new update to that they've made to their service right so it probably put in this email so how this works is when you ask a question to this chatbot it's going to find all the relevant emails uh that might CH your question then we take all the email contacts and we put it into the prompt of open Ai and then we send it out and then it will give us back a response so that's what retrieval augmentation retrieval augmented generation means right so like that is cool right I can ask more questions like these are useful because I can ask like when is my next flight right in this case like because I don't have any flight information emails here it's going to say it doesn't have enough information but imagine that I sync my entire inbox here I'm going to have like my flat confirmation emails in here and this AI is going to be able to tell me when my next flight is without me having to go dig through my email right so other than this retal augmented generation we have a full Tex search so you can have shortcut so I press I'm pressing slash and I can search um aliot and so this will give me a full text search of my entire inbox I can search for insta field insta field let's see this right it's going to find the insta fill I can search for like let's say class pass it's going to find all the class pass related stuff here right so the the full Tex search is incredibly fast and Incredibly versatile right and the whole thing is kind of command based right so I can navigate around using J and K I can select highlight email tracks here all using my keyboards if you see at the bottom left corner and I can press kind of D to mark it as done right I can select like like all the items here I can press D this will mark it as done which then will be transferred into the done folder right you can see that these are the done emails that I just kind of cleared away and if I want to ever move it back into my main inbox right I can just select everything again I press U for undone undo and it's going to basically move it back into the inbox right let's just wait for it to refresh and yeah all my emails are then basically back into my inbox right so I'm going to just clear out some emails like death I have read right so these are cool um and by the way there's also another AI feature in this email box uh if I start typing like dear insta field right I can say like hello if I press command J you can see there AI auto complete so I'm going to press command J right see it's going to automatically complete my next sentence for me so this super helpful if you're like typing some stuff and you need to quickly just ask AI to complete your sentence this like a GitHub co-pilot kind of experience you can just press command J I can say like best best regards right I'm going to press Comm J hopefully okay maybe that's a little weird but yeah the point is that there are some like AI features here where you could help you automate and make it make your email experience much faster and cleaner and I mentioned that that is light mode and dark mode so there's like light mode dark mode here you can toggle it everything is responsive and when you press command K you see this I have never seen someone teach this on YouTube yet I'm going to teach you all of this today so when you press command K we're going to bring a command bar right here is where you can go to your different sections so you can see I have a draft page I got a send inbox right so I can quickly navigate around by using command K and typing inbox it goes my inbox I go to drafts it's going to lead me to draft I go to send it's going to lead me to send and if I want to go to the done I can search for my done emails so this will switch me to the done tab I can see my pending email so switch me back to the main inbox so everything super fast and if I want to like switch my email accounts I can quickly do that by doing command K I can search for my Gmail account right so then I can quickly switch to my Gmail account as well as my other like incubates email so everything is streamlined on the keyboard for developers by developers right and of course you can compose emails right and you can press command C to quickly bring up the compost box and this is basically the same stuff you can search through all the email recipients that you want to send to cc's work subjects work and you can also just say quickly draft in a apology email to John so let's see what it does um yeah I mean that's a good start I guess and I'm going to take this and can just continue writing my email yeah so you can then obviously send this message out right so this is a lot of stuff that I want to teach you today and I'm going to teach you everything here today and this is a fully featured SS application what this means is that you can receive payments so you can manage subscriptions right so people can kind of pay for the service to upgrade it so by default by default if you do not have a pro version of this you are only limited to have one email account connected and you can only have a maximum of 15 AI messages to be sent um every day so then once you kind of allow users to upgrade by paying $1 you can then uh make the account kind of the pro version which then unlocks all the features of normal human yeah so I'm going to teach you the entire strap integration for this as well so I think without further Ado let's actually get started there a lot to learn a lot of apis to learn today so let us get rolling before we continue on in the video I would like to share a short story about myself for context I started to learn how to code about 3 years ago when I began I learned the full stack M so that was mongodb Express react and not GS I started to build a lot of this kind of small projects with the mertech slowly expanding my tool set I started to build my own authentication library to be reused with all my personal projects then slowly I added more email modules then I learned about stripe and I was starting to able to charge people for the S products that I built and in the past year or so I've actually been working at a YC back startup where I was the founding engineer who led the entire full stack infrastructure and seeing that startup grow from zero with the stack that I used there it really showed me that this stack has been proven to be scalable developer friendly and blazingly fast and on top of that you know from my channel I love building and trying out new set ideas so I've been building this products for years and I keep running into the same problem every time I start a new project at waste weeks setting up the same infrastructure authentication database file storage payments you name it and I thought there just got to be a better way and that's why I created this start s project is the starter kit I wish I had when getting into the game we're talking about the full stack nextg setup postgress SQL with Prisma we got the all solutions S3 file storage stripe Integrations all the essential ials to get your SS off the ground fast now I'm not going to you right this is not magic you still got to put in the work to build your actual product but start says this I hope that it can handle all the boring infrastructure stuff so you can focus on what matters most to you which is the unique ideas that you're bringing into the world I plan on building a community around this idea where I'll be posting updates blogs and newsletters about all the experiences I have learning over time where I continue building out this s projects and I've got over 1.5 th000 over stars on my GitHub projects and even some YC startups are building with it why because it works it's battle tested and it scals if you're building aess and you're tired of Reinventing the wheel do check out start says it's going to be a culmination of everything that I have learned failing building succeeding and scaling so the link is in the description and I'll be having a Discord Community where if you're interested in about building s or you just have questions about the videos that you're seeing do come join us in this community we I'll be there to answer questions all right enough about that let's dive into today's topic on building an email client okay so before I get started I'm going to tell you what you're going to be exactly learning today so by the way the text T here we're using a lot of different apis today so we're using clerk for authentication nextjs as our full framework Prisma as our object relational mapper so this kind of helps us interact with our database we're using neon DB as our database provider so neon DB is a kind of cloud provider that gives us uh serverless postgress database uh that they will handle it for us right we're using tawin CSS and shed CN for styling right so it's a component Library we're using trpc so trpc is a kind of um type safe a way to do type saave apis so imagine you have the traditional rest apis where you us like post get requests to another server right this is an alternative to rest which allows you to interact with your front end and your backend through a type typescript based type save uh API layer so I'm going to explain more about this uh during the tutorial so just going to give you a quick recap okay then postgress postgress SQL that's the database that we'll be using obviously we'll have stripe integration to receive payments and upgrade version upgrade users to the pro versions uh we're using typescript of course we're going to be deploying our stuff on verell we're using open AI for all the open AI features like the AI sending AI composing features and these are the two very important kind of apis that we're going to be building on top so orama is a fast dependency free full text and Vector search engine so this is what we're going to be using to to do our full text search so what it does is it allows you to store documents inside the database and then allows you to kind of operate on it by searching through it by filtering and stuff like that so you can see that how it works is you create a database schema right with the different uh Fields you can also have embedding field so we're going to be using this embedding field for retrieval augmented generation later and then what you can do is you can just insert documents into the database right so imagine this is we're going to be inserting the emails right like the the the subject the body the who is it from who is it to Etc and after it has been reinserted you can then search through it uh using like just a term and what it's going to do is going to pull down all the relevant documents that matches the future so that is how we're going to kind of uh fulfill the full Tex search right so this is using orama okay so other than orama this oringo is the main API for today because oringo is is what is powering our inbox API so it's like a very pretty Niche API that I found but what it does is allows you to have a unified email API so whether people are trying to connect that Gmail they're trying to connect their Outlook account right what oreno does is it gives us a centralized API for us to operate and sync our inboxes and send out emails right so you can see that it allows developers to quickly build Integrations with many mailbox providers like Google Office 36 five Outlook Etc and I'm going to go a lot more in depth into all of the apis in this oringo later right it's super powerful you can like receive emails you can send out emails you can sync inboxes Etc so I'm going to be teaching you all of that in this tutorial right and so this is kind of the main text stch that you'll be learning today it is more intermediate in terms of skill level so I expect you to have kind of a general knowledge of how to use next GS react you you have to understand and uh the data flow from database to the back end to the front end to be able to kind of follow along so if you're a beginner I have a lot of other beginner tutorials uh like a chat PDF SS product that you can watch to kind of catch up with your skills before coming back to this video yep okay so what you learn is how to build a Rec Pipeline with a custom checkboard so Rec as I mentioned is retrieval augmented generation which means you have a database of documents and when you ask a question the the chat boo is going to search through all the relevant documents and then put the context into the chatbot before kind of getting the response back so this allows for a very powerful uh kind of chatbot inter interaction because a chatbot will have then custom knowledge about your entire email inbox we'll have full Tex search with orama we will build a full email client clone right so it's basically a Gmail clone right where we will sync in the emails from your current account we'll be sending and receiving emails we'll be composing emails replies Etc and we'll teach you how to make an AI smart compos feature right like a email writing co-pilot which I've shown you just now I'll teach you how to uh add a command bar feature to your user interface and you can use this anywhere in your app in your other full STP projects which makes it so much more professional once people see that you have built a command bar uh feature right and lastly we'll teach you how to set up strike payment to receive money for people to kind of upgrade the account to use more of your inbox features yep and so I'm going to break it down into mainly 12 parts right so that I can set some expectations for you so the first section of this video is going to be really understanding the email client functionality with or rinkle right so I'm going to break down everything you need to know about how the email API Works how do you sync the emails what's the difference between an email and a thread the different terminologies used when creating an email client right and then the second step is we're going to set up the the B barebone structure with nextjs SH CN Clerk and setting up the datab base for ourselves and then we're going to dive into the oreno API to start receiving and syncing emails from your current Gmail inboxes orever inboxes you have and then we'll also do some data based engineering and web hook management so this is important because when you're trying to sync your email with Gmail when Gmail sends you a message you need to be able to receive the notification and basically sync their servers data with our our database so that we can always have up-to-date version of the email inboxes okay so the first kind of first half of this tutorial is very back and heavy so I hope you deal with me I'm going to be drawing out everything so you have a perfect understanding uh and before you actually code it out okay and we're going to hook up the full text search with orama and then finally we're going to start kind of building out the initial UI to display the email and threats we're going to show you how to build the search UI and then we can start doing some interesting stuff with AI by building the rec Pipeline with Q&A using theel AI SDK and then we'll go into setting out replies and composing and sending out emails alongside the email co-pilot and then the final step is setting up Stripe Right so that you can start receiving payments I'll teach you how to set up subscriptions and all that good stuff and then we can then deploy to verell and I'm going to show you how to quickly build a landing page to start uh showing people your awesome project right so there's a lot of stuff to be to be done today uh it's a very backend heavy very kind of data heavy tutorial but if you are able to build this out you will really truly understand how email clients work how o off Works how a lot of these backend processes are are kind of the you can see the data flow from all these different data providers into your centralized UI platform okay so without further Ado let us jump right right into understanding how an email client really functions okay so this is going to be the hardest part to understand of the whole tutorial so if you listen closely and you truly understand this part of me explaining how the email client API works you'll be good for the rest of the video okay so let me going to first introduce you to the problem of white oringo exist so imagine if we don't have a centralized API and we want to kind of directly read emails and write emails on behalf of of another person's email account so let's say we have a Gmail inbox and this Gmail has two accounts right so John has a Gmail account and Jane also has a Gmail account and in this case like let's say Andy has an Outlook account so imagine like we want to build an email client email client so what what what would we try to do we're going to basically try to access right we're going to try to access and be able to read SL WR to John at gmail account gmail.com so we need access to their account in order to kind of read all their emails to kind of put into our own database so that we can display in a custom UI and then when they they want to try to send an email from our UI we also need to be able to have access like a right permissions to their John gmail.com in order to send out emails and so imagine what you have to do is you have to do the same for all the different Gmail accounts and all the different providers and the different different providers are like Gmail Outlook Hotmail whatever other inboxes there are right and the thing is Gmail has a set of different API so Gmail uh Gmail API so Gmail has a set of apis where they have different specifications where you can read and write but then the thing is Outlook has another set of apis that completely different from Gmail and their data their schema is different how they represent that their emails are different how they represent their threats are different so imagine having to write and kind of write all the code in order to integrate with uh not just Gmail but also Outlook right it's going to be Troublesome and Gmail is like super hard to work with their apis right because they have like weird kind of syntax so this this is the reason why a service like Orco exist Oro basically kind of imagine it as like a central proxy that sits between the Gmail API and the Outlook API so when when you want to say I want to send an email all you have to do is you tell oringo hey I'm going to issue you a command like api. orn.com mail send right I'm going to send I'm going to ask to send an email with I want to send it from this email called John gmail.com and I want to send this with the subject of test and the body of test right so then what is going to happen is I send this post request to oreno oreno will then detect that I want to send Using John's Gmail account then Oro on the back end like they will handle it they will basically uh use Gmail's API to help you orchestrate the entire process and send it out uh basically using John's Gmail account so you don't have to kind of think about Gmail you don't have to think about outload all you have to do is think about oringo so they kind of abstracted away this entire API service for reading sending and composing emails all that good stuff including authorizing access because when John wants to give us access right we have to basically John will have to give us consent so I'm sure you have seen the Google consent screen right it looks something like this right son so John will have to say like hey uh oringo wants to access your Google account uh will you do you want to allow them to read and write emails for you so in this case John is going to press yes because they want to use our email client so then basically John give gives oringo sorry John gives oringo permission to use their inbox to send and read emails and then we use oringo to access John's inbox so I hope you understand why now oringo is good and why oringo exists right so then we will our application will then interact with oringo to basically authorize and save Account Details save user details and basically read and write the emails and threats okay so now let's actually walk through the entire the initial process of John like see John he's in our application I'm going to show you step by step how John will give us access to his Gmail account and how we're going to use or rinkle to kind of read and sync his initial messages in the inbox okay so uh let's let's just clarify some terms right when I say when I say uh inbox right what I mean is the messages within a certain email account right so an email account is just uh uh how do you put an email account I guess an email account is just associated with an email address so imagine John has email address as John gmail.com so this will be considered one G one email account right and the inbox refers to the entire kind of messages slash threats within the inbox account the within the account okay and so what do I mean by messages and threats so um message is fundamentally an email message so email message is like when you send out email that's like a atomic like a singular form right but then you know how you can like reply to emails and like forward emails right so when you do that all these different emails is going to be put together inside one thread so we group them together and when you have a group of emails that are related to each other we call them a thread so if you go into let me show you for example this so this you can see that there is two emails two email messages because there the first one and the second one but the reason why they are in one threat is because I replied to the first email with this other email therefore Gmail is I mean what mail service is able to kind of identify they're related to each other and put them in One Singular threat okay so that's what the email message and threat means and that's what inbox means and an account is just an email address associated with it okay so the first step John is going to come to our application and it's going to try to Grant access to oringo so he's going to press a button on our screen that is that is going to show the content screen and saying like hey John do are you sure you want to give access to oringo to read your inbo okay so for example here when I'm in my inbox I'm going to PR add account so this add account is like me trying to give access right so I'm going to press add account and so what it's going to do is going to lead me to the uh consent screen so it says choose an account continue to or so this is the exact like kind of permission that Gmail is asking hey Elliots are you sure you want to give or rinkle access to your Gmail account so in this case I say yes I'm going to press this and it says okay it says haven't verified this app but it's because like we're in development mode so it say sign to oringo right it says by continuing Google share my name press continue so you can see oringo if you if I give permission to Oro is saying that hey oringo will have the ability to read compose send and delete email from Gmail and if I am willing to do that I'm going to press continue and in this way right oringo is then going to have access to my Gmail inbox right so this part so the first step here is granting access to oreno then oringo will send a request to Gmail and Gmail is going to ask hey are you sure you to confirm so let's say John confirms by pressing yes please authorize Oro to have access to my inbox right then Oro is going to say that hey Elliots uh uh John has give us permission to use his inbox so then step two Oro is going to send us back a authorization token so remember this word token he's going to send us back a token alongside the users's email information so then oringo we can set like a call back endpoint so let's set it at/ API oringo callback we're going to receive the information we're going to receive the token we're going to receive the email of John right and then we're going to take this information and we're going to step three we're going to save it to the database so what this means is we're going to have account table so remember an account is just a a kind of it's just a information related to one email address right so we'll have an account ID which is like identifier from oringo site they're going to give an account ID and they're going to give us this authentication token so this off token is important because we need this token to be able to make requests on behalf of of the user okay and we're going to obviously save the email address as well in the in the accounts row okay so after saving step four is we're going to get the email right because look at this this is Gmail server right that's this John's inbox right so imagine this his inbox inbox and John has like 12 emails sitting there and what we want to do now is trying to get like read this 12 inbox and attempt to save it in our emails table right email message table so therefore step four is I'm going to take the authorization token and use it to sync the inbox so I'm going to take this authentication token and I'm going to go through oringo API right so then I'm going to send the token I'm going to say that hey I'm going to hit this endpoint with the account ID of 06498 and I give you a token of token d a b CDE e f so with this two information right the account ID is the identifier for John's email address and the token is the authorization token needed to make a request on John's accounts behalf so with this two information if we send it to the oreno API what it's going to do is this oringo API is then finally going to give us back a list of all the emails that currently exist within the John's email so imagine we are basically copying these emails and oringo will give us give us a list of the current emails in a Json format and then we can basically save all these emails into the email table right and then the email have the ID the subject the body and the front okay and I'm going to actually show you the shape of this response back because once you see the Json shape it will make complete sense to you so let us I'm going to show you this so I'm going to show you email. Json right so this is kind of the the shape that we're going to be getting back so we can see we're going to get back a list right a list of stuff so each item in the list corresponds to one email so each email message will have a unique ID right it will have the thread ID that the email belongs to course remember related emails like an email will always belong to a thread right and it's possible to for that a thread to have only one email message right so this is entirely possible so you can see that the email here belongs to a thread ID there's a time where it was created it it was a time where it was sent at okay so if we go back to the example of here let me show you my app here so you can see there's this sent at about 18 hours ago so this field is the one that maps to be sent at so this is when it was received at this is an internet message ID which is another identifier used in the SMTP protocol we have the subject this is pretty self explanatory we got a list of system labels so these are the labels that has been automatically CL classified by the SMTP protocol I think right so then we can use this to it to basically display you see this unread and inbox this is coming from the system labels right and we have a list of keywords we got a list of system classifications we got the sensitivity we got who is it from right we can see this email was from notifications at account. def with the name right and you can send in who is it to so you can see the to is a list of addresses right it's a list you got a list of CC's so these are carbon copies a list of bcc's which are a list of blind carbon copies right and there's a list of reply to so this list of reply to is like when I send an reply back to this email who should I send it to right this is where this field will tell me who to send it back to and whether the email has attachment right and then we have the actual body of this email which could be text or which could be uh like HTML right so later we'll deal with the HTML bodies to be rendered nicely here because if you see you see this fancy images and buttons the body here is made of HTML right and with have the body snippet the body snippet is just like this part of the text here so it's just a shter summarized version of what the body is we have the list of attachments and then we have Internet haters so internet haters are what you call metadata on the in the on the email so that's like who it was delivered to who was received by uh the the source of the SMTP who was received and so these are like uh kind of metadata we don't necessarily need to look at this we just need to know that that it exists on the email itself right and then we have the folder ID so they can be inbox it can be draft it can be sent and then this is like omitted which is like another oringo field so you can see that it's pretty self-explanatory right we get back the list of emails from oringo oh my God I keep losing it we get back the list of emails from oringo after hitting the email/ sync endpoint so we take this this list and we then just save it in our emails table and once we have this emails and threats table we can then finally have a nice UI to display everything we have okay so that's pretty cool pretty cool but now I to explain to you how do we actually keep the details in sync how do we keep the details in sync so this entire process right of them authenticating and stuff like that this is what we call the initial onboarding process so the initial onboarding process involves getting consent getting the token storing the token creating the account after creating the account sending the initial syn synchronization request to oringo to get back the current list of their emails but now that after the initial onboarding process is done we now want to keep the the inbox in sync how do we do that okay so now I'm going to go to a different part of the canvas actually I'm going to copy this right okay so now imagine we have our database so I'm going to copy this here so let's say this is normal humans server right and then we have our database okay so imagine we have the database of emails right so we can have the subject the body the from and other fields right the other fields like when it was sent Etc but we'll ignore that for now so imagine John Suddenly receives another email it's like hey and then it's like Jane Jan gmail.com send an email right so then Gmail will suddenly have an draw email here but the thing is we don't have that email so we want to kind of uh continuously update our database based on what Gmail has and this is where the kind of web hook concept comes in so if you're not familiar with web Hook is basically a okay let me search the definition of web hook together uh how do I explain this um web Hook is a event driven communication that autom sends data between applications ah that is too complicated of a definition okay so my my kind of definition my simple explanation is Imagine uh it is exactly like a call back where whenever Gmail gets an email we're going to give Gmail an API so imagine we have a domain is like https www.n normal- human.com API um callback right callback so whenever Gmail receives a new receive a new email on behalf of John's inbox he's going to Ping us right GMA is going to ping ping this URL with the actual content of the body so we have the subject with have the the body Etc right and then because we control this endpoint right we will able to then know that hey there's a new email and therefore I should probably write this kind of we should probably write this new email into our database and therefore we can take whatever Gmail sends us to the web hook and write it into the database so imagine have 1 2 3 4 we have the body of like pong we have sorry the subject is pong the body is Ping and it's from j@ gmail.com and so now we have successfully kind of uh kept our database as well as the Gmail uh gma's inbox in sync right so this is the concept of a web Hook is basically I give you an address and whenever something happens on your end just make sure to Ping this address so that I know that there's an update for me to synchronize our database okay so that's the entire concept of web hooks okay and so we're going to basically or Rinko gives us the ability to set up web hooks to listen for new events in the Google inbox right so we're able to connect create a connection between the John's inbox as well as John's account in our database So then whenever John receives an in email oringo is going to Ping us with an API to the web hook telling us that there's a new email and then we're going to take the Ping we're going to then uh sync up with Gmail's inbox again and therefore keeping the emails in Sy yep yep yep um I think it's going to be much clearer to you once we start coding and once I start showing you how the data is flowing and then you will understand but for now this is the general overview of everything that we're going to be doing throughout the um throughout the whole tutorial so we've covered oringo we covered emails and threads we've covered the metadata we've got accounts I talked a little bit about syncing and I talk about web hooks so if you understand the high level overview of these topics we can then actually go into the code and I can always reference back to this diagrams as I code along step by step and explain to you which parts we are coding now so now let's actually start uh getting the project up and running so we can start seeing some progress cool so now that we understand the high level overview of the email clients let's start setting up the code so I'm going to open my terminal okay and let's expand this out a little bit so we're going to be using the T3 stack so if you're unfamiliar with it the T3 stack uh is built by Theo and it is a type save next year starter kit that has nextjs typescript trpc Prisma TN CSS and we're not going to use next off but we're going to be using click okay so to actually uh create a new project I'm going to go into my coding folder I'm going to do um too bu X create Das T3 app at laters so throughout the tutorial I'll be using bun uh in replac F for mpm and if you're using mpm you can just replace it with uh MPX create T2 app and another note is there are some people asking me why I'm using Pudo is just something wrong with my MacBook that they don't allow me to run commands unless I'm in Pudo mode maybe there's something wrong with how my user is set up within the computer but most likely you will not be using Pudo throughout the tutorial so whenever you see me using sudo you should just kind of ignore that so you should be just running bu X create dt3 app at latest okay so it's going to resolve it it's going to download down the repository it's going to then ask us what our project is called so I'm going to name it normal uh human- yoube we'll be using typescript right um we'll be using TN CSS press yes we would like to use trpc we'll be using clerk so we'll just press none for now we'll be using Prisma for orm and yes we would like to use the nextjs app router and then we'll be using postgressql because we're using neon DB and let's actually initialize a g repository and run BN install for us I'm going to press allias for the import alien for now and then it's going to go ahead and set up the repository for me cool so it's going to run but install and in a few seconds time I should be able to open it up in my code editor so I'm going to CD normal- human- yoube I'm going to press code Dot and this should run so I'm going to just delete my normal repository and okay cool we're now in a fresh new repo what I'm going to do is I'm just going to run sud sudo bun Dev so this is going to run on Port 3000 right and let's open the browser to make sure that we can actually see what's happening so let me just close down this things here first uh actually I need my excal draw but let's go to Local Host 3000 and I should be able to see the next GS app running and let's see okay in this case we can see that uh we can reach our database server at local host 5432 so what I'm going to do is I'm just going to come down to our page. TSX so this is the main first page that we're running and in this case they're trying to I'm going to just delete everything within the code block we're going to start everything fresh I'm just going to return a hit one that says hello world I'm going to save this and you can see the issue where I'm saying that I do have enough permission so it's a purely me problem so I need to run change owner to make sure that I own the repository for some reason you probably don't need to do this I'm going to run Bev again right and then I'm going to save the file and I'm going to refresh and hopefully yes cool we see Hello World running so our next GS app is running and what I'm going to do is I'm going to bring this to the side here so I have a split screen view of what's happening okay so uh let's actually try out Tailwind so if I give you a class name of text - r- 600 I can see that it's rate so T is working perfectly cool so now I'm going to try to set up set up shet CN which is this very nice component library and this also where we'll be stealing this email inbox UI from right so to set it up come down to your command line I'm going to open a new terminal here okay and I'm going to run sudo bu x uh sh cn- UI at latest in it so this is going to help me initialize a new Shen uh configuration right so it's going to resolve um okay there's issue with this I need to run PM PM PX PM PM vrx sh CN it latest add so I'm just kind of initializing shet CN right uh it's asking me what components I want to add in this case I'm going to press a to select all I'm going to press enter right so I'm going to press confirm so what it's going to do it's going to download all the components from sh CN into my repository I'm going to choose a New York theme with the the neutral I'm going to press yes to all right so it's going to install all the dependencies right so it's asking me if I to overwrite some stuff I'm going to press yes just overwrite everything here and cool so let me exit out the terminal and now if I see into my my folder I now have a under my source folder I have the components - UI sorry slui and this is where I have all my components from Shen so I have this button component which they have redefined some class variance Authority like some stylings for me and I can use the button directly so let's test it out I'm going to return a button right so I can automatically import it at/ component SL ui/ button I'm going to say hello world and boom our button is here nice and pretty cool and now let's actually set up up our authentication provider which is we're going to be using clerk cool right so clerk is a manage authentication server for us to use um it's up to you if want to use your your own authentication provider I just cler because it's so easy to set up I'm going to continue to create a new application so register for a CL account first if you have not but then what I'm going to do is I'll give you a new name normal humans YouTube let's ask for their email Google and allow them to loog in with their GitHub I'm going press create application right and then what I'm going to do is I'm going to choose next GS I'm going to follow their quick start guide so the first step is to install at cl/ nexts so let's do that together I'll open a new terminal and I'm going to run sudo bun install right sorry bun install at CL SL nextjs so this going to install the component for me the library for me and then I'm going to copy the environment variables into myv file so let me see if I have a EnV file I do cool so I'm going to copy and paste it down here copy paste right so you can see my cler publishable key and the cler secret key is now pasted in my EnV file and then I'm going to update my mle we TS so come back down to your Source folder so under the source inside the source folder create a file middleware DTS right and what I want to do is just copy whatever code they've given you we're going to change this later by the way so what this does is we import the CL middleware and it's saying that whenever you see a route uh whenever you see a route here you're going to make sure that cler kind of protects the route or make sure CL runs it for us so that we can get access to the user information okay and then finally we need to uh wrap our app in the CL provider in order to use the clu hooks like use user use off Etc so come down to your SL Source slapp layout. TSX so this layout. TSX is wrapping out entire application right so I'm just going to copy um I'm not going to copy the whole thing because we have this trpc react provider that don't want to overwrite so what I'm going to do is I'm going to copy this part here and I just want the CL provider right and then I'm going to wrap my entire HTML with the CL provider going wrap it down here and everything should work fine now and finally if we go back to our app you can see that nothing is happening which is good because by default default clerk doesn't protect any of your pages so in order to make it such that uh the the kind of landing page is protected what we can do is let's go down and let's utilize our own page for authentication so let's continue down to nextjs guide okay so the first thing is we're going to build a sign up P sign up page right so lit have to follow their instruction so go down to your app folder and then create a folder called sign-up then create a uh Open Bracket dot dot dot sign- up and then do page. TSX right and you can copy in this into uh the into the component so you have to import react to kind of remove this error and we'll do the same for the sign-in page so let's come down to app let's create sign dashin let's create Open Bracket do do do sign- in and we'll create page. TSX let's copy and case it in we also have to import react from react and save it so you see by default click middleware so if you go to the middleware this click middleware function makes all Roots public and so this is only if you want to make all Roots protected by default which is what we are trying to do so what I'm going to do is I'm going to just copy this code I'm going to replace our middleware TS so what's happening here is we're going to Define a variable called is public root right which is saying that these two Roots should be public so this is a reject a regular expression expression so whatever rout that starts with SL sign sign in will be public and whatever root that starts with SL sign up will be public as well which makes sense right because if you're accessing sign in you're probably not locked in yet okay and then we're going to export the clock middleware so this middleware function is going to run before any kind of next year request uh request gets hit so if you're requesting for a page this middleware will run first to kind of protect our Roots so we're checking if it's not a public root we're going to protect the Page by kicking them into the signin page okay and then we're going to update our environment variables to tell our app where is the actual signin page so we'll just copy the EnV variables that they asked us to and let's copy it down so next public click sign in and sign up URL is exactly what we have written here which corresponds to the folder name called sign in and/ sign up cool and finally if we go back to our app so we go to Local Host 3000 we can see that it's asking us to sign in so if I go to the slash page because it's a protected route we can see that it automatically redirect me to the sign in page all right and to actually show you how to make a Roots public because we're going to have to do that a lot later right to make a root public you just have to kind of edit wait what's this okay you will have to add it to the list here so I going to make slash if I make slash a public rout if I now neate it to slash look go host 3000 slash you can see that now this is a public route but if I remove this right it's going to then protect the root and make it so that I have to sign in to access it cool and then the sign in page right what I'm going to do is I'm going to just wrap it in a to center it so I'm going to return a div here and for this div we have a class name of absolute uh sorry I can just do F Flex just this F Center item Center High screen and it will Center the component inside for me cool and now what we're going to do is we're going to sign up right so another thing is whenever clerk signs up a user we want to kind of sync it to our database right so after they sign up we don't know that a user sign up because we want to create a kind of a a user table within our database so that we can keep track of which user uh is locked into our platform right so to do that I'm going to set up our database next so let's set up Prisma cool stuff and I think they already set up Prisma for us so if you go under Source SL lip actually not lip let's search for DB okay yeah so under SL Source SL server SL database. TS right we can see that by running create T3 app they have already created a prart client for us and one thing I'm going to do is I'm going to remove this query thing cuz I know it's going to get annoying it's going to basically FLIR our uh our locks with the query like SQL query stuff which is annoying so they give us a Prisma query client and so now we just have to Define our database schema in Prisma so if you go under if you search for schema schema. Prisma right so this is the file in which we Define how a database is going to look like so before we start designing our database let's actually have a database in the first place so I'm going to go to my neon console neon teag and just create an account in neon if you have not done so I already have an account so I'm going to log into the dashboard and so so neon will give you a free database by default so you can just create a project and in this case I'm going to go into a current project okay so I'm going to create a new database in here I'm going to call it um normal human- YouTube and I'm going to press create and so this will quickly spin up a new database for me right then I'm going to just copy the database URL come back to my end folder and let's actually replace this local host post grass with my new database okay so this is my new database right and then what we're going to do is come down to our schema. Prisma we can close down the neon console right we're going to have we're going to start designing the table we're going to start with obviously a user table so in order to define a table within Prisma you can to create a model so this will basically say that okay I'm going to create a new user table and then the user table will have a ID which is a string which is a a primary key ID and by default we're going to give it a CU ID as index uh the user will obviously have an email address which is a string and it has to be unique it will have a first name which is a string you will have a last name which is also a string right we will have a image URL which will also be a string but it's optional because it might not have an image okay and then I think for now it's fine so let's actually save this and then to push our Prisma schema up that means we have to sync our tables within Prisma schema the Prisma up to our actual database what I'm going to do is going to run sudo bun Prisma DB push so if I press this right and I enter my password you don't have to run sudo by the way you should just be able to run but Prisma DB push this will basically migrate our table so we can see that they loaded in our environment variables and it detected that we're using this database URL right and then now it says that our database is in sync with our Prisma schema right right and to confirm it if we do bun Prisma Studio this will open up a kind of database view for us and we can see that we have a mod user model which means it's a user table with the different fields we have ID email first name last name and image but we have no rowes yet okay and then I let me show you uh if I come down here I'm going to create a new file called playground. TS within my source and this is where we can kind of experiment around with typescript so I'm going to say await DB uh sorry Prisma sorry yeah where is my Prisma TB yeah where's my DB though it should be here okay I'm going to reload my window so that I can get access to my database okay so I'm importing my database from SL server DB which comes from this file here so you can see we're exporting database I'm going to say await db. user so I can see that I have this user object here do find uh sorry I'm going to say create right the data will be so I can see if I press control space I get like the type sense intell sense of all the fields I can create with I'm going to give an email address of test at gmail.com I can give a email I can give a sorry this will be email address I can give a first name of Elliot I can give a a last name of Chong I can have a image URL if I want but I can leave it out because it's optional right and you can see the ID is also optional because it's automatically generated by default and so to run this file what I can do is I'm going to quit off my Prisma Studio I can run Studio Bun Run playground. TS so when I do bun run to the file that the typescript file what it's going to do is I'm going to say console.log done right I'm going to press enter uh okay it's not found because it's actually in Source playground. TS so you see I run it it's going to say done right because it gets console. log out and if everything successful I should be able to see a new Prisma user table I'm s new user Row in my Prisma table so let's run pris PSE sudo buan Prisma studio so then I should be able to see my user now has one row with the test Elliot and Chong inside so that's super cool and you can see the ID has been automatically generated so this is how we're going to be interacting with our database from now on so I'm going to delete the row right I'm going to start a fresh I'm going to press delete right and now what I'm going to do is I'm going to set up a web hook with clerk so why do I need a web hook with clerk so this is what is happening right so it's a very similar kind of structure with us trying to sync the emails we have to sync our user with click okay so imagine I take click over here so click is here and we have our application our nextjs app here right so when a user comes so imagine we have a user a user comes to our app right and then they sign up to clerk so clerk has the actual data right CL has has their own database right they have their CL database and they have the the user so imagine Elliot gets created in the first row of their database but the thing is we have our our own database right with our own user table right and the thing is I want I want the data from clerk to be inside my database as well I want to store the user in my database as well so how I'm going to do that is I'm going to use the same concept I'm going to give clerk a a web hook so I'm going to expose a web hook saying that hey clerk whenever whenever you get a new user new user created uh ping me so it's going to then ping my web hook which is like let's say https actually no I can say I can do SL loal host 3000 API clerk web hook okay and then whenever someone get registers a new account with clerk clerk is going to hit my endpoint here with the information about the user it's going to tell me the name the email the image URL Etc right so I can then take this information that cler has passed me and then I can write it into my user user table and that's how I can keep my data my user data in sync with cler's database so to actually orchestrate this process we're going to basically come down to the clerk dashboard here again right we're going to go down to back go back to the user configure so if you go down to configure under dashboard so we can see we have this web hooks session okay so this is where we're going to tell clerk to hey ping me whenever you have a new user so I can say I'm going to add a new endpoint right and in this case I'm going to do like HP loost 3000 and I'm going to say hey hey ping me when you have a new user right so I'm going to subscribe to events and this is exactly what I mean by kind of a ping me when something happens so the the phrase after when is the event that I want to subscribe to so I'm going to say whenever a user was is created uh ping me so this what this is how I do it uh you can leave the advanced configuration as default so we're going to see that this won't actually work why can't I put Local Host 3000 in this because imagine how CL is actually working whenever CL CL has this uh endpoint here right it's going to try to Pink Local Host 3,000 but the thing is they the clck server doesn't know what Local Host 3000 is to them a local host 3000 is the is the Local Host 3,000 that's running within their server The Local Host 3000 is not the Local Host 3,000 that is running on our server right so imagine like I'm trying to develop on my MacBook so this is my MacBook right my MacBook is a has a private IP that is within my house within my own router and then cler has their own kind of set of server and their kind of network within their own infrastructure so the local host on clu server is referring to a different machine than the Local Host within my server within my network therefore in order to make sure that cler can actually send information to our to our Local Host we need to set up what we call a web hook proxy so I'm teaching you a lot today I hope you kind of stay with me these are pretty advanced stuff that I've not seen anywhere else on YouTube but if you follow with me I'm sure you will learn a lot of new and cool stuff okay so a web hooks proxy is basically a external service that sits in between uh the clerk and our server so this proxy lives on our our on my on my MacBook so this is a proxy that listes on my MacBook and then what we're going to do is this proxy will expose a public URL a public domain like let's say test- web hook. cloudflare.com so cloudflare will help us will give us a public URL domain and then we can actually take this test web hook and we're going to basically put it in this end point URL so test- web hook do cloudflare.com So then whenever click get SE the new user is created it's going to Ping this test web hook and because this test web Hook is a proxy that is in my MacBook this proxy is going to then send forward the request to my local host so it it goes to this middle step where it sends to this public domain and this public domain then sends it to my uh logo 3000 so this is how I can kind of test web hooks locally and later on we're going to use this same concept when we are trying to test the web hook with oringo to sync our emails with the oringo server okay so to get this test web hook right what you going to do is does this service right is called you can run MPX uh un tunnel at latest and you can say tunnel HTTP loal host 3,000 so this command this command here creates a proy web hook that connects our connects the public domain to our computer so if you wait for a few seconds we can see that it's saying that started Cloud fa tunnel to logo 3000 and this is exactly what it is it is just a tunnel right because imagine here what it's doing is if you imagine like a a slanted rectangle right this is basically a tunnel right it's tunneling the request from the server to the to my to the proxy then to the proxy it tunnels it into my Local Host 3,000 all right so we can see that the tun is ready at https veterans do ESP blah blah blah TR cloudflare.com and if I copy this right and I paste it in my domain you can see that is actually accessing whatever is exposed on my Local Host 3000 right this is whatever is showing on my Local Host 3000 and if I kind of stop my server running and I refresh we can see that it's saying that 502 B Gateway because nothing is running on my local 3,000 that's why it's it's saying that there's nothing running so there's nothing to tunnel to so going start my server Again by running bndf and now if I refresh it's going to it's going to it's going to hit my server again you can see is is seeing the request and my request are tunnel to this public URL again cool so now that we have this public URL I'm going to copy this I'm going to paste it into this uh web hook endpoint URL all right and one additional thing I'm going to do is I don't want it to just hit the the base end point I want you to hit like something like SL API SL clerk SL web hook so let's actually set this up right so I'm going to just make this work first I'm going to press create so this will create the web hook right and then what it's going to do is we can come back down to our uh to our vs code and now let's actually create the web hook route so go to your your /a/ API so you can see this API already has some trp see routes that is being that is put here by create T3 app so what I'm going to do here is I'll just take this API folder I'm going to create a new route called uh Slash Clerk and inside here I'm going to create a new folder called Web hook I'm going to create a new route. TS so this is a special route this route. TS is a special file within nextjs that allows people to hit my next year server with/ API cl/ web hook so if you see the structure here/ API each folder represents to a route segment here so you see/ API SL clerk you see this clerk folder this represents the clerk segment and then the web hop is another folder which represents another segment and finally if you have a route. TS at the end of it this will correspond to the route Handler here and then over here I can then uh have my clerk web hook okay so the first step here I'm going to to expose a post end point because what clock is going to do is it's going to send me a post request to this this proxy and the post request is going to come down to my server so I need to be able to handle that post request so export cons post right equals to an async function or async Handler the first request is going to be a request object right and then the function what it's going to do is I'm going to first extract out the data from await request. just on Json right and then we're going to just first console.log out the data I'm going to say clerk web hook received and going to consol the data and then I'm going to return a new response to clerk saying that herey I received your web hook everything is good I say web hook received with a status of 200 okay and of course like I hate why this es L thing is always so aggressive so I'm going to do is I'm going to come down to my es lint and I'm going to just disable a lot of stuff here I'm going to disable this two stuff and I'm going to just disable everything here boom so hopefully this goes away okay and yeah so I mean this is just a personal preference if you want to have trigger type checking just leave it on but but I know kind of what I'm doing here so I don't want uh yes to be shouting at me okay so we're going to test this this endpoint right so we can see everything should be good here and how we're going to test it is we're going to come down to our next our click dashboard and then I can actually test it out I'm going to press testing here I can select an events type I'm going to say user. created right I can I'm going to you can see that there's a schema of how the the Json payload is going to look like so we have a data we have the email addresses we have got the name and the last name Etc right so I'm going to press send example and so hopefully this will ping my my tunnel Cloud flare my cloud flare tunnel proxy and then it will forward the request to my endpoint so if I press send example so let's look at the console carefully 3 2 1 press so let's see do receive it um in this case it looks like nothing is happening so let's actually look at why that is the case funds locks all okay so you can see that it says failed and let's look at the reason why it failed so it's saying that veterans do blah blah blah SL API click web hook so I think the reason is because we have not authorized this endpoint to make it a public endpoint so remember in our middleware dots we need to specify that all the web hooks has to be a public endpoint because people are not authenticated when when a request is being sent through a web hook so we need to do/ API clerk web hook right and so hopefully this will make it so that uh the request can be uh kind of tunnel true so let's actually test it again so let's do test I'm going to send a user created send example and boom we can see post /i/ CL returns a 200 request and we can see that the data is being locked out so it says CL web receive this is what I'm console logging out in the console and the data payload is whatever we have here right so we have got the email addresses we got the data blah blah blah and if I go down to my overview right I can see that it says the web hook succeeded cool so now that we Reed this email like sorry this data payload when a user gets created we can actually write the data into our own database so what I'm going to do is I'm going to say cons email address equals to data. email addresses the first email address I'm going to take that email address we have the first name which is data first name we got data. last name we have a cons image URL right so everything is from here you can read so first name is here last name is here we got image URL somewhere down here and then obviously we have the ID we can take data. ID which is like somewhere here here uh here this ID uh this one user underscore something something okay and with that we can then await DB we're going to import our database do user. create we're going to create a new data that has where sorry it's not yeah I'm going to create data that says the ID will be the ID that we received we have the email address to be an email address first name last name and image URL so with that we can save it and then what is going to happen is I'm going to test it again I'm going to test my web hook again and everything if everything works well I should see a new uh user Row in my database so I'm going to come back here um let's see can I test it hello uh let's go to web hooks here I'm going to go to testing I'm going to send a user Creator type right so if everything is correct I'm going to just console.log out user created with the user here actually no it's fine so I'm going to press send and I can see hey user is created everything is okay right I can just ignore the console loog now so now I'm going to close down the tunnel so the web will no longer work but I can check my Prisma studio and hopefully I get to see a new user that has been created with the information from Clerk and I do see it the user ID the the email the first name last name and the image URL perfect so now that we have this we can actually push this to production so that we can give clerk the production URL to kind of Link our our user uh our user data to our database okay so I'm going to deploy this right now together to versel and I'm going to see how it works okay so the first step is I am going to create a new GitHub repository so go down to github.com new right I'm going to say this is what's this normal human- YouTube I'm going to say a powered email client we'll make this private for now I'll public it later on so you guys can all reference it so let's create a repository cool and then I'm going to follow the example here so the first step is to get in it but the thing is we already have an initial repository so then I'm going to do get ATS all wa let me check my gting nor uh okay I should probably do get status I can see that uh everything here is not added here so I need to Stage it I'm I want to I'm checking if I have a okay the EnV has been ignored so it's fine I'm going to add everything to the stage environment and then finally I'm going to run get commit DM initial commit for clerk then I'm going to basically add the remote origin and then I'm going to push it up to me cool and with that if you refresh we should now be able to see our repository on GitHub I'm going to now go to verel I'm going to create a new project I'm going to import it from my normal human Dash YouTube import this right and everything I can keep it the same and I can then put in the environment variables right so I'm going to copy everything here command C I'm going to just paste it in here and you automatically populate and there's actually one more thing okay you know you know what it's fine we'll do it later but then I'm going to press deploy and hopefully it's going to kick off the deployment process and we're going to see it live okay so I'll be back in like 50 seconds and one more thing that I want to add before we continue okay okay it errored out by the way and I think I might know why yeah because it's saying that to compile because there is some type errors and like we can ignore it for now so go down to the next config.js and inside the confict what I'm going to do is going to press typescript and I'm going to say ignore build errors to be true right and for ES lens I'm going to just uh ignore doing builds as true as well so when we deploy this if we Face any type errors it's just going to ignore it and just continue deploying it which is fine honestly if you know what you're doing so let's do the same we're going to do get adds get commit I'm going to say uh ignore type errors I'm going to get push this up and hopefully just going to kick off a new deployment right so let's look at deployments um you can see start someone is starting build to build and then hopefully this will succeed because we're ignoring all the build errors for now okay and before we continue I'm just going to to go down back to my chisma studio and I'll delete the test user here just press this and delete the record and boom we got it deleted nice so after it deploys which it should deploy any time now it's going to give us a domain cool so you can see it has given a domain called normal human- youtube.app and it's going to go to the SL path but thing it's going to kick us to the signin page so now we are back to the signin page but the thing is it's deployed so good news so now that we have this actual URL right we can then use this URL as the production web hook for clerk so I'm going to just close this down so now instead of having to rely on a proxy here instead of having to rely on a proxy I can literally just have the actual domain right www.n normal humanyoutube docomomo so then this because it's already directly linked to our production database right everything will work perfectly so let's now go back to our clerk web hooks right so let's go back here I just refresh the page okay wait for it to load and then I'm going to press this I'm going to edit this I'm going to just remove the the proxy I'm going to do https sc/ www.n normal d. normal human that youtube.com so make sure that this domain is the one that kind of versel has given you here so make sure it matches this exactly and you need to you should add the www in front as well so I'm going to press save and everything should work fine now and so if I test it again so if I test my endpoint uh how do I test my endpoint uh and has all fail activity locks activity how do I test it come on testing okay if I press send example this should give me a 200 request which means succeed so send send example and hopefully please oh my God it failed so let's take a look at the reason why let's go back into our sell locks so if you go on to Locks let's look at the reason why it failed uh is it because ww w. normal human-y youtube.com oh because ww doesn't exist okay that's my bad that's my bad so what you have to do is come down here to edit and remove the www dot right and save right wait no I know why oh my God because it's a versa.com oh my God I'm so horrible at this yeah so let's try www. normal human- youtube. versel app uh what we remove the www come on okay this works so let's remove that we have normal human- YT do. ver. app so I made a mistake I told you guys to follow exactly and I didn't follow exactly right so if you go down to project you can see the domains this is the one that you need to copy right versel app so let's save this and hopefully hopefully this will work so come down to test testing and let me send a user created status send example and pray that it succeeds and come on succeed or fail yay succeeded so this means that if you go down to our logs we should be able to see a user created lock here and if we check our if we check our Prisma Studio let's restart this Prisma Studio we can see the example user being created again cool so our production works that's good that means that we can start actually using clerk in our Local Host so I'm going to do BND again right and then I'm going to do is just come back to my uh react on my next J Project and finally finally if I press continue with Google and I press my kind of uh email account this should basically send a request to Clerk and clerk should send a request to my production endpoint my production web hook and automatically create a user for me in my kind of in my database so let's do Prisma Studio okay and finally I should see yay I see my ID my email address my first name last name my image URL right and this image URL should correspond to a my actual profile image hopefully y the marble cool so we finally got our authentication our database going and you learn a lot so much about web Hooks and this web hook stuff is important the reason why I put it here in front is because it serves as a educational purpose so later when we learn about Oro web hooks you have a more comfortable experience with this kind of syncing between an external data source and our own data source right and if you come down to Clerk and go to users I should be able to see a new user has been created in the dashboard and with that we have kind of uh successfully set up our project our nextjs our Prisma DB our authentication and now we can start actually building some cool data end point stuff with email syncing so I'll see you in the next section cool stuff okay so now we finally get to go into oringo which is like the main cruxs of this video to set up the the email client okay so the first step is you have to go to or. and create an account and loog into the dashboard if you have not already right and so once you're in it you're going to basically come into the dashboard and you get to see you get to create a new application so let's do that let's give you the name normal human- youube and let's just choose the the mailbox API so I'm going to press save so this will create like the API keys and the API secrets for us blah blah blah and cool now we can finally see that they gave us like like test St for us to use and so what I'm going to do is I'm going to first kind of Copy in the client IDs the client secrets and the signing secret into my EnV first so let's just do that so let's go to our EnV and then oops that's not it let's go to let's do oringo D API uh sorry oringo client ID is this boom boom boom and wait I'm going to check I'm going to cross check my code actually no it's fine and then we have the oringo client secret let's copy this from here paste it in here so you should not show this St by way I'll delete this account after filming and finally we have orango signing secret which is this thingy cool so we got this three client ID client secret and signing secret so this two stuff are basically what is going to what we're going to use to help oringo identify our application and help us request uh authorization for someone's email account on behalf of us okay so now that we have this let's actually go back to the excal draw flow to see what's the step by step so remember the first step is we need John or whoever is trying to use our app to give us access to their email inbox right and so to do this right we're going to basically ask them to go to the Google consent consent screen right for for them to like give us access so how do we actually get the URL to go to the con conent screen right so I'm going to show you where I'm getting all this information from so if you go into uh doc uh oringo docs uh docs oringo yeah dro.com and you can see email API right and let us go down to to email messages okay so they have a very comprehensive API for us to use so the first step here is authorization right we need to start an account authorization flow which means uh helping us get access to their Gmail account okay so see what we need from what we need to start it is we need to hit this slv1 off/ authorized endpoint right and then this we have to passing out client ID so this client ID is the client ID we got from our dashboard and we have the service type which is like what do we want do we want to connect to Google uh to Outlook and they support all of the different uh services and then we need to pass in the Scopes which is like what actual information do you need so in this case we need to like read email write email send email Etc right and then this this is the response type so the response type is uh do we want to get back a code or a token so this is what they call the oo 2.0 flow which I have done a video before in kind of my previous authentication video so if you're not familiar with the O 2.0 flow do check it out to make sure you you understand what actually is happening so we're going to basically whenever we're going to hit this endpoint with our client ID and secret Oro is going to give us back a code and then with a code we're going to then exchange it with another endpoint to get back a token and finally this token is what you see here you see oringo sends us back a token so this is the token that we are aiming for remember without the token we can do anything and remember that a token is unique to each user's email account okay so we're going to execute this flow for us so let's actually write some code okay what I'm going to do is I'm going to just reset I'm going to come down to my lip folder I'm going to create an oringo dots file um yeah of course I don't have have permission to write to my own computer anyways after we have done that I'm going to create a oringo dots file okay and this oringo TS file is going to hold all my kind of a code to interact with Oro API right so the first step here obviously is we need to export cons um get or wrinkle off off URL right it's going to be a kind of a it's going to be a a server action so because it's a server action we're going to just do use server right and so what's going to do is we're going to accept a service type so remember this service type can either be Google or Office 365 right which is what they stated on their documentation or it can be any one of these right so with this I'm going to first kind of get the user who is locked in so I'm going to do cons us user ID equals to await await off and so this off comes from cl/ nextg right so next will tell us whether this user is logged in if we don't have a user ID that means they're not logged in because just true and error saying unauthorized okay and then after that we're going to basically hit this endpoint this/ authorized endpoint so what I'm going to do is I'm going to uh construct the perms so this perms is what is needed to pass into this URL right because of the get request so I'm construct new URL search params and I'm going to say client ID is the process. env. Ringle client ID which is what we have set up here right remember here client ID and exactly what we need from here so I'm just like using whatever is in the docs right but just I'm going to code it out you can follow along you can refer to the docs if you want right and then uh I'm going to say a string to solve the type error we need service type which is the service type password in the parameter and then we have the Scopes right so the Scopes is mail. read right I should just copy this yeah so it is deiminated sorry deiminated by space so we need to read we need permission to read we need permission to rate write we need to send we need to create drafts and we need to have all the permissions right and then the response type is going to be code so we're going to take this code that we return and exchange it for the actual token that we can use on behalf of the user and finally the return URL so this is important because the return URL is the call back so remember here it's saying that orino sends us back a token actually you could do token SL code uh code right it's going to return us to this call back URL so this callback URL is what we kind of Define in the return URL so I'm going to do is I'm going to just just template in our process. env. next public URL so we have not defined this URL let's do that so I'm going to do next public URL to be Local Host 3,000 so this is where we're running so when we when we deploy to verell this will be htps normal human yt. verell app okay so for now let's just keep it at Local Host 3,000 and then we're going to do slash uh what we're going to do is SL API SL orle SL callback so I'm going I can you can Define anything you want here so this I'm going to just fit here so whenever oringo send us back the token I want you to just pass it to this callback URL and finally finally I will have to hit the actual API here right so I'm going to just return https call off or sorry api. or wrinkle. iv1 off authoriz question mark and I get just make the parameters into a string so that it will kind of fit the format that oranging go is looking for cool so when they visit this URL oringo is automatically going to redirect them to the Google consent screen or the Outlook consent screen depending on the service type okay and so to test this out what I'm going to do is I'm going to come down to my pit. DSX I'm going to create a new button here so I go go to my components here I'm going to say link Account button TSX sorry TSX right I'm going to make it a client component and I'll do RC right so this a es6 es7 snipper from extensions that gives me a arrow function component so let's rename this account button so I'm going to return a button here um and I should be able to import this I'm going to reload my window again because typescript vs code is so janky finally I can import it from my Shen /ui button all right and say Link account so when I click on this what do I want to happen right I want to basically because this is a server action remember we defined oringo here is a server action we can actually directly call this backend function from our front end code so this is a new nextjs I think 13 feature server action I'm going to say cons off URL equals to away get orle all URL so import from this this server thing right passing in Google let's just test Google right let's make it this Asing function and then let's actually control the log out the URL first okay then what I'm going to do is I'm going to go back to my page instead of rendering this hello world button I'm going to render this link Account button cool so my server is still running that's good if I go back to my kind of local 3000 I can see my link Account button and so if I click on it hopefully I get to see my sorry I'm going to just expand this yeah Boop so if I click on this I should be able to see this is the this is the URL that has been returned if I actually press on this URL and redirect to it let's see where it goes it will go to okay it's going to show an error because look at what I say it says return URL doesn't match the configur URL in the app so it's saying that we need to configure this call back URL the configur the return URL so let's go into the dashboard let's go to settings and we need to find the callbacks right so this authorization code grand URL so we need to Define HTTP colost 3,000 SL API oreno callback we have to Define it exactly as the Callback that we want to use here if it doesn't match it's going to tr the error so uh alongside that let's actually also put in the uh normal human- yt. verel app API Oro call back so in case we don't forget it later I'm going to press save and hopefully hopefully if I try again so I'm going to clear my console press again and it should lead me to the Google content screen awesome so now it's going to ask me if I want to give permission to Oro right so let's actually make this automatic instead of having to click a link in the console so let's go down to our link Account button so I'm going to do window. location do hyper reference equals to off URL so this will directly allow my browser to navigate to the URL that has been returned from this so if I press this button it should now redirect me directly redirect me to the off screen cool and so we're going to go through this flow but before that I want to set up the call back right because once I finish the flow it's going to try to hit this/ API Oro callback so to do that I'm going to come down to my API folder I'll create a a wrinkle route segment then a callback segment so is exactly the same as the clerk web hook right the folder represents the route segment and I'll do route. TS so this will correspond to/ API SL sorry SL API SL or Rinko callback right you can see that the file names and the URL matches okay and then to actually process it let's actually just export cons get first right Asing request so whenever oringo is going to redirect us back to the this route let us just actually um try something I'll just do cons user ID equals to await off so I'm going to just check the lockin user first I'm going to just conso the lock user ID is is user ID I'm going to return a next response. Json saying that hello world so let's let just test this out and see if it works for us so I'm going to go to the flow and then after the flow oringo is going to redirect us back to this route and then they hopefully we can see the user ID being locked in the console as well as they see the response in the browser so I'll give access to this email right it says Google hasn't verified this app it's it's okay you have to go into a Google console and like make sure that uh you you pass this app through verification right can say go to oringo so sign in to oringo it's going to ask do you want to allow oringo to read and compose and send I trust myself so I'm going to press continue and then hopefully it's going to redirect us back to Local Host 3000 API or Ino and perfect that's exactly what we get we can see the message H has been printed out as well because this is what we return in the browser and they display the Json in this format cool and we can see we can see this this callback URL as well and we can see the code that's being returned so let's copy this and I'm going to explain it more in my excal draw so you see this oringo uh oringo callback URL this is the URL that return us oh there a huge payload but let's take a look side by side so we can break this down into a few things actually I'm going to just copy down here uh you know that's actually not a good idea okay but the main point is we get back a few kind of Route segments the first thing is we get back a code so you can see this super long code all the way until this request ID so I'm just K I'm going to just delete some stuff here I'm going to do dot dot dot right so we have a code segment and we also have a request ID segment right and then this request ID is also long so I'm going just trate it as well dot dot dot and we also get a status which say success that's good and then we have a type which account of result so we get back four things right we get back the code the request ID the status and the type so we need to do check two things the first thing is we have to check the status is successful if not we just true an error then secondly is we take the code that has been given to us by oreno and now remember we need to exchange the code for the token always remember the token is the important part the token is what allows us to make requests on behalf of of John gmail.com so if you do have the token we can't do anything we can read emails we can send emails so we need to now exchange the code for the token and if you go down to the documentation we can see that they have this URL that allows us to to exchange a code for the token so we give them the code and in return they give us access token right as well as the user and the account ID so you see this account ID and token this is exactly what we're looking for in order to save into our database so we need account ID which corresponds to the email and we need the access token which is what is actually the most important part cool so how do we actually get the token sorry how do we actually get the the code from the URL here what we're going to have to do is okay first we got to check if not user ID we just have to uh just redirect them uh actually not redirect let's just show an error saying that unauthorized with a 41 error we're going to get back the PM so we can do get the PRM by doing request. next URL uh next URL do search prams okay so actually we can replace a request with next request because that gives us more information and then we can do con status equals to perm. getet status so this status is what you see here which is Success so we're going to check if status not equals to success right we're going to just directly save to to link account and then now we need to get the code so let's do param get code so we need to get the code get the code to exchange for the off token sorry access token okay and then we're going to do uh we're going to try to get the token so let's go back to our oringo and let's write another function right to exchange exchange exchange code for Access token so we're going to get back I'm going to delete this I'm going to get a code as a URL right and then what I'm going to do is I'm going to hit this end point so the end point that we saw just now in the documentation is this off/ token code so what I'm going to do is I'm going to write that out give me a second so we're going to use exos for this so let's actually bu install exos exos so exos is like fetch allows us to kind of make HTTP requests let's import ex from exos okay and then let's actually do the request so the first step is we're going to do uh we're going to do a TR catch block so con response equals to A we x.g we're going to get https api. oringo doio slv1 SL account and then we have to pass in a few things which is the first thing is the haters and the haters um sorry yeah let me see actually no this is wrong because you can see they actually looking for the app authentication which means they are looking for uh we need to just pass in the third object so note that the second object is MD but the top object is where we can Define our off and this is where we put in our username username and password so the username is process. env. client ID and the password is the client secret so this comes from the EnV variable that we have here right so let me see why it's showing an error oh sorry it shouldn't be post right it should wait sorry it shouldn't be get it should be post so let's check yeah this is a post request my mistake so got post so the second object usually is represents the body but we have no body we're passing in directly an off object and then we're going to just return response. data and the shape of the data that respond has four things right account ID token user ID and user session so let's return it as account ID so I'm just going to copy this boom right go have the shape so this for typescript and then if there's an error if exos is exos error that means it's an ex error we're going to just show a new error actually no we just say console. error we're going just log out the data and then if it's not ex error just like some random error we'll just log it out cool so hopefully we can call this function here in this so let's do that so obviously if there's no code first we're going to just return there's a no code no code was provided but if there is a code we're going to say token equals to await get oringo play exchange code for Access token we'll pass in the code and we'll check if there's no token we go then through an error saying that fil to exchange the code for the token right and then lastly now that we have the token right we can actually access it we can exchange token for the account information so the account information will give us back the email their names Etc right so let's do that so now I want to show you where we are in the process we are here so we are still dealing with the token and email but now we get back the account ID and we get back the token but we are not sure what their addresses so the final step is exchange exchanging the token for the address and we should be we should then write it to the account table okay cool and so let's actually write the function let's go back to Oro let's write the function to uh get Account Details so for this I'm going to take in the access token as a parameter okay I'm going to hit the endpoint so I'm going to do a TR catch on response equals to A wait ex. get https api. rangle.io V1 account okay and then for the for the HS we're going to pass in the authorization to be Bearer and then uh templating in Access token so this will allow us to authenticate uh we are exchanging the users access token for the account information so remember the access token is specific to the user is specific to the account and therefore when you exchange the access token they will give us back the information regarding to the person who holds this access token okay and then finally we return response. data as we get back an email and we get back a name that's all that we need right and then for the kind of error handling what I'm going to do is I'm just going to take this paste this in right so I'm just checking if it's an ex error I'm going to just console lock error Etc okay and then now we can use this get Account Details in our R so then we're going to say hey let us actually cons Account Details equals to await get account details let's import that passing in the token right that we got soorry so this token we can see that we have the excess token within it right and finally we got this Account Details let's actually conso the lck out the Account Details um actually it's fine I want to write so now we at this step we're here we have all this information we have account ID we got the token we got the address let's actually write it into our database so let's actually create a new table within our data database for the account right so here this account table let's create this so model account we have the ID which is a string so an account is linked to a user ID so this user ID is a user ID within normal human is a user ID from clerk whereas this account ID is the email account on oringo side so you can imagine the structure where here let me draw it for you so we got normal human so normal human has a user so this user can be like uh it's just a user object so this user can have can be linked to many email accounts so like for me like I'm Elliot right I can have Elliot CH 16@gmail.com I can have another email I can have I can have three emails I can have so many more emails so this is each individual here is one account and each individual here is one user so one user in our system can have multiple accounts so I hope this makes sense so let's do that relationship okay and then so remember we have a token so this is access token right uh so I'm going to write access token here which is a string we need this and this is a unique string obviously all right we need a kind of email address which is also a string we have the name which is also the string that we get back from the account details and then and then let me see if this is enough uh do I want to include this um no no it's fine for now it's fine let's do it step by step okay so then let's actually link it up to the user ID and then now we can make the relationship a user can have money multiple accounts therefore it's put in this array right and then we have the relation which is the user ID cool so now let's actually push this up so we can do sudo buan Prisma DB push my hit is blocking so let me move this here so I'm running sud the but Prisma DB push so this will push it will create a new table called account within my database okay awesome awesome awesome stuff you know actually I can run Prisma Studio on another terminal so let's always be running Prisma studio so we can always see our database so now we can see that uh I can see now I have account table in here with no roles okay so let's go back to this terminal cool cool cool okay all right finally I can then do uh I can then let me see what should I write I want to insert into my database right I can say await db. count dot uh so I'm going to import database do upsert so what upsert is is a comination com combination of updates and insert so why saying that if the record already exists I want you to update the information but if it doesn't exist I want you to insert it therefore it's called upsert update and insert so I'm going upsert it so I'm going to check where do I already have a token do account ID right so if I already I if I already have a to like account with this ID I'm going to update it so I'm going to update it to uh make sure that the token the access token is the most is the latest access token that I'm getting back from this callback right but if I don't have a account within my database I'm going to create a new account I'm going to pass in the ID to be the token. account ID I'm going to have a user ID which is the user ID from from clerk right so let's just shorthand this and then we have the email address which is which comes from account details. email and obviously we have name which comes from Account Details on name cool and the last thing we need is access token but I do have access token oh yeah so the access tokens will be put in here and then finally after we have all this we are now in kind of this step so let me show you where are we we have saved it to the database and now the fourth step here is use a token to SN that inbox but let's not jump ahead so fast yet let's just make sure that this works right I'm going to just return a I'm going to return instead of uh normal URL I'm going to just um let me think yeah it's fine I'm going to return a redirect redirect to/ mail so this mail is the mail inbox you're going to see because they connected their account they should go to the mail uh dashboard now right and this should be fine so let's actually go to this flow and hopefully they redirect us to mail so uh let's see let's see so if this works I'll be super happy okay so if I refresh hopefully it returns us to SL fil to exchange code okay because it probably expired so let's actually try this again so let's go to Local Host 3000 let's press Link account so let's go let's link this account let's go to oringo Let's press continue allow and so what's this going to do it's going to give it's going to send me back to my URL and then oh fill to exchange code for exess token so let's look at the error here that's causing this uh okay okay interesting not found so it's saying that api. Oro SL V1 account something is wrong here okay it is my mistake here so I copy the code wrong uh so you see exchange code for Access token here this URL shouldn't be slv1 account right it should be slv1 SL off/ token right and then we want to pass in the code so let's actually template this in let's change this to back six cool and so because you see the endpoint to kind of search for the request exchange token is SL all/ token code so I made a mistake here this URL should be uh SL SL token code and so hopefully if I refresh the page I think it's expired um let's see yeah Okay cool so it works and you can see it redirected us to/ milil and what this means if I check my console everything should go fine cool it says you can see that my call back worked right it compiled and it ran properly and now if I go into my Prisma and refresh I can see I have account that has been created and let's look at this and cool so I have a account ID with the user ID so this is a user that corresponds to this you can see that it's the same user ID we have the access token so this is the access token that we need to make all the requests down the line we got an email we got the name and it's link to user very cool all right we're doing great we are doing extremely great so now that we have this access token we can now go wow we can start syncing emails right so now let's actually go to the final step of the initial syncing process which is syncing up the the inbox okay so if you go into the if actually let's just show something on the mail page first so let's go go to let's go to my page uh Pages sorry my app where's my app here let's actually create a folder called mail all right and then under the mail I'm going to do index. sorry page. TS X I'm I'm just going to export a um RFC mail dashboard and then I'm going to say return mail dashboard so hopefully you can see mail dashboard is being show cool so now have this midd page so now actually let's continue doing some backend work we got back the access token so now what we want to do is we want to exchange it sorry we want to sync it up sync use the token to sync the inbox in Gmail Okay cool so let's actually go down here to email messages sorry email sync okay so this is another part of explanation that we need to do together okay so let's go to sync um so let me go to to the docs. oringo doio and let me show you what they are talking about synchronization so let's read this together actually it just bring the same page anyways so we're going to start a new email sync what this means is we're going to send a request to G to oringo saying that hey we want to like uh get the first initial like all the current inbox into our system now right so we can see we can pass into two things we can pass in the days within which is how many days within the last few days we want to actually scan the emails so I can pass in a huge number like like 1,000 that means I'm getting all three years of the emails uh scanned to like scanned into my database right but for now for testing purposes we can just set it to like a few days so we get back the current inbox within the last few days of actual inbox and then the body type which is HTML text we're going to just pass in HTML to just get back the actual HTML body right okay so so so so after we after we start a new request thing you can see the response is we get two things sync updated token and sync actually that's the only thing sync updated token okay we're going to take this sync updated token that we got from this request then we're going to use that token to actually request the the actual emails okay which is which we have to get by this Delta token okay so this Delta token is a token received from the pre previous initial start sing request and we can also have a pagination token so because this request change email might have multiple Pages we have to keep requesting it until there's no more next page and then we know that we have gotten back all the information okay so this is how it's going to work okay uh let me draw this out for you so we have this initial sync endpoint what do they call it actually they call it uh slil sync SL email/ sync so this is the initial sync initial sync endpoint okay and then we have a request changed email and this the the sync updated so let's do that slash email/ sync SL updated right right right cool stuff so this is to to get the actual updated emails right so when we first hit SL email/ sync it's going to return us with a response of whether is ready ready or not it's a Boolean and a actual Delta token right that's why it returns us a Delta token right so if we we we see that the response is ready we're going to take this Delta token right this Delta token we can then pass into the email sync updated endpoint right we can see the sync updated endpoint here receives a data token so this is uh this is to tell oringo where do I want to begin syncing from so imagine okay this was like super confusing for me for when I first interacted with this so I really want to break this down for you guys to make sure you guys understand so imagine you have like a Gmail inbox okay and I have like I have like a lot of kind of emails okay so imagine I have this situation where I have a list of like emails in my my inbox account and I'm going to segment it by you can see one day ago two days ago and three days ago so anything anything above this is from one day ago anything between here is from two days ago everything here is 3 days ago and obviously here uh you can see it's 4 days ago right so we're going to say we're going to pass in the initial Sync here we're going to ask how many days in the past do I actually want to retrieve the emails so for example I can give a random number like two or like 200 depending on how much data you want to sync so if I say I want to sync two days ago what is what Oro is going to do it's going to look into my inbox right it's going to find the last email that was sent latest two days ago right and it's going to give me this uh it's going to give me like a a bookmark and the bookmark is what they call a Delta token so they're going to give me a bookmark SL token thing so we call this Delta token right and this token is important because then I can take this to token right I'm going to point this here I can take this token for example this token they say like ABC right this token points to the position in which I want to get the latest email from two days ago and then I can pass this token into the sync updated endpoint right so when I pass in the token like called ABC here the sync updated token will then start giving me will start taking emails from this in batches it will give me like let's say like five at a time so then it will give me this record so it will give me a list of Records which is a list of email addresses right and then it will give me a next next page token so because it's doing it in batches right right so here it's going to give me another page so it's going to move the cursor up here because it has already given us back the first like five emails here so then it's going to give us back let's say a page token the page token is like P1 123 1 4 so then we're going to take this page token we're going to then run this run this page token into this sync updated function again end point we're going to pass in p12 3 4 sorry p12 3 4 and then because the updated token not that we are now looking from this point onwards is going to give us a next batch of emails right and then the next batch of emails we can continue adding it in email addresses we'll keep on adding it in until there's no more next page token which which means that the update the Sync has given us kind of all the emails right so now the pointer is pointing at the latest point in time so imagine this like new 1 to3 this is the the latest Delta token right remember this the latest data token and we know now that we have completely syn sync up everything from the two days ago so it's a pretty smart book marking system to actually help us get it Page by uh pated one one batch at a time and with we're going to store the latest Delta token in our database the reason why we need to store this new one 123 in our database we need the latest Delta token because remember there can be new email messages that comes in so I'm going to just keep copying this so imagine there's like five new emails that come in here right what I'm going to do is whenever whenever Gmail gets a new email right Gmail is going to ping me remember the web Hood concept here Gmail will ping me uh will ping me saying that hey uh there's a new email there's some new emails what I'm going to do is I'm going to take that ping I'm going to take the latest datta token which is the pointer in which I last looked at I'm going to point this I'm going to put this datta token into this uh SL s/ updated and because this uh this to this Endo knows that I'm looking from this point onwards it will then give me the latest it give me the latest five tokens based on the data token that I've taken it will then give me the latest records from this new one 123 right and then it will give me back another new token to signify that this is the new n per se so then we have a new let's say new um 6 SI 654 and so we're going to continue using this process we're going to store the latest data token whenever we get a new sync request so that we can always have a pointer so whenever new emails come in again I can always pass in this Delta token to oringo to get back the kind of the latest ones that I have not seen yet because the alternative to this which is that we have no pitch token kind of concept every time there's a new kind of there's a new email what I have to do is I have to read the entire history of the mailbox and I have to load into my database which is super wasteful because I'm I'm not sure where I stopped that if I do have the the idea of bookmarks I don't know why I stopped that and that's why if there's no tokens I have to reread all the emails into my database which is super inefficient so I hope you understand this like more the most kind of confusing part of the entire process at least for me maybe it's like easier for you guys to understand so let's actually encode this process and sync up our initial emails into our database okay all right so we're going to continue coding here so remember here after we have gotten back our after we have linked our account what I'm going to do is I'm going to come down here here and I'm going to I'm going to basically start the initial thing process so I'm going to create another API endpoint called Initial D sync I'll create routes. TS right so this export cons post go to x async so this is the Endo that I'm going to hit myself after uh the after we have created account to actually begin the syncing process Okay so so let's actually import this so initial sync right so this is where we actually get it so uh after we done after we have done here okay uh I'm going to do is I'll do something like I'm going to hit hit uh trigger initial sync and point and I'm going to just trigger this and directly return the redirect because it doesn't make sense because the sync is going to take like a a few minutes like maybe a few seconds but I still going to take at least 30 seconds to do and so we don't want the user to be stuck in the loading state for 30 seconds not seeing anything before returning them to a UI therefore we're going to return the UI directly but we're going to run this asynchronously and to do that we're going to download another package here right called install ad verell SL functions so this just allows us to keep running our asynchronous code while directly returning a response from our import so let's import this function called wait until wait until so if you hover over it it should give you yeah you'll extend the lifetime the request Handler to the given promise so we're going to run a promise here so we're going to wait for the initial thing to complete before kind of shutting down this process but we're going to immediately return them a redirect response okay so we're going to do wait until what I'm going to do is I'm going to do ex. poost I'm going to post to my process do env. next public URL SL API initial sync so this is the initial sync that I'm building on right now so you see/ API initial sync this corresponds to SL API initial sync right so I'm going to hit this endpoint and I'm going to pass in a few things I'm going to pass in the account ID that I've gotten which is the account ID and then I'm also going to pass in the user ID that I've got from here and finally I'm just wa get. Den uh response I'm going just console. response. data and then if there's any error I'm going to just console. error fil to trigger initial syn so hopefully now after we connect the account they're going to redirect here but we're also going to see the kind of initial sync process starting to run okay so let's actually run the initial syn here so what I'm going to do I'm going to just console the lock I'm going to just get back the body I'm going to get account ID and user ID from request. Json right I'm going to check if there's no account ID or there's no user ID I'm just going to quickly through an error saying that we're missing one of them okay cool and now with that we're going to just search for the account within uh search for the account within our database so cons DB account equals to await db. account find unique where the ID equals to account ID and with the user ID equals to the user ID so there a unique uh compound key pair and so if it doesn't exist in our database we know that something wrong happened so we could just return a quick error here not found and now finally so we need to run a few steps here right we need to first uh syn uh perform initial sync right after performing initial sync we're going to get back of the the list of emails right we can get back a list of emails so hopefully we can do something like cons emails equals to await perform initial sync and then we can just do like a way sync emails to database and we can pass in the emails hopefully this is the the kind of end results that we're going to do we're going to get our emails from this and we'll then get the list of emails that actually write into our database Okay cool so let's actually write this perform initial sync function and to do this I'm actually going to create a rapper class on the account account object okay so this account object will allow us to have many methods on it like perform initial sync right I think it'll be super convenient so let's come down to our lip our library here let's create an account. TS so this account. TS right is we're going to export a CL class account so we have a private token here which is just going to be a string and I have that's a Constructor to take the token and pass it into this. token so then here I can just have a new account equal to new account from our lip and we can actually pass in the access token uh sorry access token do I have that uh sorry DB account. access token so now within this I have this token I can use this token to make requests on behalf of my my email inbox right so for for example right you can see that in the oringo API um for the email sync you see it it requires a it requires a account token so we need to pass this account token if you can see here uh let's see here what's this um right here you can see we will hit this/ email/ sync updated all right the initial sync and will pass in the account token to the barer off so this account token represents the authorization token for a specific user SL accounts that we are kind of syncing to okay and so let's actually have a function say um let's do uh yeah Asing per form initial sync so this is going to we WP in a TR catch block we're going to try to start the sync process that we describe inex Cal draw so let's just do let sync response equals to await um this do start sync okay and let's actually also write this another private function so this private method will only be accessible to this class okay and then this start syn function what it will do is it will um have a response equals to await ex. poost right we got to post to um What's this called This is https Epi rink. iv1 SL SN slil SL syn okay and then with this with this thing response we can then then uh we need to pass in the um the authentication so let's have an empty object because there's no body here right we only need to pass in the haters and for the haters we need to pass in the barer token for this access token here cool and then for the params right for the pams I can see right no this PM Is Here Yeah so the pams here we need the days within and the body type right you see the query param days within and body type so we have days within let's just put it as two days to begin with right obviously like if you want to sync more you can do like 3 days 300 days like the the whole history you can do that right for now just want to test with two days and I'm going to do body type to be HTML cool no space here and then I'm going to return response. data okay and it's going to return us with uh promise actually I'm going to I'm going to write generic so what the shape of this so the shape of this um okay you know what let's create a types. TS file because for this types you're going to hold all the typescript types that that's going to be needed from the response type of Oro so I've actually coded out already and so you don't have to kind of code it out again use typescript right so let's just go to Source let's create a types. TS and I'm going to copy in this whole thing okay so like you should just copy from the repository that I've linked down below but the main point is that we get a sync response which we get back a sync updated token a sync deleted token and whether it was ready right we have a sync updated response which the next page token the next data token and a list of email messages and we have the email address schema which has a name and address and this is how email message look like so I've already looked if I I shown you this just now if you go to the just Json file that I show you just now we have everything that in here we have the trat ID created time we have the system labels I rype everything so you have to worry about it we have the email address we got the email attachment the email haters and so this all the types you will need in the project so I don't to manually type this out so you can just copy this okay so with this uh I can then safely say that whatever this will return me with a sync response cool right and then finally you can see if I hover over this it's going to return a sync response and if I do a wait start sync this sync response I can see whether that's ready whether it's updated whether whether it's a deleted token so if you go back to our sccal draw I really want to make sure you guys understand this and not just blindly coding it out so remember the first step is hitting this initial sync end point right and we're going to check if it's ready if it's ready we're going to get the Delta token so this Delta token is going to point to the latest Mage we send here so The Bookmark at 2 days ago or how many however many days ago you pass into this right so it's going to return as The Bookmark at this point of time okay so so let's do this let's do that let's do that so start sync we get back to initial sync process um perform initial sync okay so we're going to check right while not sync response. ready right so while it's not ready we're going to keep hitting the end point until it's ready we're going to just await a new promise we're going to wait for 1 second before hitting it again right so to give it some time so we'll just set the sing response to AIT start syn again so at this point once we exit the loop that means that sing response is 100% true is ready so now we can access get the bookmark Delta token thing okay and then to do that let's actually have a store Delta token initially it's going to be a string and we'll get that to be sync response. sync updated token okay so this is the first ever token that we got here so now that we have this token we can hit the second end point which is this/ email/ sync updated to actually get back the records as well as the next page token okay and so to do that we have a let updated response right equals to await this do get updated emails so let's write this function to actually hit this second endpoint here okay so this get updated emails will be an Asing function get updated emails and then we're going to take in data token and the page token from uh this object because we can see here in the documentation uh what's this here the query programs require the data token and pit token so we'll just take this in as prms so that we can then uh we can have a prems this is going to be a record from a string to string right so because this can be optional like they might pass in one of these or both of these is possible as well so I'm going to check if Dela token I'm going to set the params to be this but if there's also a page token I'm going to set the params to this right and finally I'm going to check I'm going to just hit the response await x.g I'm going to get a sync updated response from the type here so this type is we're going to get back the next page token and next Delta token alongside with the records of email all the emails and let's actually hit htps uh api. or. email/ SN SL SN SL updated okay and then we can pass in the haters to be this and we can pass in the params to be that cool cool cool cool cool cool yep so we passing the access token here the p and can return response of data so let's continue writing the so we're going to get updated email we're going to pass in the Delta token to be the store Delta token from the initial sync response okay and then we can check if updated response. next Delta token so if there's a next Delta token that means that the Sync has completed Sync has completed and we need to take note of the latest one and this is what I meant by keeping track of the latest Delta token this is what I meant by that so if this has completed we're going to just put the St Delta token to be updated response on next Delta token and then we're going to have we're going to have a list of all emails to be um a list of email message right going be an array of them we're going to take the updated response. records right to be like this so This records is a of email messages and then we're going to keep looping in the next page because remember this API is giving us this messages in batches so we need to keep kind of checking whether this's next page under that isn't so we'll fetch all pages uh if there are more okay so while updated response. next page token I'm going set the updated response to be await this. updated emails passing in the pitch token okay and then I'm going to just concatenate everything together I'm going to do all emails concat concatenate the updated response. records records and I'm going to check again if there's a next Dela token that means this has SN sing has ended I just want to make sure I keep track of the last data token that I've seen in all this request and finally I can do console.log initial sync completed uh we have sync all emails. length email so it's like 50 emails 100 emails Etc and then obviously to store the latest Delta token for future increment incremental things right so yeah so for example for example if you want to con like get the next batch of email that comes in in the future we can just do await uh await get sync updated email a get updated email and pass in the data token to be the last scene data token and this will give us back like the next p the next kind of results right so hope this makes sense and finally I'm just going to return the list of emails to be all emails I'm going to return the data token to be the the latest data token so I once I get back this stuff I'm going to just catch the error right so if it's a exos error I'm just going to console lock it out like this cool okay so now that we have this perform initial sync I'm going to come back to my initial sync endpoint and I'm going to do I get to do cons email accounts. perform initial sync right so now there's this method on it and it returns us with a A list of emails or data token so I'm just going to check for the response so let's come back here blah blah blah blah blah cons response so if there's no response that means there's an error that has been shown I'm just going to return um sorry if there's no response return next respon saying that fil to perform initial sync as a internal server error if not I am going to then do I'm going to get this uh emails I'm going to write it to the database so I'm going to just and destructure the email from response we're going to extract the emails as well as the latest data token right and then I'm going to write this function to actually start syncing my emails to the database so uh before that I can also do await db. account. update right so we need to now keep track of the latest data token we've seen what I'm going to do is I'm going to come down to my schema. Prisma I'm going to search for my account sorry not this is schema model account here I'm going to keep track of my latest data token so I'm going to say next da token so it's a bookmark it can be optional I'm going to run Prisma DP push so this will update my database column it will add this column and with that I can come down here I can update where the ID equals to the account ID all right uh and then where what do I want to update on to update the next datta token to be the Dela to the latest data token that I've got back from my syncing response okay make sense and then at the end we can just return a next response of success equals to true and I can say console. lock sync completed and I'm going to log out the next Delta token uh sorry Delta token just to be just to be itic okay so now let's actually uh let me see okay you know what I'm going to do I am going to just remove the update right and I'm going to just console. lck out the emails for you guys to see okay so let's try this and see if it works so remember the flow is first it's going to go to oringo is going to send us a call back in the call back in the call back oringo we are basically uh inserting a new account and then we're going to trigger an initial sync so then this initial sync so hit this endpoint and then this endpoint should then uh use the the method that we have written to get back a list of emails from their current inbox and they should conso the log it out so let's see if this will work uh but before that let's actually add this initial syn route to the public RS so let's just do /i/ initial sync so that we can actually hit it from our other endpoint okay and hopefully hopefully we should able to see a list of emails okay so let's come back here I'm going to go to my to my slash account I'm going to do Link account I'll try again I'm going to link to this blah blah blah let's actually continue so this going to hit my callback then the Callback will create a new account then after that it's going to hit my initial sync so let's see so you can see blah blah is getting and boom see initial syn triggered success It Go true the syn has completed and this is the next data token that we got right this is what we conso the L out s completed and now we get a list of emails wo so you can see the email has the ID Trad ID created time send at received at blah blah blah has the body which we can pull so that means everything works perfectly we got now a list of emails right I mean the body is huge but at least now we can write these emails to our database okay so let's actually start doing that let's actually create our email table first right if not it's kind of worthless and you know I'm actually going to I will have to kind of paste this in because it's a lot of code but I will walk you through it don't worry okay uh okay so I copy in a few tables I copy in uh one table copy in threats so we have a threat table we got email table we got a email label table we got email address table email attachment and sens and we have some enums to represent sensitivity and the meeting message method okay and there are some errors here because you can see it's linked to the account right but really for the account we need to have uh uh the threads to be a list of threats and we also need a list of email addresses to be a list of email address so I'm want you through each table to show you what is actually for so you not confused by it okay let me see if there any other stuff no I think we're we're doing really good we're doing really good Okay cool so let's start with threats okay so a threat as I say is a collection of of messages so a threat will have the main subject so the subject is probably the latest email in that in that that threat and then the last message date we need that we need the kind of the the participants the list of email addresses that's within that's participating in this threats obviously uh uh the thread is linked to an account right an account can have a list of threats and then there's a Boolean whether is done so that we can like classify them done or not done so we inbox data so is this TR in the inbox so if I show you my code here okay let's take a look you can see I have inbox draft and send section so how I'm classifying that is uh whether it's inbox whether it's true whether it's in draft whether it's true and whether it was in sent which is a Boolean as well and yeah so this is how we classify the threats and obviously a threat has a bunch of emails and we're just putting the index so that we can faster index then because we have a lot of threats and make sure you have these indexes because if you don't have these indexes your query speeds are going to be absolutely like horrendously slow okay and then an email is pretty self-explanatory so these are what we have seen in the types which I've explained and I'm just putting in the database right we have a list of who is it from so each individual person is link to an email address so u a from has a list of is from one email and it could be to a list of email address it could be cing many emails it could be Bing many emails and it could be in replying to many emails so that's why you have this email addresses and then it will have a list of attachments right and an email label which says whether it's in inbox send. draft right and the email address obviously is like pre- self explementary it's just a pairing of the name and address right and obviously it's linked to account so the reason why we need this account ID linking is because if you go down here and let's on the compos an email and you see all this emails that I'm seeing here this is the list of email addresses that has been linked to this account so each individual role here is one email address rle in our database yep so that's why we get back this and then emot attachment we're actually not using this you could use it but I'm just putting it here for completeness sake cool so now that we have this let's actually create the table so bu Prisma DB push okay so it creates a new columns okay cool awesome we are doing on track so now we need to actually write the function to get the emails and sync it to the database so I'm going to create a new file uh new thing called sync to db. TS so this is just uh a file where we're going to write all database stuff to write the emails to our database so it's going to be a huge file a lot of code but once we're done with this is like the bo of the project right okay let's actually start coding so we're going to export export a asnc function called sync emails to database right so we're going to be calling this function in the initial Sync here right we're going to sync a list of emails and so we have to import the email address email address from our types our custom types right which is this oh sorry not email address it should be a list of email uh email wait what do I call it let's go to types uh email message my bad yeah so it's a list of email mess message and can now see the types are lined up right and then now that we have this email message what I'm going to do is I'm going to console. lock uh attempting to sing syn email to database I'm going to just log out how many I'm actually sinking okay and then what we're going to do is we will import another function a library called p- limit so because we're going to try to like okay imagine we have like 100 emails right we're going to try to do promise to all emails right emails. map email process uh save email right you can imagine like we are basically sending 100 concurrent database connections at once to our database and if there's more than 100 emails like it could be more we're going to basically cook our database if we don't have some kind of rate limiting function and that's where P limit comes in so p limit stands for promise limit so we will just help us send this uh kind of concurrent request in batches to our database uh sorry concurrent batches in promises so that we can better handle like concurrency and even crash our database and crash our server so I import P limit from P limit and then what I'm going to do is I'm going to define the limit to be 10 so I'm going to send promises 10 batches at a time right so even if there's 100 emails here right it's only only going to have 10 batches at batches of 10 at a time okay and so what I'm going to do is I am going to just wrap the whole thing in a tri catch block right and then if there's any error we just console.log oops with the error but we can just kind of ignore this for now so let's actually write the function to save the email okay so obviously obviously obviously uh apart from the emails we also need the account ID right cuz we are we need to link the email the emails and the threats to a specific account so we also need the account ID to be passed in so let's go back to our initial sync and let's pass in the account ID as well because we have that anyways so let's actually write a function here called upsert email passing in the uh email and the account ID so let's have a Asing function upsert email this is going to take in the email and the account ID and you know what I also want to see the index of this happening so I'm going to take the second program of map and I'll pass it into the index here so I can get the index so this is just so that I can say cond console.log upserting email index so if I have 100 I get to see like upserting email one upserting email 2 blah blah blah all the way to 100 right just for like some sanity checking cool so here let's actually write the logic of of kind of determining the label type so let's wrap everything in a big Tri catch uh then we're going to say let email label type so when you label the email so it could either be in in inbox or it could be in sense or it could be in draft so by default let's put it in inbox right so we're going to check if the email. system labels do includes inbox or the email do system labels includes uh important important what I'm going to do is I'm going to set the email inbox type to inbox right then I'm going to copy this here then we're going to write the else if if it includes send the email label type is send if it's in the draft we put in draft right and then we going to do is uh we want to then get the list of email addresses like individual addresses to be written into our database right so we'll just get back like so we're going to get uh cons addresses to upsert so remember upsert just means update or insert so if the email already exists in our database we're going to update it if that isn't a current row we're going to insert a new row okay so it's going to be a new map so like I'm just trying to like filter it out so that imagine you have like a lot of you got a list of emails I want it to be unique so that when I insert the database it is like purely unique and it doesn't cause any issues okay so just follow along with me so I'm going to do for cons address off it's a big array of email from so this email. from is going to be a email address view right and this email address view is going to look like it has a name and has address right this is the shape of the email address right and then we need to also spread out the email. to right cuz that it might be to a lot of people we also spread out email. CC and BCC as well as email. reply to okay so we're just trying to get back a list of email addresses and then finally we're going to set we're going to map it from the address to the actual address here so this is going to be a mapping from strings to an object so you see this object is email address which is this field and this address. address is a string so I hope this makes sense and then we'll have um we're going to do four cons cons we want to upsert uh an address right so let's actually write a function to upsert an address so we have Asing function upsert email address so it's going to take in an address which is an email address and then we have the account ID which is a string so we're going to then link it together so we just put a TR catch block so if there's a error just do it like console block fill to upsert email address we return now right but if if not we're just going to just copy this in so I'm going to import database right so we're going to first see if there's existing address by looking at the compound key account ID and email address right we're going to see address um what's this email address wait a minute oh right okay you see this email address type it shouldn't be from clerk this email address should be from our our types okay so remember to import it correctly and then we will then check if it has already exist if there's a row we're going to update it if not we're going to create a new email address VI okay and then we'll use this function here to actually create the email address blah do set and then we're going to do cons uh sorry let's do H for cons address of addresses to upsert the values so we get back the list like the filtered list of items here and we're going to do cons upsert address equals to await upsert email address passing in the actual address and the account ID right and then this will return us with the address right we want to save we want to save the list of database transactions that we got because we're going to use it later on so what I'm going to do is have a list a new list called cons um address mapping sorry address map it's going to be another new map but this map uh sorry not this map it's going to be a list right so uh I'm going to do is Con uper the addresses it's going to be a list and what's the type of the list the type of the list is going to be awaited so it's like typescript stuff so awaited return type we're going to take the return type of the type of upsert email address so let me explain what is happening here so upsert email address is this function that we have written so you see this email address upser email address we get back we're trying to basically insert this into the list right so we can see that it returns a promise of this shape so we're taking the type of the return type of this function and we're going to await it so this is for typescript to like unwrap the promise and so we can now see that this has the exact same type as here so then we can finally do upsert the address address. push into this array cool and then from this we're going to get the actual cons from address because we need to know who is this email from this is important to us so we need to do address map. getet uh sorry uh we need address map yeah address map equals to the new map right and this is going to be from upser address uh dot kind of filter do filter Boolean so we you filter out all the stuff that is not that is like knowledge in value and let's map the address to the address do address we know that it will always exist because of this Boolean right and finally we can get the from address equals to address map. getet email. from. address so I know it's like super complicated but honestly it's just code that needs to be written to kind of insert stuff and fits data into to like from our typescript schema into our database schema so there's not much like really interesting code you can just copy it from the from the geub repository and like in fact that's actually what I'm going to do I'm going to copy in some code uh cuz all you have to do all you really have to know is that this function is going to take the the response from oringo and it's going to insert it into our database into our tables so like nothing much that needs to be done I'm going to just quickly just I'm just going to quickly uh how say copy this in and then we will be good to go okay so let's go to the next part so we'll get the next will check here form address so if there's no form address we're going to just push an error so we again get the two the CC's the B bcc's and the reply two right and then we're going to then insert the threads okay so let's actually insert the threads boom boom boom nothing interesting here so we're just going to do await db. threat. upsert so we going to find the email. threat ID we'll update the information right with the participants and then we'll create it if there isn't a thread that already exists and then find like lastly we need to upsert the email we got this giant piece of code here that's needed um so after we upsert the threat we need to upsert the email like really I'm just like putting stuff like you see subject equals to email. subject like this is boring kind of data mapping stuff the same for the create you just got to do this you know so I already got the boring stuff done for you guys you just have to copy it so then after this we're going to get back to threat emails right then we're going to classify the threat based on the emails inside so if any of the emails is in inbox we want we know that the threat the whole threat is Inbox but if the threat only contains emails that in the sense field we know that the threat is also incent right and then finally after updating everything we can then upsert the attachments right and let's actually get the function to upser the attachment here so it just takes the email attachment where we can import it from our types and we're just upserting it into our database yeah so that's the whole file like I this so use very big but 239 lines right but now the magic Park is we can just call this function pass in the emails from oringo pass in the account ID and this should then start writing into our database and let's actually see in action okay so I'm going to just I'm going to just uncomment this I'm going to update the account I'm going to no longer comment out console the emails right but I'm going to see this in action so if I come back here to slash hopefully if I go through the flow I should now see like data in my database I'm going to just restart my server just in case something needs restarting I'm going to go connect this funds go to orle so this going to go to our web hook then it's going to call Initial sync DB so this will immediately put us into mail but the thing is it's going to start syncing my emails in the background which is like super cool so now if I go back here you can start seeing things happening so uh blah blah by the way there's a there's an error that I noted just now which is when you're upserting email address if there's an existing email address you should just return the the found email address you shouldn't try to update it okay because we have like some rise conditions happening so let's try again so if you go to slash and now let's actually Link account let's link this let's go to the pro process so remember it's going to hit our web hook the web Hook is going to get all the messages going to hit our initial sync to DB and now hopefully we see in our console things should be happening blah blah okay one more issue I found with is that it's actually not very kind of good to do a promise to all and upserting the emails like at batches at the same time because we're going to face a lot of race conditions for example when you're like even if you're like sending in like five emails at once at a as a batch right the batch may have like a lot of different addresses that's being sent in right like from address to address and so it's going to cause a lot of race Condition it's going to break like the Prisma at functionality so what I'm going to do is I'm going to remove the promise all in fact I'm just going to just await uh await and insert them sequentially okay so now let's actually go through the process again so I'll go back to slash I'm going press Link account I'll connect this email address Advance go to orle let's continue right and hopefully will bring us back to/ mail and it's going to sequentially kind of read through our email address and update them so hopefully I should able to start seeing stuff happening so you can see updating upserting email address uh yeah in this case you can see it's like zero but it's fine so in this case you can see that syn has completed and we got triggered so I think this we can also get the index right um you know it's okay it's okay let's just leave it like that but now that we can see we upseted how many addresses we can we upser 21 email so go back to Prisma now if you see we can see we have 21 emails in our database we have 21 sorry 11 email addresses which are literally just like the names and the the addresses and I can see the list of emails that has been sent from here right and then we got the threads so the threats will contain the account and the list of emails that actually is in the threat cool stuff so we have successfully done it we successfully kind of uh read in the orle API to get the emails and sync up our emails from Gmail into our own database and we have done the database engineering and now we can actually do some cool stuff and like make some U eyes but before that we actually need to do one last step which is to configure the web hooks so that we can continuously monitor for changes in the Gmail inbox and whereever there's a change in the Gmail inbox oringo should be able to send us a web hook in order for us to continue keep our to keep our database in sync so let's do that in the next section Okay cool so now that we have kind of our database setup we have our threats we have our emails let's actually start doing some fun stuff and actually display it on the UI so let's actually start building the UI first okay so we're going to be basically building this kind of inbox form right and so okay let's begin by by going back to our vs code okay and let's go down to let's just clear out everything first let's go back to our mail page so instead of exporting a mail dashboard I'm going to just create a new component component called um mail um m. TSX okay so inside this mail. TSX I'm going to just export uh just do RFC I'm going to export a function called mail right and so going to do is I want to basically build this kind of a sliding layout so you see this resizable panel so um what we're going to do is going to build that resizer p panel so we're going to import it from shn itself so we going to import from at/ components slui resizable and let's actually get from resizable handle resizable panel and resizable panel group okay so the whole thing is it's going to be wrapped in a kind of tool tip provider first uh so we need the two tip provider because uh down the components we need this provider to be able to use the to tip but other than that right other than that we can actually put the resizable panel group here okay and we'll have the direction of horizontal okay and then we have a on layout right so we're going to just get the sizes from here boom like this so every time the layout changes like we drag it around right we're going to get a callback to get the sizes so I'm going to just console lck out the sizes for now and then within here I'm going to give you a class name of items that's stretch hit full and mean height of screen cool and then inside here we'll have our first resizable panel right we have a default size default size of let's say set a variable up here so let's actually let's get some props type props equals to default uh default layout right it's going to be a number array or it could be undefined so let's actually export this out and so by default if they didn't give us any uh default layout I'm just going to have 20 32 and 48 so what this means is the first the on the left side is going to be 20% of the screen the second layout is going to be 32% on the screen and last is going to be 48% so by default I'm going to take the first item here and then uh I'm going to have a collapse size so we also need kind of whenever the thing has been collapsed we also need uh because this is on the left side right these are the nav bar so we have a nav collapsed size which is a number right so by default let's actually let's actually require it to have the number so kind of we'll just do collapse size to be nav collapsed size um and then we have to make it collapsible right so you can actually drag it all the way to actually collapse it like this cool and then let us let us do Min size equal to 15% and Max size let's do 40% okay uh on resize we also need to kind of do some logic here but we'll do that later on and then lastly we have a class name right so let's just have import the CN module for now we just have a mean width of 50 pixels right transition all duration of 300 is in out so this is for the animation when we kind of collapse it okay and then within this resizable panel div with the class name of flex Flex call height of full Flex of one right and then within this div let's have another div so this div is for the account suure here right so let's have a account sorry account suture right so for this we'll have a flex height of 52 pixels right and then we'll have items of Center justify center right and I just leave it here for now and actually we also need to track whether the the sidebar has been collapsed so let's also have an another kind of variable called is collapse right so by default by default is going to be a false right we can also pass in another prop here called default collapse which is a Boolean and we can pass this in like this right cool and so actually with that uh set collapse we can actually also do some stuff here so let's see let's see collapsible true right and then we have on collapse on collapse we're going to just set it collapse to True okay and then on resize we're going to set on collapse set is collapse to false okay and then here with the class name here we can also track some stuff for example if it's collapse uh actually you know what this is only applicable this whole class name is actually only applicable if the whole thing is collapse so we can just do something like this so yeah only if it's a collapse then we'll apply this classes right and then inside here we have this Flex right so let's also make this a class name component here and then we'll have a if is collapse right we're going to have a height of 52 pixels right if not we're going to have a padding X of two cool and this is where we're going to put our account switcher so for now I'll just put account switcher as a placeholder so let's actually go back to logo 3000 let's see how it's going so let's actually import and use this component inside our mail page so let's just return mail like this right and then we need to pass in some default props right so let's actually pass in um default l out it's like this default collapse equal to false and when it's collapse let's put it to four okay and you still only work in client component so let us make this a use client component and boom now we have this right we still can't see the the the resizable panel just because we have not finished kind of the component Okay cool so now that we have that let's actually continue coding out so under the account switcher div right outside of this div we're going to have a separator coming from our component and then we have a this is where we're going to put our sidebar so let's just put a mental note here and then we have a flex one to actually expand all the way out and this is where we'll put our AI so ask AI so this is the part where we'll put this thing here this ask AI part here right so we have the account Swit we got the Naf sidebar and then we got the this huge spacing here and then we have the AI section cool and then let's actually go back so let's see because right now we have no other stuff right that's why it's expanding all the way cool and then let's see what I'm going to do here is move down here let's actually continue coding out the other resizable panel so under this this let's have a resizable resizable handle like this with hand so if we go back here we can now see okay it still expanded all the way oh but now we have this here right so actually let's see the error no yeah it's actually fine we just need another resizable panel right resizable panel okay and this resal panel we have the default size of layout default size of the default layout index one the minan size will be 30 right and then we're going to have a bunch of tabs so you're going to see here right we're going to have this tabs here we have the Inbox and we have got the inbox Tab and the done tab okay so for now I'm just going to just yeah wrap this into this tabs okay and then we have a default value of default value of inbox okay and then inside here we're going to just have a div this div will have a class name of flex items D Center padding X of four padding y of two okay and then we have a H1 that will have that say inbox so we have a class name of text XL font dashboard so now we can see now it's starting to look correct right we got the inbox here cool and then I'm going to do here is after the inbox I'm going to have the tab list I'm going to import it from components right we have a class name of margin oh class name of margin L of Auto and then we have a bunch of T trigger inside here we have a value of inbox and the class name of text zinc 600 and when it's dark mod I'm going have text zinc of 200 okay inside this T trigger I'll have it inbox so now this looks more like it right so D value of inbox cool TS list TS trigger Y and then we'll have another TS trigger down here that's exactly the same as this except that the value is going to be done and the value here is done cool so now you can see this things are starting to happen okay awesome awesome awesome so we have the value here blah blah TS list inside the TS trigger and we importing everything from okay so the tabs trigger has to be from at/ component okay so now it looks right I'm going switch in between like this cool and now let's exit the div right let's exit the div um so outside the so above the tabs let's have a separator okay and then this is where we're going to put our search bar bar so let's just put search bar for now so this is where we're going to put our search bar um actually no this uh actually why is it outside no actually it's correct yeah you see outside the separator below the separator is our search bar right and then below the search bar we're going to actually put the content the text content which is this section here okay so let's go back here tabs content we have the value of inbox and then we have another Tex content for the done page so we're going to say here we're going to be inbox done so now you can see here I can switch between I get to see my search bar and my done and my inbox cool so now let's build out kind of the final panel here on the right side okay so below the resizable panel right we have another handle and then the last resizable panel right right we didn't know the default size is going to be the second index the main size of 30 and let's actually put the thread display so this is where we'll display the threads right here cool so we have buil out the skeleton right and now we're going to go in step by step starting from the account switcher all the way to the sidebar and we're going to start displaying the threads cool good job guys let's continue cool so now that we have this done let's actually create the left side first so let's actually work on the con switcher but before that I want to kind of talk you through the data fatting strategy that we're going to be using which is through trpc so in at the beginning of the video I've mentioned that trpc is an alternative to the rest protocol meaning that that we will have end to endend type safety when we are fetching data from the front end to the back end and trpc is built on react query which means that it will give us the power of uh kind of refresing caching and everything out of the box right so when we did create T3 app in the beginning to breed out this project it has given us the boiler plate code for the trpc uh functions so now what we have to do is actually use them so I'm going to give you an example so let's go into trpc um so this is the rout Handler for trpc by the way so if you go into Epi router. TS right this is the post router meaning that you are able to hit hello so this is kind of a root imagine this is like a rest end point this a rest endpoint so we're going to say that this hello is a public endpoint right and then this public endpoint will take in uh an input of an object and the object has this shape of text which is a string right and then we're going to query this and we can able to access the input and return a type save input of input. text right make sense so we're going to be using this kind of methodology to start fetching data so the first thing we're going to do is under our routers folder let us create a account. TS uh yeah so for this account. TS I'm going to export cons account router equals to create trpc router right so when we do create create TR sorry when we do create trpc router we're just basically grp grouping end points together into like a certain uh router that's even how it works in Express right so I'm going to just get my kind of rep reference for getting the accounts okay so the first thing we're going to do is uh we're going to do get accounts so this is going to enable us to kind of just uh get all the accounts back for a given user right so in order to that we need to kind of protect this route so to protect this route we're going to go into our trpc dots so this under server API trpcs right you can ignore most of this stuff but come down here to the bottom right because we're going to create a protected middleware okay so we're going to just create a function here called is off right this is going to be a t do middleware so this middleware is going to take in a few it's going to we can destructure the next as well as a context right and then this we're going to check if not CTX if not context dot off but you see we have no authentication kind of thing here and so where does this come from so this all comes from if you scroll up here right we can see we are going to create a trpc context so the context is being created through this function so this context is what sits in the middle of our request and our route Handler okay so in order to get the authentication shape what we're going to do is let's actually see this where is this okay cool I see it here so we can just get cons user equals a weight off so this off is a function that we have been using from clerk uh from Clerk and then what I'm going to do is other than the database I'm going to also return the off to be the user object right and now that I Expos it in our context if you scroll all the way back down here and I can do command control space I now see our off uh data in the context so I'm going to do if CTX do if not CTX off. user ID that means they're not authenticated I'm going to throw a new error saying that unauthorized if not I'm going to just call the next function because it's a middleware right so I'm going to call the the function after the middleware right I'm going to pass in the context to be uh whatever context I have from here but because I know the user is 100% going to be here I'm going to pass this off to be CTX do right as required type of CTX right so what this does is saying that because we have already checked that they are authenticated when we return we are saying that we telling typescript that whatever value of O is they're going to definitely be like there not going to be n or undefined so we're going to do this required so this is from typescript so now that we have this is all function we can export a other than the public endpoint we can do a private procedure so procedure is just a Endo so you can Define t. procedure we're going to use the EO middleware so this Endo that uses the isos middleware so whatever procedure that hits here we're going to make sure that they're authenticated already okay so now if you go back to kind of account. TS under the routers right we can do um get account equal to protected procedure procedure right and we can import this from okay private procedure yeah we call it private procedure right and then we can do do query because we're actually querying this endpoint Let's do an aing let's actually unwrap the context in here all right we're going to return await cx. DB so the database the Prisma database lives in the context variable as well account. find many where the user ID is equals to the context. off. user ID so you can see we're matching it here based on the off that we pass into context and quing the list of accounts in our database right and for this I'm just going to select I only want the ID field I want the email address field and I want the name field okay so now how do we actually query this endpoint right let's go into let's actually go back to our mail page so let's actually work on the account switch right so for this account Swit it's going to be a select right and then we're going to put in the list of accounts and display in a drop down cool so if you go into our search account switcher right we have this account switcher let's actually create create a new component uh let's create a new component right here called account that's switcher dots X cool and with the account switcher what I'm going to do is I'll just have a uh R fce sorry TS RFC account switcher right so for the props I need to know whether the sidebar is being collapsed because if it's collapsed I need to just kind of hide it away make sense so I need to have that access to the props is collapsed and then I'm going to do is I'm going to get back list of data from API so you see API there's two apis one from react and one from server so I'm going to get the one from trpc react because we're calling this from the client side so to make this we have to also put use client on top here so if I do API dot we can see a list of stuff but I cannot see my kind of uh account router so to add account router I'm going to have to go to roots. TS under my server API right I'm going to go to account I'm going to just put account account router in here so just ignore everything out here the only thing you need to know is whenever you create a new router right you need to add it into the the basically app router so that when you save this you can now have access to the account here and if I do space I can see the get accounts function that we have written here so this is the private proced procedure do get accounts and going do do use Query so this is the react query part of this and from this I can get back data variable and you can see this data is the exact data that's being returned from the function that we have written here get account so if press GD right which s for go to definition right I get to see uh I get to directly jump into my back end right so Isn't that cool cool so now that we have this data I can actually just just I'm just to display for now I'm going to just do um data map account right I'm going to just return return a diff that has the account. email right and we have also a key and then I'm going import this account switcher here passing in the is props and so now if you go back here we can now see we got a list of emails but they are sight to S side but we actually want them in a drop down okay so to put them in a drop down I'm going to come down here so the first step is we're just going to first check right um if we don't have data that means this component is still loading because this data will not be initially available right because we're still fetching it by the moment it fetches it's going to come back with the list of accounts so we're going to check if there's no data we're just going to return now first to kind of not render anything and then instead of rendering this we're going to render a select right and then within the select we have we're going to have the account ID so let's have account ID right and how I'm going to set this account ID is I'm going to instead of using react. state right which is uh temporal temporal meaning that once I refresh the page the state is gone right I'm going to store this state in local storage so there's a very handy hook here that we can use so if we just do sudo but install um let me see use hooks DTS so this use hooks DTS Library gives us a lot of nice utility hooks for ex for example one of the hooks that it gives us is use local storage so this use local storage is a direct replacement of use state meaning that I can store I can store the key here as which logo storage key I want to store it as and then I can give you an initial value of of stuff so now whenever I change the state here it's going to be stored in my logo storage so whenever I refresh the page or I close the tab and I come back the local storage value is going to always be persistent in that tab so the reason why I have this account ID is because if I change it to let's say another account and I refresh the page it's going to remain at that account even though after I switched it because everything is St in local storage and so it is just persistent in the browser okay so for the value we'll just have a default default value of account ID account ID and then on value change we're going to set the account ID so this is the exact same format of of react use state so if you understand use State you can literally just replace use state with use local storage and you immediately get the benefit of persistency of the Box Okay cool so now in here let's actually have a select uh trigger select trigger from components and then we have a class name of of CN right we're going to check um some actually just copy this because it's a long class name like this okay so just copy this from the giab repository we're just doing some styling with the internal span and svgs right and then down here let's actually have a select value in here and the placeholder is Select placeholder select an account okay and within here we're going to have a span and the class name is going to be right if we're going to basically hide this is hidden soorry hidden if it's not is collapsed so we are saying that if it's not collapsed we're going to hide this and then we'll have another special value for when it's not hidden okay so then we're going to do account sorry data find so this data is basically the list of accounts find account account. ID equals to account ID and we're going to get the email address right and we're going to get the first character of the email address so this is what happens you can see when I Collapse it I can see this o which is the first letter of this uh this email address makees sense cool so below this span we have another spin but this is the opposite right we're going to have a class name of it's going to be hidden if it's collapse so you can see it's the opposite of this where it's hidden when it's not collapsed here it's hidden when it's collapsed right and let's also give you a margin level of two okay true and then we'll have the same but this time we show the actual email address instead of just the first letter then outside of the select a kind of trigger we'll have a select content from components okay and inside the select content let's actually do data. map and then uh for each account we want to return a select item from component s select let's give you a key let's actually make this a curly brace sorry a normal brace key goes to account ID uh what's this data map all right we need to wrap this in this so for each account we give you a value of account uh do ID and then within here uh we can just have a account. email address cool and so if I save this and we come back here we can now see I got a lot of accounts right because because I pressed I try to link it up a lot of times right so uh I'm going to just clear out in a bit uh actually I can clear out now so I'm going to go in here so let's look at the account so this is account 68589 meaning that I need to clear out anything that's not 68589 so let's just clear out everything previous here let's delete the records cool so now if you refresh I can see there's only one account and I can select it and if I refresh it you can see it's persisted local storage right cool and so let's continue so other than here I also want the PE I want people to be able to press add an account here right so let's actually add that so uh just above the select content we have a div right and then we have a plus icon here so we have the plus we actually have to get it from so install Lucid D react so it's just a icon library right so let's actually import plus from Lucid react cool so this plus icon will have a class name of size of four margin rate of one and here we can just below it we'll say add account right so you can see now we have this add account by look super ugly so we're just going to have for this diff let's have a class name of flex relative hover BG gray 50 with a full cursor pointer item Center rounded s and then padding y of 1.5 padding left of two padding right of eight text SM outlined non Focus BG accent right pardon me it's a super long one and I think it's fine yeah so I'm going to just save this if I come back here now this looks the same as the both cool so what happens when we want to press add account we're going to basically use the same function from the link Account button right so what I'm going to do is I'm going to do unclick right let's have this function so remember we have this uh we have this initial get orle off URL let's try that as well so we have this uh link Account button right we can actually just copy whatever is in here and just put it in here cool and let's make this Asing and let's import this so now when they press add account it's going to lead them to that Google authorization page all right awesome so now we have this uh kind of Account button uh done and we successfully saved it to account ID and the beauty of this is we can access this account ID from anywhere in the app and let me show you why I mean so let's actually just create the sidebar component first so let's go down to uh uh here let's actually create sidebar. TSX use client tsfc cool sidebar right so let's actually import this sidebar so let's go back to our m page whever this sidebar is let's actually replace it with the actual sidebar from our component right so we got a sidebar here and let me show you we can actually access the account ID from use local storage and as long as we're using the same key which is called account ID if we get to use the same key I can actually access account ID from this component as well see 68589 and if I change change it here it's going to automatically react here so it's like having a global kind of atomic State Management just through this uh through this to the local storage yeah so let's actually work on that sidebar first right so for the sidebar I also need to know whether it's collapsed right to actually conditionally render it collapsed okay and then what I'm going to do is let us have a nav so we need this nav component so to create that let's go down here nav TSX so this na. TSX um let's see let's see let's see I'm going to copy in here so just copy it from kind of the just copy it from the the the repository but basically what what we're doing is we getting whether is collapsed and then we're just looping through a list of links right so the list of links is like inbox draft and sense right and then for each link we're just having a tool tip right and then we're going to show the link the icon so the icon is what is being shown beside here right and then yeah that's pretty much it so let's save this na. CSX right and then return here so we'll just export this nav component and then in here we'll pass in the is collapse variable and for the links let's have three things so the first thing is a inbox right the label let's just put one example the icon will be the inbox icon from Lucy react and the variance let's put as default so now we should be able to see the first item here and this is the label so by by right we should be able to have access to kind of the number of the threads within this mailbox but let's do that later let's do the same for draft so we have title is draft right and then the the label is also let's say put for for for placeholder the icon will be file from Lucy react the variance is put as ghost and and then lastly we have send right let's just move this and then we'll just put a send icon from Luccia react and then we have the label of six cool so now we got the three things on the side here amazing work guys and now that we are here let's actually also kind of um set the tab right so we're going to have another local storage variable called uh tab to be lose local storage we're going to put it as normal human Dash tab so you can name it whatever you want as long as it's the same name across all your components right because this is the key of the local storage right because underneath the hood what it's doing is doing local storage. set item and putting in normal human. Tab and changing this value every time the state changes okay and we can give you a default value of inbox so depending on the tab we have inbox draft and set right cool so this will be inbox draft or set and so we get a type safety here inbox right and then because we have this nav this this value now we can actually uh conditionally render the the variant so if tab equals to inbox we're going to put as default and for this if the tab equals to dve we can put at default and the same here okay and so now we have this local storage and T there's one more caveat that needs to be to be careful when you're using this uh local storage which is that we need to actually dynamically import this right so if you go down to um nav TSX sorry we go back to mail. TSX right we can see this component is being used here right sorry there component is used in the m page we actually need to dynamically import this so cons Mill equals to Dynamic from next so we're going to do is return import do/ Mill and we're going to set Sur side rendering to Bea so because by default all the components even if you mark it explicitly as uh as used client nextjs will still serers side render it and try to hydrate it and stuff like that so to make sure server site really don't touch this component we're just going to make this purely client site by turning the server site rendering to be false okay so now that we return to the page right we can see that our kind of tabs work again right and is being persisted across render so I can go to draft and if I refresh wait sorry if I go to draft and I refresh the page we can see that it's still UND draft right make sense cool and so now let's actually continue on coding soah so one more thing is go back to your kind of mail page so you need to add your kind of e collapse pass it into the sidebar component okay so now let's actually kind of build out let's actually make this data real because we do have the data right so depends on how many tracks we have per box right so let's actually go into our let's actually create a new uh let's actually go into account our account router so account router right so we have this get account right but let's also have another function called get num threats right so this get num threats will we pass in whether we're looking for inbox whether we're looking for draft or whether we're looking for sent and you will return us with how many threats are in that inbox okay so let's actually do that uh ma let's see get threats Okay cool so get num threats so as always this is going to be a protect a private procedure right we're going to that we're going to have a input right so the input we're going to use zot so Z is like this a schema validation Library where we can Define the input to be an object z. object we're going to need the account ID being passed in right cuz we need to know which account you're looking at right now we also need the tab right so the tab can be a string but obviously it should be inbox send or draft and then we're going to do the query we're going to get the async we going to take out the input and this context in here so we can just this query is going to pass in the context as well as the input and you can see this input is has the same type as the the zot object that we have passed in so that's kind of the magic of trpc and typescript okay and then here we're going to first authorize the account access because remember CTX do off. user ID gives us back the user ID but what if the user ID is trying to access information on account that doesn't belong to him right that'll be that'll be critical so let's have some kind of authorization process so let's go to the top here and let's export a cons authorized account access right this will take in the account ID that we are trying to access and the user ID who's trying to access this right so the first thing we're going to do is we're going to get account to be await DB do db. account. find uh first where idid equals to account ID and the user ID equals to the user ID and then we're just going to select the ID to be true we're going to just take out email address as well we need the name and we need the access token cool and then we're going to check if there's no account meaning that there is no such valid user ID account pair that means that this user is trying to access an account which he doesn't own so therefore we're going to just show an unauthorize error right we're going to just say account not found and then if not we just return the account so now we have this authorized account access we can call it before here so let's do U cons account equals to await authorize account access pass in the input account ID so this input account ID comes from this input here and then the user ID comes from CTX off. user ID so if this passes we will get back a valid account from our database right and now we're going to check right so uh we're just going to try to filter the inbox so what we really want is to return await CTX db. threat. count cuz we're trying to count how many threats there are in the inbox or the draft or the scent right so we're going to do the count and we're going to pass in where right and so how how we determine what is so account ID will always be the account ID that we're filtering by and then we're going to check uh inbox status equals to either true or false true or false right so if we're looking for the T the inbox tab we're going to filter out by uh whether the inbox stus is true okay and then we'll do the same for draft and for set okay that's cool that's cool and then and then let's see let's see if this will work yeah actually we can extract this out as well right because we can just have a cons uh we can have a filter right this filter will come from Prisma dot uh this is the the type of this is thread where input right and this going to be initially empty so if input. tab equals to inbox right we're going to set the future. inbox status to be true if not going to set draft status not going to set St status and then here we just put we're going to spread out the filter right so this is going to conditionally filter out the inbox draw status and send status based on the input that we're going to give it in so now let's actually use this by going back to the account switcher okay and so what we're going to do is let us get kind of um the list of we're going to get the number right so let me see let me see account sorry not account sidebar right cuz this is where we are kind of calling this function so for the first thing we're going to do is we're going to get data and we're going to rename it to inbox TRS equals to API so API imported from at TF PC react API do uh account. getet num threats and going to you query it by using doing get use Query and then this query I can get back account ID to be the account ID up here and then I need a tab right the tab can be inbox right so this inbox threads will give me the number of current early thre current threads in the inbox tab okay and then we we'll do the same we'll do the same but for draft and let's rename this to draft threads and we'll do the same for send so let's rename this cool and now that we have that can replace it with inbox threats draft threats and send threads so with this we can see that the data comes from the the use Query hook so if we kind of come back here we can now see that this is accurate information right so inbox has 13 trats in there my send only has four and my draft has zero which is accurate awesome so now that we have built out the kind of left hand s right now let's actually move on to building uh the the list of threats okay all right so now let's move on to actually displaying the list of threads so we're going to do this middle section here the one that's kind of under below the search bar right here this stuff here okay and so what we're going to do is let us come back to the code so the first we're going to write the function to actually uh get the list of threats by the tabs right so we're going to go back to our account. TS router and it's going to be a very similar filtering output from here right so let's actually come down here and let's do get threads right it's going to be a private procedure where the input is going to obviously we're going to have the account ID right so which account are you looking at we're going to have the a tab the c. string right okay and then we're going to have the done filter whether we have completed right so it's going to be a bullan whether it's true or false right so we have two things to filter we have this filter by the tabs and we also have filter by the Done Right therefore we need this two kind of filter variables here okay and let's do a query let's actually get back a this the context and the input cool and now that we have the context and the input right so the context always contains the user ID and the database and the input contains the input as passed on into this function so the first thing is let's actually authorize the account access using the function we've written here and let's have the filter which is also very similar right we can actually copy it directly here that filter okay and then what we're going to do is we're going to do filter DOD equals to uh equals to this equals if input equals to input done yeah so we're going to do an additional future on the done column right whether it is equals to the done field that's pass into the input okay and then finally we can then uh get return await uh CTX db. threat so we're going to quy the threat threats. find manyu all right so where would be the filter right right cuz the filter is is exactly the the for the threat where input and then we're going to include the emails cuz each threat can have emails right so we're going to do an internal joint right within Prisma we're going to order the the emails by the send at field ascending order okay and then we're going to select only a fi field few fields from the emails each email we need a the the email from the body right we need the body snippet right we also need the email label we need the subject uh subject we need the system labels and we need the ID and we need to know when it was send that okay cool and then we're going to only take the latest 15 threats right wait actually here yeah we're going to take the latest 15 threats and then we're going to order the threats by the last message date in the descending order okay and now that we have this we return it directly okay so now let's actually uh use the data written by this get threats so what I like to do is to actually put because we need we're going to be using this threats data a lot in our kind of app I'm going to create a custom hook right to get to actually allow us to access the list of threats that we are looking at so let's go into to hooks let's create a new hook called use- threats. TS right like this so use TR is going to basically call the function that we just wrote The backend JPC procedure and it's going to save it in a variable cool so for use strats let's actually just do our fce use strats and then so we're going to need the kind of we could just fetch all the data that we whatever need in our app so the first thing is let's actually just fetch the accounts right cuz we need this somewhere else so we'll do API from react so make sure that this very common mistake make sure you're importing API from react instead of server if not it's not going to work okay so import api. account. getet accounts. usequery right and so we rename it to accounts and then we're going to have the account ID right which is going to be use local storage and the key is account ID initial value is empty screen and then we need a tab right is use local storage normal human tab so this is one we Define in the sidebar right so this is the tab and this is the done right and then um we're also have a done field which is going to use local storage and we'll have a normal human done so this is done and this is tab as I mentioned just now okay and finally let us get a uh we need the query key right yeah yeah so no actually let's just do this first so I'm going to get the data I'm going to rename it to threats and this is from the api. account. getet threats function that we just wrote so what do we need to do use Query we need to pass in a few input right we need the account ID we need a tab and we need to know whether it's done okay and then I'm going to only enable enable this this function to be called when I know that there's account ID and I know that there's a tab right because if there's no account ID or there's no tab it doesn't make sense for me to call this endpoint right okay and then I'm going to give you a placeholder data right and I'm just going to return the same data here so this is to basically maintain the the old query data whenever we have a let's say we change tabs right the placeholder data is going to allow the data to be rendered statically without kind of flashing in and out of the screen and let's allow it to refret every kind of 5,000 milliseconds every 5 seconds it's going to automatically refresh in the background okay and lastly I can just return the the list of threads that I have and I can also get the is fetching variable cuz I need to know whether This Thread is f the function is fetching so when it is fetching I can get a bullant value out of it right and then let's actually so let's also export the is fetching variable right and then we can have a refres we can pull out refret from here as well so I can get this function out so that anywhere in the app we can just call this refresh and you will automatically refresh this data and let us also just export out the account ID and let's get account which is the accounts. fine so this is the list of accounts we're going to find the one has active ID okay so we export everything out here so we can find work on our list of threads right so if you go to the mail page right we can see that this inbox thing here is the we want it to be a list of threads okay so let me just come down here inside this mail let's create a threat that's list. TSX component we'll use use client we have a rafc we have a thread list okay so this thread list we don't need any props so let's save this first and let's actually just replace this with threat list import this and for the D done tab we can also just show the tread list here right because the the difference with this inbox and done is that the state is being changed but the the component is the same because we're going to be pulling the the done tab from the local storage anyways right so we're going to go in here we can see the tread list being shown up here so let's actually do this we're going to return a diff and the first diff alter diff here will have a class name of Max WID of full overflow y of scroll Max height of we got to do a calculation of 100 view height minus 120 pixels okay and then we have a another div this div is going to have Flex Flex Das column Gap -2 padding D4 padding top of zero okay and then inside here is where we're going to kind of get the list of threads so before that I'm going to just get the threads from use threads so now you can see I'm importing the use threats function the hook that we wrote just now and we're able to destructure the threads and you can see I can get the account the account ID that is fetching refret and the threats right so with this threats right I can actually then render it in a list but I also want to render by the date so you can see I'm grouping it by the date so September 25th September 24th September 23rd so what I'm going to do is I'm going to group them by the dates first before rendering out the groups individually right so let's have a group grouped threats right so this is going to be threats right do reduce right so I'm going to take the accumulation and the current threat that I'm looking at right and I'm going to look at the current date which is the which is I'm just going to format it right so format I can actually install it from uh date- FNS so I need to do studo bun install um let me see is it from format where is this format from yeah so date- FNS sorry date functions basically so it provides a lot of utility functions for us to operate on dates so in this case we will need the format variables from date FNS we need to format the threat. emails at index z. send at right if not we just have a new date by default and we're going to format it in this format year year year month month day day so it's going to show me this so I'm going to do send add like this um right so I think it's going to be fine fine so it's basically a format the so when we do index zero we're getting the latest email in the threat and we're formatting it by the year year month month day day format okay and then we're going to check if we don't have this date in the group yet we're going to then set the date to be an empty array and then we're going to just do accumulation the date do push pushing the threat so we're just basically grouping the threats by blocks of the dates and then let's actually just return the accumulation and we're going to start with a object right and this object finally is going to be a mapping from the dates to a list of threads and how do we get the list of threats right we can just do type of type of threats so this threats come from here so if I do type of threats I ultim get the invert type of array nice so now that we have this group threats we can actually display them right so let's continue here so within this Flex call I'm going to do object. entries right group threats or none right and I'm going to do map and so I get back in the first array the date and then the list of threats so this date is like 2024 September 25th and the threats is actually the list of threats that were in this date okay okay so now that we have this let's see we can then map it like this let's see what's wrong with here can find date uh we have to wrap it in another here so that we can destructure this yeah and then let's actually just return return a fragment right we'll just do react. fragment with the key being the uh let me see I get this right the map yeah the key being the date okay and let's save this okay let's see what is wrong here why am I getting an error so object. entries right map I get back here oh I see the error so let's just remove this here I should just reset from here so object do entries I'm going to do map so from this map I know that the first variable right is going to be array and I can destructure it to get back the date and the list of strats and then let's be careful then we can return a react. fragment with the key of dates okay now it works and for now I am going to just have a div here with the class name of text- XS font medium so this is going to be the date string text muted foreground and then margin top of four and the first one I'm going to have the margin to zero and this is the actual date so now if we refresh and we should be able to see okay silly me it was not showing up because um we were in the draft Tab and once we look in the inbox tab we can now see the list of threats cool so now we know that things are working right we can see the list of kind of um dates that they are showing up uh that's weird why is it showing like this format that's not right your your m m what did I put oh it should be capital M okay so now we get back the correct format cool cool cool cool so you can see that we are IND getting back the last three days of emails from what we did from the initial sync all right so now let's actually map through the list of threats below it okay and so below this diff I'm going to have get the traps I'm going to map over it for each threat what I'm going to do is I'm going to return return a button with the key being the threat. ID and it will have a class name of flex actually just let's do the CN thing we need that later so flx Flex D call uh items start g-2 rounded Das large border padding of three text left text DSM transition all and hover okay uh yeah wait so not have a relative okay and then within this div sorry within this button we're going to actually show the threat so I'm going to have the div the class name is going to be Flex Flex that's call with of full gap of one and then within this we'll have a another div with a class of flex items Das Center and then within this oh we'll have another diff it's a lot of diff guys we have Flex items that Senter G of two and then finally we can have the final diff I promise guys font semi and in here we can have the item sorry uh we can do sorry uh threats we can do threat right do emails do add negative 1 so this add is a method on the array and when we do negative one we're basically getting the last element of this array right Dot from dot name cool so now we should be able to see this is uh just a list of my name from the the the from of the the threads okay and then and then let's come down here so this is the first diff that you come out of and then the second diff right right and then under the second div that you come off we have another div okay here and then in here we'll have a this div have a class name of um let's see let's see let's see let's see uh we need a class name of let's do the CN thing and we'll have margin left of auto text of XS okay and then in this div we'll have the format distance to Now function from d f date functions we need the thread. emails. at1 so we're trying to get how long was it before you can see that we have this about 19 hours ago right so we can get that by doing this uh. sense at new if not is new date and then we'll just do uh at suffix equals to true so if save this we can see this about 3 hours ago oh right hopefully this makes sense we can zoom in a little bit for you guys all right cool so we get this stuff and then uh underneath here so we exit out by one diff and then exit out by another diff then under the second diff we have a another div here with a class name of text XS for of medium right and this is where we'll put the threat do subject so let's save this we can now see we got the subject underneath here cool and then let us come down here um cool so after the subject div you come down by one and come down by you exit again so now we're at the button again so this time this is where we're going to put the body snippet so we have the div but this class name will have text XS line - clamp of two text muted foreground and this time we're going to put the actual body s HTML inside here and so to do that you need to have the dangerous leet inner HTML and underscore uncore HTML to be uh let me see I threat. emails we're going to get the last emails body snipper if not going to be empty string so if we save that we can now see the empty string uh from we can now see the kind of the body snipper in here and because this is dangerous to set the inner HTML you kind of exposing yourself to vulnerabilities we will have another library to help us kind of clean up the HTML before we insert so this library is called um let me see Dom purify so we install Dom purify okay and so we need this one function I think we also need like add types so don't purify yeah we also need to install the typescript types for this okay let's import Dom purify from Dom purify and where wherever we were setting the HTML before we actually set it down we're going to just do Dom purify do sanitize do sanitize here okay and then we're going to have a used profiles for HTML to be true so just this just allows us to be a little safer but nothing really still changes about this okay cool stuff and then we're going to exit this div where we're putting HTML and now we're going to put the system labels so we're going to do item sorry threat. emails the first email we're going to doist labels do length so if there are system labels right we're going to just show a div with a class name of flex items D center- Gap 2 and then for the item the threat. emails. system label we're going to look through each label and we're going to return a badge so this badge comes from components d i all right and then uh we'll have not this but we want the variant the variant we get B uh variant from label passing the label and where do we get this function right get variant so we get this function wait get variant that's actually a really good question where is this function coming from um okay yeah it's a function that we have to write ourself below here so function get bch variant from label right so we take in the label as a string okay and then we'll just kind of return comp component props from react and what do we want to return we want to return the type of batch and we want to index the variant of it so finally we going to che check if work do includes label. to lower case we're going to then return default right else we going to return secondary cool so this is just basically gives us like a few different colors for the batch if they are kind of different system label po cool so now we get this nice UI right and then I think I think that's pretty much the bu of it yeah we did it cool so now let's actually uh allow people to select the thread they are on so when they click on it they can select right and to do that we need to keep track of the thread ID right and so to keep track of the thread ID here is what I am going to do um I need a global state to keep track of the threat and to keep track of of a global stage like that uh we should install jotai so ji is this um how do I explain joai um J Tha is a flexible State Management Library so it allows us to have just put little information into packets and we are able to use the state anywhere in our react app without going through that react context Redux 2 Kit type of thing so how it works is you literally just Define an atom so an atom is just like a thing to store information and now that you have this count atom you can use this atom anywhere in your app to this use atom hook and now you can access the value from anywhere in the app right so it's like a super simple library and so what I need to do is let's go down to use threats right let's have a let's just import Atom from joai and then we'll export a con thre uh Shad ID atom right equals to atom string on now so we want we want to keep track of the active Trad ID so if I click on this Trad the Trad ID should be set to this thread right and then I'm going to just have thread ID and set thread ID equals to use atom and I can pass in the Trad ID atom make sense so now I have this state that I can use anywhere within my app so I'm GNA pass it down here thread ID and set thread ID cool cool stuff so now with this thread ID I can come back to my thread list right right here and so I'm going to add some additional styles to my CN here so from the top button right if this button is selected means this Dr is selected I want to kind of uh show a different style okay so if I have let me see um actually no it's not here it's not here we want to show it somewhere here um let me see let me see yeah know what it's fine it's fine so if the thread. ID equals to the current active thread ID thread ID I want to give it a different background in this case background accent so now if I actually put this on click so on click I'm going to just set the thread ID to be the thread. ID and I'm going to just destructure this as well so hopefully you can now see when I'm selecting one the thread ID changes which causes my uh background to change as well because we're conditionally rendering the background based on whether the thread ID goals to the selected Trad ID yeah so hopefully this makes sense and now that we have uh and now that we have this threat ID uh actually no yeah I think it's fine I think it's fine now that we have this threat stuff uh we can then take this and we're going to we can then display what's on the the display here on the right side okay but before that I'm going to add the dark mode in here so remember in our app we have dark mode so it's actually super simple to do so let's go down to the documentation and search for dark mode okay select nextjs so the first step is to install this thing called next team so Stan install next- themes okay and then we need to create a theme provider so let's create that so go down to my components and let's do theme- provider. TSX right let us copy this in here so we're just exporting a provider and then we need to wrap this wrap our app with a team provider so let's copy this so let's go to our layout layout here so this wraps everything let's see okay so they're putting it in the body cool so let's do it here team provider okay let's see and let's wait for it to export okay so okay let's just wait for it to okay maybe my vs code is dying let's just load the window window then I'm going to import this team provider right I'm going to save it all right so now that I have this team provider I can work on my my my toggle button okay let's see why is this not being imported okay no it's fine this should be fine okay so I'm going to save it and I'm going to come down my components I'm going to create a team- too. TSX component okay so for this team toggle right I'm just going to make it a client component uh let us do rafc team toggle right so I'm going to get the theme and the set theme from the use theme Hook from next SL themes and I will have a function called toggle theme that just sets a theme uh to the opposite of light and dark and finally I can just return a button here right the button do have a variant of outline and then the size of an icon right and un click we want the toggle the team and then in here right we're going to just copy in these two icons here the moon icon and the sun icon cool and so this is literally just saying that look on my dark mode I'm going to scale it to zero which means I'm going to hide it and on the light mode here here uh I'm going to scale to zero on my moon icon so depending on which mode it is on it's going to hide automatically so if I go back here I'm going to add this tee tole to my main page here and so I'm going to just wrap this in a uh okay how's going to do I'm going to wrap this in a fragment I'm going to have a absolute uh bottom four left four and I have a team toggle like this and then hopefully I can see this and if I press on it you can see boom our UI is changing from dark mode to light mode and everything looks nice awesome awesome okay so now that we have done that let's actually go on to work on the threat display okay so now let's finally move on to the thread display on the right side right so when you click on the thread now we are able to to select and keep track of the active thread ID but now let's actually use that thread ID to uh to show some interesting stuff here okay so now let's actually go back to our cursor I mean vs code and then let's actually clear everything out let's go back to the main mail page so while we see the threat display let's actually create a new component right let's call it threat Dash display. TSX use client uh RFC threat display okay so Trad display let's actually save this and let's go back here to show threat display right and then we should able to see threat display everything is still showing up here but now if we uh let's actually get the thread ID from use threads here I'm going to get back the thread ID and I'm going to show the thread ID that's being selected okay so if I come here you can see you see as I am pressing around the thread ID changes right so I'm going to use this thread ID to find the selected thread from the list of threads and then we're going to just display it so remember we also exported out the list of threats so now do you see why we actually did this Hook is because we are able to access the list of threads anywhere in our app without needing to kind of rewriting rewrite the refetch hook right hope this makes sense and so now let's actually find the uh threat so we just do we just look through the array and find the one that has the active threat ID right and then since we have that we can also show uh we can finally show the threat do subject to begin with right so now can see if I click around I can see some um yeah we can see some threads that are being shown up here cool so now let's build out the UI for this so we're going to have a uh we're going to have this stuff up here on the top right uh but I'm going to do here is let's just do something like this so instead of returning the subject so let's return the D first so the this the diff we have a class name of flex flex-all uh height D full within that we'll have another div with a class name of flex items that Center gap of two all right and then inside here we have basically uh button right and the button will have a variance of ghost the size of and sorry the size of an icon right and then we're going to disable it if there are no thread there's no selected thread okay and then there is archive button in here right and the archive button has a size of four so if we save this now we can see something that is happening here cool and I think we also need some padding here right so I'm going to find kind of where we're putting the padding padding uh try display interesting interesting do we not have padding here H let's see how do I get P okay you know what it's fine uh we'll just we'll just see what happens oh sorry this is not gap of two my bad this is padding of two ah now it looks right okay cool so the second this have a padding of two okay and then so now let's actually copy the button down a few times and so for this is not archive anymore this is the uh archive X icon right and let's copy this down again and then we have a um it's not archive X anymore we'll just have a uh trash to Icon okay this makes sense um Flex item Center there's one more here right oh I know what happened okay so you see these buttons this buttons need to be wrapped in other Flex items that Center there's Gap two so that we can give it some spacing between here so just know that there's like three l of div down here right because we have kind of the first uh first div here sorry uh that's three because we have kind of this section here and also the other section here and within here we have another div so this first div is for the three initial buttons here right I'm going switch back to light mode okay awesome then we'll have a separator kind of after this button separator let's see um let me think let me think does this make sense this makees sense separator cool from component UI and then the orientation is vertical right and then you can see it's like this and then we'll have just a another button but this is for the snooze icon so the snooze icon will have a clock icon yeah makes sense so I think this two stuff has to be in the div as well so that we have equal equal spacing and then let's just give it a class name of height of four you know what I'm going to move it back out here and let's just give it a margin level of two and then this button let's have a class name of margin Lev of two all right awesome and then we're going to have another a diff out here right so we have a diff here that's also the same Center uh like this sorry it should be in here okay and then we'll have a margin left of Auto right and then in here is where we can just uh put a kind of drop down so let's just for the drop down component from Shen here right so let us just copy the import and let's copy the utilization cool and then we'll have a obviously you just have a button here but the button trigger would actually be a more vertical icon more vertical and in here we can just uh put the content as a line to the end and then we can have Mark as un Red Star Thread at label and mute thread okay okay this looks weird I think it's because uh Flex okay it should all be in this div right cool so now we got this Mark as and red awesome and then now if you come out of this div so let's just collapse this row so this is the buttons row and then we have a separator so this separates here like this right that's cool and then here is where we put the actual displays of the thread so you can see we got the subject here we got a reply to got the date Etc right so this is where we're going to put our stuff okay and so I'm going to come down here and I'm going to just see if there's a thread right so if there's a thread I'm going to display something if not I'm going to display that hey you know what there's no message selected so we have a class name of padding of eight text of Center text muted foreground and say no message selected cool so if I refresh this right it says no message selected cool but if I have a threat what do I want to show okay so if I have a threat I'm going to have a div pardon me like this so the the diff will have a class name of flex Flex of column Flex call Flex of one overflow scroll okay within the div I'm going to have a flex item Center padding of four okay and within this we can have a uh class name of flex items that Center gap of four text XM okay and then finally within this thing is where we put our Avatar so component Avatar we have Avatar fallback right so the fallback we have a well sorry we have the image first uh sorry the image will be a self coding tag okay and then the out will be kind of Avatar and then obviously we have the Avatar fallback and inside this fallback is where we can actually put the initials so we'll just take the active threat we'll get the emails the first email we'll get the who is it from we'll put a name and then we'll split it by the Whit space right and then we'll just like kind of just get the first so basically getting the first character of every uh word in the in the name all right and then we'll just map it ch ch the we get the first character of the first word and then we'll join it together do join using this this empty space just put some optional training and now we got show up something cool so now you can see my name Chong we're getting the first initials of the whole of the of the two words so EC that's why you are getting the EC here make sense all right so then let going to exit the Avatar right and then we have we're stilling the the third div right and then we have another div here with a class name of grid gap of one right and then inside this div we'll have a class name of fonts semi bolt and then we'll have thread. emails the first email from. name right and then we have another diff with text XS uh line clamp one inside here we're going to put the the subject right subject and then we'll have another div with the same class name text XS line clam one and then here we can have a span. font D medium inside this font medium we can have reply-2 right and then down here we're going to have thread. emails the first email dot from. address uh yeah from do address so if we save this now we can see things are starting to happen right so now let's come down so uh come out of the reply to right so you see the first div that you come out of the reply to and then the second diff of the line clamp and then the third diff like this right so it should be let me see if it's out of the grid Yeah so basically just collapse the div with the grid and you come out of it right and then come out of it one more time so we are kind of out of that so now you have it should be two divs out from the bottom and then let us do thread. emails do send at so if there is a scent at I'm going to put a div uh div like this with a class name of margin left of auto text of XS text mut foreground and then I'm going to format format from date FNS so I'm going to import this wait is that it date FNS we'll get new date we'll get the threat. emails at index zero. send at right and we're going to format it in the PPP format so this becomes like this kind of long format September 25th uh 225 PM okay cool all right so then we come out of that uh we come out of the diff uh right here and then I think let me see come out this and then we can come out one more so now you just one layer out you can have a separator right separator sorry separator okay so it separates this and now finally under the SE operator let us have a diff the class name of Max height and you have to calculate 100 view height minus 500 pixels right overflow of scroll Flex flex-all okay and then inside here we have a diff that says class name of padding of six Flex Flex call Gap of four okay and then we can map through the the email so now finally we can map through the emails and display each individual email card so TR the emails. map for each email what do we want to do we want to return a let's just put a div with the subject of the email with the key of email. ID right so now we can see this is the subject like this okay and we're going to put the actual email card later on but then what I'm going to do is uh finally I'll will come down to the last diff here below here uh I'm going to have a flex one and then I have a separator again with the class name of margin tab of Auto and then this is where we're going to put our reply box so this is the email box here this is the list of emails we have a big Flex one to space it out and then this is where we put our reply box okay so for now I'm just going to put a stand like a placeholder reply box item here okay cool so if I save this cool so we got reply box in the separator okay so now let's actually work on displaying the list of emails okay so let us create a new component called email email- display. TSX whoever use client we will do our uh t srf c e email display so we need to be able to get the email that we're trying to display right now so what is the type of email okay the type of email is going to be router outputs so this router output is going to allow us to get the type definitions of the account do get get threats right the number so we're going to get like the first element of the get threats we're going to then index into the emails and we get the first element of that so then this will finally resolve to the actual type here so this is just a really nice utility feature from trpc where we get the uh the type output of the output of the procedures so then we can then return actually return a email display here passing in the email and the email ID as a key you can see you see this matches up exactly and there's no type errors because this router output is always correct okay so now let's save this so I'm going to just unst structure the email from here okay and what I am going to do what I'm going to do is I'm going to just look through it okay and let me show you what I'm going to do here uh okay so okay you see the issue is if I send to let me see if that's a good example add it okay you see this a great example I love this so um if it's coming from me I want to have a this different UI and if it's coming from someone else I want to have another UI so I need to differentiate between the emails that are coming from me and the emails that's coming from someone else so we're going to have a variable called is me right so now it's going to be true right but how do we determine as a if an email is from me or not so to do that we need to know um who sends the email and if the sender is me okay so to get my identity I can just get it out from use threats so remember we got the account right so if you go into use threats the account is basically the list of accounts and which account I'm looking at right now so in this case the current account is this right so uh I can show you I can show account. email address right so this will show me so this is my email address that I'm looking at right now so then I'm going to look at this email right so it me is if the account email address equals to the email. from. address right does it make sense so if I'm the one who sent out the email and the account is me like then this variable is going to be true okay so now based on this variable we can then display things differently so I'm going to return the div the div will have a class name right of we have a CN then I have a border has to be around rounded medium padding of four cursor pointer trans actually no not cursor pointer transition Das all hover translate X2 and then okay and then if it's me I'm going to have a border left of of gray 900 and Border left of four only if it's me okay so if it's me I'm going to display this thick border on the left side and then inside this div I'm going to do is have a div with a class name of flex items Das Center justify Das between g-2 and then in here we have a narrative with a class name of flex items at Center justify between gap of two cool and then finally we're going to check so uh let me just okay you know what for now I'm going to just put the uh email. from. name so who is it from okay so you see Google this from Google right Mak sense here so both are from my name basically cuz it's two different email accounts by the same name so I'm going to have this Avatar icon right so how do I get this Avatar ion uh I'm using this library right so this library is called install react D avatar so this will give me uh a component where I can just pass in the name and you will tell me give me this nice Avatar icon okay so let's remove this pen so if it's not from me if it's not from me I'm going to Showcase Avatar so this Avatar comes from uh from react Avatar cool and let's actually do this let us have a name so the name be email. from. name right uh or email. from. address and then the email will be email from the address and we'll have a size of 35 to make it bigger our text size ratio should be two and let's make it round round equals to True okay so if I save this now we can see I got this icon here looks pretty right okay and let's see yeah I think this makes sense cool and then so if this will only show up if the email is not from me right but then if it's from me is me I'm going to show is a span with just a normal class form medium right that says um let me see let me see actually no this span is outside and then I'm going to check so if it's me I'm going to show me if not I'm going to show the email. from address cool so now I show the name beside the Avatar cool stuff and then underneath the div so I'm going to have a pag with a class name of text XS text- muted foreground okay we have a format distance to sorry to now right and then we have the email. send at or new date and then we have a add suffix equals to true so we get to see when it was sent all right then finally under the div outside we have a height of four just a spacing of four and then this is where we show the actual email so we are going to use another library to showcase the HTML email right so come down here and install install react dlet okay so I'm going to just import letter from from react letter okay and so I can just do I can pass in letter and then I want to pass in the HTML to be the email body right and then I can have a class name of BG white round medium and text black Okay cool so now I should be able to see the email this HTML email being formatted cool awesome so now we have built kind of the basis of an email client we're able to display emails right okay awesome so before we continue I'm going to start actually teaching you how to build the command K bar so you see this command K when I'm pressing command K I get to bring up and I get to quickly switch to my either my sense folder my inbox folder I can switch my dark and light teams right I can press TT right which you can see is the the kind of command to switch teams I can go to my send view by doing GS GS and GI right you can see like super clean right and then I can also switch my accounts right to the different accounts that has been connected to this user so I'm going to teach you how to make this command bar item right and so let's actually go back here to our code and so the first thing that you got to do is to install a new library called Kar so I'm actually using this Library called Kar right which is just a allows us to make it super easy for us to actually make Command K applications work right so let's actually do that I'm going to walk you through step by step on how to do that so first thing let us install slow but install K Bar okay and so let's come down to a component components here I'm going to create a new folder called Kar and I'm going to have index. TSX in here so this going to be our default export from Kar right and so we're going to just export default function Kar right it's going to take children which comes from react. react node and so uh how we're going to do this is the first thing is the first thing is we're going to have another component called cons actual component so because this K bar is actually going to be a wrapper around this actual component right because we're going to have a keybard provider right so let's import that import keybo Provider from kbar um let me see if that it what's my keyar provider um let's see K Bar provider nice yep just a misspelling so we're going to wrap it here and we're going to wrap the actual component like this right uh and then the actual component will actually then wrap the children right and so this actual component we're going to take in the children right like this right is going to return a kind of fragment and inside the fragment we're going to just have a Kar portal so then within portal we have a Kar positioner within the positioner we have a k bar animator and then finally we can have a kind of uh K Bar search and then we have the we can then put the children right below the K portal okay so this good and what I'm going to do is I'm going to just wrap my app in this Kar provider Kar provider so let's come down to our um let's see where should I put this in uh let me see let me see um K Bar yeah let's put in in our layout so let's come down to our layout and then um let's actually come down to our all the way into our children here inside the crpc react provider and we'll wrap it in K bar so let's wait for it to load Kar let's restart our vs code buggy uh Works only in client component of course so let's go into our K Bar and let us add a use client on top here so you see if I press command K you see this input is starting to show up here so now let's actually start this right so we're going to start it by doing this so for the kar positioner with have a class name of fixed insert zero background black divide by 40 when it's dark mode we want to have background black divide by 60 backdrop backdrop Das blur DSM scroll bar Das height uh important to have no padding and let's just do have a a 999 Index right and then with a keyboard animator with a Max width of 600 pixels we have a margin top of 64 right with full BG white right when is Dark mode I want to have a BG gray of 800 Tex for ground when it's dark I want to have a Tex gray of 200 okay and then we have a shadow large border when is Dark mode mode I want to have a border gr of 700 right I want to have a large roundedness overflow hidden relative and I'm going to negative translate y12 right it's a super long class name sorry guys but yeah and then above the K Bar search I'm going to rra it in another a div here with a class name of BG white when this dark mode I'm going to have a BG G of 800 right and I'm going to wrap the keybo search in another Z and this one will have a class name of Border X of zero border bottom of two when is dark I want have a border gray of 700 cool so now if I save this I come back here we can now see we have a nice kind of animation and the inputs right so now that startall the input so we have a padding y of four padding of six text large with a full background of white right when it's dark we want to have background of 800 outline none water none Focus outline of none right Focus ring zero Focus ring of set of zero okay and now we have this nice command bar awesome okay so now let's actually kind of Define some actions so the basis of this is we're going to give this a list of actions that it allows us to take and then you will render it automatically for us okay so uh here I'm going to come up here in inside the kar component I'm going just Define a bunch of actions to be this action that we def that we take from Kar right so every action every action in the actions array needs an ID so right so let's say we have a inbox action so we want to have an action that goes to the inbox automatically okay and then we'll give it a name so we have a inbox here right and then we can give it a shortcut if you want right so we can have shortcut of G and I so if you press G and I on the keyboard consecutively it's going to execute this action we can give you a section of navigation right and a subtitle of view your inbox and then the most important part is the function that will be called whenever this action is selected in this case let's just conso the log inbox right and so to pass this action we just pass it into this K Bar provider uh prompt here right and then if you come down here and press command K right obviously we can't see anything CU we don't have that uh we we're not rendering the results yet okay so now let's actually render the results so come down here I'm going to create another component called render results. TSX right so this render results will have a kind of R fce render results and we'll we'll import this render results just right underneath the search right yeah so after the div we just do render result we'll save this here render results come on oh here yeah that works as well render results and then for the render results um kind of component what we're going to do is um we're going to get the list of results from um hook called use matches from Kar right and then we also need the roots action ID and then we're going to basically export out a wrapper on Kar results right and then we have the items to be the results array okay and then we can render it have a custom render right which we can structure the item and whether if is active right and then we can basically ask if the type of item equals to string we can just render a diff with a class name of padding X of four padding y of two text xsm uppercase opacity of 50 text grade of 600 when it's dark I'm text grade of 400 right but if not what I'm going to do is if it's not string I'm going to render a result item right so let's actually create another results Das item component right so this result item will have R fce result item right so this result item is special right because I need to do a forward ref so what I'm going to do is just delete this Con result item and you know I'm just going to copy this in because it's like super long right uh but the point is that uh we need to finally add one more thing result item. display name equals result item so this is literally just a component that just controls some State have some framer motion elements to it right so let's also install framer Das motion cool let's save this let's go back here and then for the ROM results let us actually import the random item right and then the action will be the actual item here the active will be set to whether is active and the current root action ID will be set to the root action ID or or empty string cool so now if you go back to the app we can finally see our action right here and so if I press if I go into the console and I select this I'm going to see console lock inbox because that is the action that we're performing in our Index right does it make sense here performing here okay so how do I actually switch the the tabs right I can remember I can I can access the tab from the local storage right that's why we're using use local storage so uh we'll get the the tab and the set tab from use loo storage and we're going to get the normal human Dash tab index and by default we just have a inbox okay and then what we're going to do is just going to set the tab to inbox okay and so if we do this if I'm going send and I press inbox look what happens I switch back to inbox right I'm going to Ty inbox it switch just mean back so perfect it works so now let's actually just do the same for the others so I'm just going to copy in the others it's going to be the same we're doing for the drafts and for the send right and let's just delete this priority so if you do this now refresh right we can now see there's a inbox and drafts and if as as I type along drafts I get to see and it automatically switches me over and if I press GS right you can see it automat switches me GS right GI does it make sense drops right cool cool cool cool so just put drops make sense all right so now we're almost we're almost there so let's go to our inbox first TRS all right and what we're going to do is we're going to do the same but this time we're going to do it for the done so uh we're going to switch this as well inbox and done right so let's do that we'll have a done is normal human done and then we'll just do the same right for the action and the pending action uh let's see here cool so when is when we have pending action here we can just set the value here to be done and it'll automatically be switched over yeah hope this makes sense so now we can see you got this done and pending right cool and so now let's actually also allow you I'm going to show you how you can add custom actions like theme switching so you can see I have a toggle light theme here right or I have a dark team so I'm going to show you how to do this so to do that I'm going to have a custom hook called use theme use theme switching. TSX right so for this use theme switching right I'm basically going to just export out a a hook called use theme switching okay and I'm going to import the theme and set theme from uh from use theme uh from use theme uh let me see uh where's this let's import it from next SL teams okay and then we have a function called toggo teams like this that just switches it based on it like this okay cool and so what do you need to actually do to register some actions within the command bar so we're going to import uh something from K Bar Key Bar called uh use register X actions okay so this hook will basically allow us to actually register some actions into this thing so what going to do is we'll have the theme actions so this is going to be a array of action right so I'm just going to just copy paste this thing over here so it's just the same that we've shown just now right but we have the ID the name of it the shortcut to it right the section and the actual function that we want to toggle it to right so for the set like see we're just like just calling the function and then finally I'm going to just call use register actions I'm going passing the theme actions and for the dependency array I'm going to change it whenever the theme changes okay so now I can just come back to my K Bar and I can just call use theme switching uh use theme switching I can import it from my local directory and so if I come back here now I can see I got my toggle light team called toggle dark team right and I press Double T to just toggle my team so that's how you kind of customize and add actions in here okay and I'm going to do the same but for the accounts so you can see I can switch my account here by like let's say if I have three accounts I can just immediately switch it from here so let's do that so we're going to do something very similar right we're going to have an a hook a custom hook called use account switching. TSX right and then it's just going to be uh I'm going to copy this in for you to understand so let's just do let's get the API oh my God typescript is did that's why okay so this custom hook what it's going to do is I'm going to fetch in all the accounts from my API my uh my trpc API and then I have this main action right so for me to switch account and I'm going to access my account ID from use local storage right and so I'm just going to register actions and what I'm going to do is I'm just going to concatenate the main action with the list of act with the list of accounts and with that I'm able to just show the account as part of my command so if I come down here and I'm going to do use account switching here and I kind of refresh I should be able to see this switch account and because I only have one connected account it only shows me this but obviously if you connect more accounts you able to get more uh options to show up here cool so that basically concludes how you can add a command K uh function and you can obviously add anything here you can add like Market as done you can com you can like navigate it anywhere you want as long as you define the action right and you define the function that you want to be called whenever the action is selected you can do virtually anything right so yep all right cool so now let's actually continue uh building out the reply box down here okay so here's going to be the structure right so actually let's go back into the code first right and for the reply box let's create a new component reply desbox TSX okay let we use client component RFC reply box okay and we'll just import the reply box component here first reply box cool so now everything still looks normal right reply box okay and then what I'm going to do in this reply box I'm going to have another component called um email- editor. TSX so I'm going to explain to you how the structure is going to be like here uh I'm going make TS RFC cuz we need props for this component okay and for the reply box right we're going to make use of the email editor for now okay so the reason why why we have this email editor component is because we're going to be using this component in both so we're going to be using this email editor component in both uh this part here as well as the compos section so this email editor will be have will be a reusable component right for this composing and also for this reply box so therefore in the reply box we're going to make use of this editor and then in the composing we're Al going to make use of this editor so let's just build out this email editor okay and so to do to do that obviously we're going to use this a library called tip tab so tip tab is a kind of very customizable R text editor that we can use to format HTML for emailing Etc right and it's super easy to use and integrate tightly with react and nextjs so let's just get the starting thing first so to actually install tip tab we have to do sudo B install at tipt SL react right we also need at tip t/d kit and at tipt / extension text okay and after you install it right let's come back down to the email editor and the first thing is let's actually in initialize the editor so we have cons editor equals to use editor and we can actually import this use editor from at tip Tac react so this editor we can actually configure a few things the first thing is autofocus we want to set it to false by default right and then we have we'll give it a few extensions so if you see in this tip TP editor that we have here we have all this menu bar items right so we can we can like type stuff and can bold it so everything here is part of the starter kit extensions that we can add to the editor so to actually get the starter kit we can just import starter kit from the starter kit from at tip t/ starter kit and we can just put it as an item in the array cool the next thing we also want is to have a custom custom editor because because in the feature we will have this command J when you press command J it will be able to autoc complete the message so to hook in the auto complete we can have our own custom extension right in this case I'm going to name it uh custom text and we can extend the text so what do we get this text from we get this tab text from at tip tab / extension text and so what I can just do is textt to extend so I'm extending a current feature right and I can to add keyboard shortcuts right so this gives me the ability to add a custom keyboard shortcut to trigger some action in this case I'm going to return I want to trigger this when I press meta- J which means command J on Mac or control J on windows so whenever we press meta J right this function is going to be called and for now I'm just going to console. loock meta J and I'm going to press I'm just going to return true for now cool so now I can just add this extension to my uh extensions list and lastly I also need to have a state right so I'm going to have a state for value and set value right and it's just going to be a react use state of a string so it's just going to be a string that holds the HTML value of whatever editor State we are having and so on update so whenever I type stuff on the on inside the editor right I'm just going to let me show you I'm just going to set the value to to be the HTML HTML version of the editor state right so now that we have this we can actually start displaying the the editor information the editor value so the first thing here is I'm going to return outer div and within this outer diff I'm going to return a uh another inner diff and this inner diff will house our content and here we have a class name of Pros with a full padding XEL four and in here I can put in the editor cont content from tip t/ react passing in the editor as well as the value right like this okay and if I come back to my component now you can see I have the beginning of what seems like to be an editor and I can I can select all I can press command B to bold it so I have some rich text editing kind of built in okay and one more thing you need to add is you need to go go down to your Global or css and add these two classes into your code that will'll be using later on so this tip T Pros mirror is just to remove the outline of our editor right if I remove this you can see I get this ugly outline again so if I add this back in the outline is gone right and we know we need this is active class later on right because we are trying to make this menu bar and we need to have this this background whenever is talk on or off so this this editor will be made out of a few sections so the first section is this top menu bar where we get to control the state of the editor and bold it I tell size Etc we have a second part where you can see that when we when we expand this we got a section to enter the tool the ccs and the subject right and then we got the kind of draft right with the email chat B here which we will show later and then we have the editor which we have done and then we have the last section here which is the sending of of the email okay so now let's actually add the menu bar on top of this component first so to do that come down to your email editor and let's actually create a new component I'm just going to call it editor- menu bar. CSX okay so for this is going to be uh very redund like there's going to be a lot of uh features that have the same so we're just going to do TSR fce right and let's rename it to editor menu bar okay and so it's going to take in only one prop which is the editor component and this editor will come from the type of editor from tip t/ react right so we're going to be passing in the editor instance from our email editor right we going pass it down into our editor menu bar right so let's actually just uh editor menu bar let me see why I can't why we can't so let's save this maybe typescript is dat again so let's reload the window editor menu bar okay now we can exit we can import this but the thing is editor might be now right because it might have not been initialized so I'm going just go is if there's no editor right now I'm going to return now I'm going to wait for the editor instance to load before kind of displaying this stuff okay so now that we have this menu bar starting to show up right here let's actually start it okay so the first thing I'm going to do is I'll just return the outer div right outer diff of a class name of flex Flex Des W gap of two right and then in here I'm going to display the kind of this buttons up here and so the first button is going to be a normal vanilla button and so I'm going to put in the Bol icon here both from Lucid D react let's give it a class name of size four and tex secondary forr okay and the button we want to do a few things right so when we click on the button we want to toggle the B uh feature of the selection so what we're going to do is when we click on it we'll do let's actually destructure the editor from props and let's do editor dochain do focus. toggle bolt. run okay so when we click on this we're going to basically call the toggle Bo function on the editor right and then we'll disable this button right in cases where the editor cannot be B so not editor. can. chain. focus. toggle Bol um toggle Bol oh toggle go bat. run so I'll disable this button when in certain cases the editor can't allow us to bat the mark and finally we have a class name right where editor do is active Bol so if it's if the current selection is bolded right I'm going to just showcase the is active class if not I'm going to show MC string so this ex active class I've show you just now is from the global or css that just adds a background color to it so now if I have this and I come back here we can now see we have this Bol icon and now if we can if we type some stuff and we come in and press Bol right it's going to Bol the stuff and my command B also works and you can see that the class name is updating as we B it right cool and so now I'm just going to copy in the rest of the code because it's going to be just the same except that we are just copying from for eism strike TRS and all the code is the same logic right you can see that we're just call the toggle code uh function on the editor right so now if we go back here we got all the stuff here okay cool so now that we got this uh thing working let's also give it some uh let's go back to the editor right and let's continue styling this this editor okay so for this let's go to to the editor okay and so in this editor uh this menu bar I'm going to wrap it in a diff right and this div is going to have a class name of flex padding of four padding y of two and the botom of bottom right so now we can see that it is we have some separators up here okay and for this editor content we'll just leave it as is and at the bottom here I'm going to add a separator from component SL separator and then below here I'll have a another diff and so this diff here we're now building the bottom section which is the command J stuff okay so here I'm going to have a class name of padding y of three padding X of four Flex items Das Center justy that's between okay and then in here we have a span of a class name of text SM and we'll say tip press here so press what what do we want to press we want to press a keyboard component so this keyboard will have a class name of padding X of two padding y of 1.5 text XS font semi Bol text Gray of 800 right BG gray of 100 border border Das gr of 200 and rounded Das large and in here we just put the words command plus J right and then we'll just have a space after here for eii or2 complete and then outside of the span we'll have a button here import from button ui/ component button that just say send okay and now if I come back here I have this nice component here that says press command J for auto complete and at the bottom right I have a send button cool things are starting to look good right but now let's actually build the kind of this draft text here and when I click on it I want to show another element that uh is conditionally rendom whether it's expanded or not so to do that let's actually also hold some state so let's have a cons expanded and set expanded to be a of of Boolean okay and so I'm going to come uh to my come to my editor content and above this div right I'm going to have a div here that has a class name sorry that has a class name of padding of four padding bottom of zero space- y of two right and in here I'm going to just conditionally render so if it's expanded I'm going to show just like this where I'm going to show my CC inputs okay so now if I come down here to my uh below the the expanded stuff here I'm going to have another diff here so this diff is going to have a class name of flex items Das Center gap of two so this is where the draft text here this this will be so I'm going to here I'm going to put another diff in here that has a class name of cursor Das pointer right and when I click on this I want to set expanded to I want to togg go to expanded State and in here I have a sp that has a class name of text green of 600 and font medium okay and in here I'm going to put draft right and below the span I'm going to have another span and for now I'm just going to put here like draft to Elliot okay so now we got this drop to Elliot you can see when I click on it the CC inputs kind of show up here because we're conditionally rendering rendering this okay so now let's work on this CC input stuff okay so you can see here I'm going to have this multi select with a list of all the email addresses that is linked to this account right so let's actually build this base component I'm going to call it teex select so this teex select allows us to select multiple values to be placed into the select and so to do that let's actually create a new component called uh Tech input. TSX so let's do TS RFC and we name it to Tech input okay and so this tag input is going to be built on top of a library called react select so let's install that react D select so this react select just give us a foundation for us to build a multi select uh input on top of it cool so the first thing let's do is actually just import uh select from react Das select and in here I'm going to return a div here and this div will have a class name of of Border rounder of medium flex and items of Center okay and in here I'm going to um I'm going to take in a few props here right so for this select I need uh the first thing I need is the default values which is going to be a array of label string with the lab the lab with the value and this will be an array right so basically each item in the select will have a shape of a label and the value and then I will have a placeholder and I want to have a label as well right so imagine here the label is like two and for this the label is CCS and this is a default value right you can see when we click around right we have a default populated a value of who want to send it to by default which is basically the person who sent us the email right course we're replying to them so therefore we need these default values to be populated and then we'll we'll accept a call back of unchange right and so this will have just this shape basically we're going to whenever they select a new stuff or whenever the input changes right we're going to give you a call back and by default the value is just going to be this array of labels and values and under the unchange let's also accept the value right so this is for the control input good stuff cool so now that we have this let's continue coding it out so inside this diff I'm going to have a span that just shows the label right let's just destructure all the props first so this span will have a class name of margin level of three text SM and text Gren of 500 text gr of 500 cool and then underneath here let's actually have this actual select component from react select and I'll just give you a few props by default let's give it the value to be value right unchange to be unchange right and the and the kind of placeholder to placeholder and we have a each multiple Fu so that we tell react select that this fu is going to be a multi select so let's just import this Tech input in our email editor first to see how it looks like so Tech input here and we'll give it the props that you need so default values let just give you empty array for label let's do two for unchange let's just console the lck out first and for placeholder let's put like uh add email sorry at recipients and lastly the value will just be an empty area for now so if you go back to the code now we can see when we expand here right we can now see that we have this react select but it looks super ugly now and there's no options here right so now let's actually start working on that recipients and the Tex select so to do this is let's actually just come down here let's just add a TS ignore cuz it like some typescript error but it's actually not issue because I know that this is correct right because it's just they have a more complex type here okay so for this select uh I'm going to just put the default value to the default values R and then let me see will have a bunch of options so how do we actually get this list of options so you see when we click on this we got this list of options so where is this coming from this is coming from all the email addresses that's linked to this account and so let's actually create another trpc endpoint to kind of fetch the list of email addresses to be displayed in this dropdown so come down to your account. TS router right so let's actually create a new uh endpoint for get uh suggestions right and this will be a private procedure right and for the input we will need obviously the account ID that you are trying to get the suggestions for right and we'll do the same thing so query let's get the context and the input right and obviously first we're going to just authorize the account access to the to the account that the user is trying to access and then what I can what we can do then is just return await cx. db. email address so we're going to find all the email address where the account ID equals to the account the ID that we access here and for now we actually just need the address View and the name VI cool so this is going to be our value sorry this going to be the label and the name for the options so now let's come down to the tech input let's actually fetch this down so cons data let's rename it to suggestions equals to API from react do account. getet suggestions. usequery right and once you get the account ID that we are currently viewing so to do that we can actually destructure it from use threats so this reusable hook that we' have written and let's get back the account ID here so now let's actually pass account ID so you can see types script is telling us we need account ID right and so now that we have a list of suggestions we can actually pass them down to the suggestions sorry the options uh menu in the select here what I'm going to do is I'm just going to show the list of suggestions here um let me see so I'm going to put suggestions I'm going to map it right so because they want it in a certain format so I'll just put suggestion they want us to return an array of object you can see we need a label in the value so for the label I just put suggestion do address and for the value I also put suggestion. address so if I save this and I come back here we can now see we now get a list of all the email addresses within our account cool and one more thing I want to add here is I want to kind of have this cute Avatar icon beside the address so how do we actually make add this Avatar beside this ugly select to do that I'm going to have a new I'm going to just move these options out so I'm going to declare new variable options and it's just going to be wers in here I'm going to copy this out into it own variable I can put this options here but here instead of of this label being a text field I can actually return a uh GSX component right so this GSX component is where we're going to put our Avatar so this label is going to be a span right with a class name of flex items Das Center G of two right and in here I will have the avatar from react D avatar which we have used just now the name is going to be suggestion. email sorry address the size is going to be 25 the text size ratio is going to be two and rounded round is going to be true and underneath here I'm just going to put the suggestion. address and now if we save this right we can now see I got this cute Avatar beside the address field okay and now let's actually also give it some styling to make it not so ugly all right so let's go back here let's see so for the select let's actually um give you a class name here class name of width of four Flex of one so it expends it all the way out here and let's get rid of this ugly border so to do that write we have a class names array sorry object and here we can actually customize every little stuff within the react select so in this case we can customize the control which is the out the outer diff here the outer select here and to do that I'm just going to copy in here class names so for the control we just want to remove for the borders and the outlines to reset it then for the multiv value we just applying some dark mode themes right and for the labels as well and so you can just copy this in from the repository but what you then get is this nice array a nice select where we can see we have the Avatar and the recipient we have this list of stuff here we can now go back into our email editor and use this once more so what I'm going to do is uh this is actually one more thing I need to do so if you see in the the the behavior if I start typing Elliot and see that nothing pops up I want to be able to select the start the Emil that I manually chose here right and so to do that you can see that right now if you start typing and there is no kind of email corresponding to it I can select it here so therefore what I want to do is actually keep a state of whatever I'm typing so that I can show it as the last option here to do that I need to maintain another state con input value right and this has to be a state and so what I can do here is I can do uh on input change so whenever I start typing I can just set the input value to be this and so for this options here right if I have some input that means I I have typed in some value what I want to do is I to add this input to the end of the options array here so I'm going to check so if there's an input right I'm going to do options do concatenate so I'm adding it to the end of the of the options the last option right I'm going toate the label to be the input and the value to be the input as well right so let's see oh it should be input value right so input value so right now if I save this and I come back here if I start typing stuff you can see this start showing up right and obviously it's not going to save because the value is not bounded right because it is taking out from uh external props right so let's just save this Let's ignore this cuz we know that it's going to be correct cool so now let's go back to our email editor component and I'm going to just copy this down I'm going use this for the CC's so the label is CC right and we'll save this and now we got this nice component where we can add the ccs and the tools right make sense cool and so now we our email editor is looking great and one more thing is I want to add a subject input here right so let's come down here below the TCH inputs let's have a input here from component SL input we'll have a ID of subject and a placeholder of subject and we need the value to be subject but how do we get this subject value so we need to pass all of this stuff in right so you see this these values here and this default values and the un Change and the subject we need to be able to receive it as a prop to this email editor so to do that let's actually craft out the props that will pass in so obviously we need the subject which is a string we need to have set subject oh what's this set subject to be a string here uh let's see let's reload the window here cuz it's lagging so set subject here is going to be a it's going to take in a value and just output void oh void cool and now we need the the two values right so is the the array of values that we are going to pass in here to the TCH input so two values is going to be label string is going to be array of this and set two values is going to be the same thing and for we do the same thing for the the CC values right and then we also need uh the the people that were actually sending it two right so the two is going to be a array of strings and then we're going to have a handle send function that we're going to pass in as a callback so value void okay and then we have a sending as a Boolean so that we can disable the button when it's sending and we also want a default two bar expand to be a Boolean right because you can see that by default here by default here it's not expanded right but when I'm in the compos button by default the two bar is expended so I just want to pass that in into our email editor props okay and so now I can just destructure everything from my props and I can pass this into my stuff here so for my values I can just put my two values then on change I can set my two values I can have my uh default values here as well here let me see does this make sense the default values to be the two values um you know what we actually don't need the default values to be passed in here right because the default values will pass in as the two values right so let's just remove this default values let's remove it from here cool and we'll do the same for the CC values and on CC change we just set the CC values and then now we can pass in the subject and the on change to be the subject on the set subject here cool so now that we have this super kind of reusable component right let me show you we got this super reusable component right stuff is going to like kind of start showing up here so let's see all right cool so now we got this C and stuff like that and then let's come down here so right now we got this tool handle send and a sending that we're not using so let's use this tool right we can replace this placeholder I'm just going to do to. jooin by comma so imagine you have five emails I'm just going to join them by a comma separated value then whenever we set send on click right what I'm going to do is I'm going to call the handle send function passing the editor value right and I'm going to disable this button when sending is set to True right and one more thing here is whenever they click Send I'm going to just await their function right but I'm so I'm also going to call editor do commands do clear content so after they click Send I'm just going to clear out like reset the input and then send it out so hopefully this makes sense and so now we we have used all the props one more is default value two bar expanded so let's just put this as default value two bar expanded Okay cool so now we got this props working right so now we can see that obviously the stuff is failing because we did not pass in all the props into our email editor right therefore it's failing this error so let's actually do this so the for the reply box we need to pass in like we need to pass in all the props needed right as we have defined in here so who is a subject who are we sending it to who is a cc so you can see that we will need a function so that whenever we we we are on the threat we need to know who we're trying to reply to who is involved in the CC's what's the subject of the threat so let's actually create another trpc endpoint to receive the Trad ID information and return us with all this reply details right so let's come down to our account router again and let's have a function called get reply details and this is a private procedure right that takes in the account ID as well as a threat ID that I'm trying to look for the information of right and so this kind of get reply details right the first thing is we authorize the account access and then we have to find the thread in which we're trying to get the details from right so we'll get where ID equals to input. tread ID and what do we want to do we want to include the list of emails inside of it I want to order the emails by the send at to be a sending order and I'm going to only select a few field from it I'm going to select the from the to CC the send at the subject right also the BCC and lastly I need the Internet message ID so this internet message ID is needed because when we hit the oringo Epi to say hey send a message in reply to this email I need to pass in the internet message ID in which I am replying to so that oringo can properly handle the threats for us right hopefully this makes sense and now that we have this threat we're going to check so if there's no threat or the threat. emails. length equals to zero that means we have not found a threat so let's just show an error right if not we're going to find the con's last external email to be threat. emails. reverse right find I'm going to find the email. from. address does not equals to account. email address so I'm basically finding the last email that does not belong to me right so it's like the the person I'm replying to okay and then obviously if I have no last external email I'll just thr an error cool and now finally I'm just going to return a list of objects so I'm going to return return the subject to be the last external email the subject so this is the subject I'm replying to we need to return uh to who I'm sending replying to right so it's going to be last external email. from right then I'm going to spread out the last external email. to I'm going to filter the tool by whether the tool do address does not equal to my email address cuz obviously I don't want to reply to a list of emails that include my own email and I'm going to basically do the same for the CC's right right I'm going to do last external email. cc. filter out my email address and who is it sending it from so I'm sending it from my account obviously right so therefore I need to say my name is my account. name and the email sorry the address will be my account. email address and the subject will obviously be the external email. subject uh actually we have that as well already and lastly the the ID will be the internet message ID from that last external email that I'm trying to reply to so with this endpoint set up now I can finally find out who I'm trying to reply to so I'm going to get data to be the reply I'm going to rename it to reply details I'm going get I'm going to get this from my API do.get reply details do usequery and so I can get the current thread ID from use threat right and I can also get my account ID account ID so I'm going to pass these two things in right the account ID and the Trad ID so this track ID might be now so for now I'm going to put empty string in replacement of it okay and so if you come back to the reply box here so now what I'm going to do is I'm actually going to have another component called component right and so for now here I'm this is the actual component that's going to return the email editor because this reply detail might be now at first and so if there's no reply details I'm going to return now and only after we have populated that means after we have fetched down the reply details then I'm going to render this component I'm going to pass in the reply details here so obviously this component is going to get the reply details that's not going to be now of course so to get the type of the return details I can use my router outputs here I can index to account and I can index into my endpoint so I want to get the return type of this endpoint right and now you can see the type check Matrix okay and in here I'll also just copy down the track ID and account ID so I can access them okay and now finally finally I can start crafting my props that I need to pass in here right so I need my CC values my set CC values my subject my two values and everything else so to do that I'm going to construct some props subject and and set subject right it's going to be US state so this is going to be obviously the subject of the details but the thing is I to add the re colon in front of it so it should signify that I'm replying to an email so I'm just going to check first so reply details do subject so if this starts with re that means it already has a reply I don't to add another reply in front of it so if it really begins a reply I'm just going to put the subject inide if it does not start with a reply I'm going to manually add a reply in front of the subject here right and then for the two values right it's going to be basically a list of label and values where I'm just returning this right and obviously the the name might be the address the label might be kind of the label will be the address and the value will be the address as well I'm just mapping through the list of tools that I'm trying to send to right and then I'll do the same for cc values right and now I can finally pass this in here so the sub will be the subject State here right set subject and I can have the two values to be the two values and then set two values and I can have the CC values and the set CC values right and then for the two I can just map over the two and I just want to reply with a list of addresses okay and finally you know when I want to kind of I'm going to have a use effect here that basically updates my state whenever the threat ID and the reply details change so you can see in my app right you can see that we have this subject and the tool but the thing is when I'm switching my threat so I go to another threat you can see that it automatically updates the state here right even though is the same component right you can see that the the subject changes and the threat and the two values changes because I'm basically uh resetting the values whenever the threat ID changes so let's actually do that so I'm just going to check if not threat ID or not reply details I'm just going to return now I'm just do a early return okay if not I'm going to set the subject so it's going to be the same logic if reply details starts with re right I'm just going to going to uh addit here subject reply details oh my God TPT is did again right so if it's let's see reply details where is this reply reply details do subject do starts with reply wait where is my types um reply details let's see what what's the issue here uh oh sorry it should be if not try ID all not reply details so if it starts with this right else I'm just going to set the subject here directly and I'm going to set my two values to basically what we initialize here but now we're just basically resetting it every time the reply details and the Trad ID changes and so finally the email editor requires one more thing I mean two more thing is the handle send and sending so for now we just have a empty function right handle send right we're going to put in the functionality later but for now it's going to retrieve the HT HTML value from the input box and I'm just going to conso to Lo out the value for now okay and then is sending is going to be always true for now so always false for now and hook we'll hook this up to the function that is going to be sending the emails later okay so now that our email editor is set up right if you go back here to our app you can now see that I can see who is sending it to and I can see the the subject properly being uh populated right so if I go to Gmail I can see that I'm replying to this subject with the two values being set to the Gmail account cool awesome stuff so now we can also reuse this email editor later on in the compost button here okay awesome stuff awesome stuff so now what I want to work on is I want to show you how I can add the AI features here right where I can type in like uh draft and email right so I click generate I'm going to show you how to kind of add the email functionality into the auto complete box okay so let's do that in the next section okay so before we continue on building the AI features you need to have an open a API key so come down to platform. open.com sign up if you don't have an account and sign in if you already have an account and so what you need to do is just come down to the project that you're working on right you can create a project if you don't have yet and come down to the API key section and just create a new secret key in this case I'll just create a YouTube API key right and then I'm going to press create secret key so now you have this opening I key just make sure you copy it right now you can close the tab I want you to come down to your EnV folder and just put in um open aore API key it's crucial that you enter it exactly open a API key if not this will not work properly right because the libraries they'll be using to interact with open API key assumes that you have this EnV variable sets cool now that you have this you can come down to close the EnV file and so to continue we want to build this AI button on this right side here right so to do that let's actually put this where create a new component and let's create let's call it ai- compost Dash button. TSX right let's do use client TS RFC right AI compose button right and so it's going to it's going to expect two props right the first one is right whether is what I want to know whether it's composing so whether are we in the composing mode or are we justl reping people right and we we need to use this information because when we are replying people right and this kind of B will have access to the whole email thread as context but when you're composing there's no email threat in context so you don't have to pass in any context into the chatboard then I also need to have a callback function on generate right this going to take in a string a token to be a string right and void so whenever we're going to start streaming back response from opening and every time we receive a new token we're going to call this on generate callback function that we pass into this AI compost button so then we're going to take this token individual token and then we're going to append it into the editor right cool so now we can finally start making this so we need to have like this model popup thing so come down to ui. shen.com and search for dialogue and so we're just going to copy this right copy this component let's copy the import statements right and let's copy the usage right let's come down here so let's also import this AI compos button right we'll put it right beside the right under the D for cursor pointer AI compose button I will pass in the E composing to be the default two part expanded and on generate cool and let's see oh what's this error button cool so now we got this component if you come down to our app we can see that this dialogue that says open right so we want to replace this with the the chatbot button so come down to your AI compos button let's just replace this with the with a button right inside here we'll have a bot icon with a class name of size five right and this button will have a class name of size uh sorry a class name sorry not class name but size of Icon and a variance of outline cool and then we're just going to have a state that checks whether the tracks whether the dialogue is open so when you click on this button obviously I'm going to set open to be true right so now this is how it looks like and then for the dialogue content let's just replace it AI smart compost AI will help you compose your email underneath the description let's give a spacing of two and let's give it a text area let's put in a text area let's keep track of the input so what what prom we are trying to set and we'll just control it by passing the promt into the value and then we're going to have is going to have a placeholder that says enter a prompt below that let's have a spacing of two again and then we can have another button right the say generate and obviously when you press on this button right I'm going to just close the model and set the just clear out the prompt and here is where we need to kind of generate uh call the generate function Okay cool so to actually call the generate function let's create a server action right by creating action. TS file so This Server action will will will be Mark with you server So This Server action is going to be run on the server it's going to hit the opening air API and going to slowly stream in the tokens into our front end so to actually write this server action we need to install a few libraries the first thing we need to install is at ai- SDK /op aai and the next thing is we need the AI function from sorry AI library from versel so just quickly install this to package right and then we need to import a few things from here so we need to import stream text from AI we need to import the open AI uh module from s AI SDK openi and we need to import port create uh streamable value from AI react server components right so this will be create streamable value and now we can export an async function called generate email which takes in the context which is a string and a prom which is a string so this context will be a list will be a concatenation of all the emails in the Trad right that will be passed into the context of the chatbot and for now I'm going to have a a stream object first right I'm going to call create streamable value and then now I'm going to do an immediate invocation of an Asing function right I'm going to call the await stream text function from uh from the kind of AI Library I'm going to get back a text stream here inside this dream text thing I'm going to pass in the model which is open AI I'm going to pass in GPT for Turbo right you can EX around with the different models and then the prompt right The Prompt this is what I'm going to do the prompt I'm going to just copy this and you can you can play around with it uh but let's do this okay so the prom is just you're an AI email assistant inside and better in the email client app your purpose is to help the user compose emails right and then you can pass in the time you can tell you start the context block the prom is here and some additional system information cool so after we get this uh text stream object here we can then for await cons Delta of text Stream So for each individual token so each Delta is like basically one token for each token of the text stream I'm going to do stream do update Delta so basically I'm looking at this text stream and then I'm passing I'm basically for each token account I'm going to pipe it into this stream object so I hope this Mak sense let's look at what is there's an error here it seems uh are you missing a semicolon so I think there's something wrong with the syntax here oh to okay yeah I just need to add this I need to add this semic colon at the back here to make this work okay so we're going to basically get the individual tokens from the TCH stream and then we're going to update this stream value here and finally after this whole thing is is done right under this kind of for loop we're going to just call stream DOD to tell it that the stream is completed and finally outside of this async we can just return an output which is the stream. value so this will create a a kind of a text stream that we can consume from the front end right and then to do this let me show you so we can now come back to our AI compost button right and then when you call this I'm going to call I'm going to make a function called AI generate right which is going to be async that takes in uh that just basically calls the server action so let's just call AI generate right so what this is going to do is we're going to just um for now we'll just get the output equals to await generate email so we can get this gener email from the action the server action that we we wrote just now right I need to pass in the context right but for now we have no context but the promt is something we can pass in here the prompt and then for I'm going to do for a wait sorry for a weight cons token of re streamable value from AI Rea component from the output so basically I'm going to consume the stream so remember this is the output stream right the streamable value I'm going to pass this into the read streble value function from RSC and returns an async iterator right meaning that we can just get the token one by one out of the stream and what do we want to do with the token we can just call the props to on generate passing in the token right and we'll do this only if there's a token like this cool so now let's actually try this out and see if we can see happening so I'm just to console to lock out the token for now and we can see it happening in real time okay so now if I press like uh write me a poem hopefully I should see the token start streaming into my console and yeah I see it happening so you can see blah blah so let your hearts be light and free embrace the beauty all around blah blah cool so now I'm going to take this token and obviously I'm passing it into the uh props on generate right so I'm need to go into the the my email editor where I'm using this AI compos button and this UNG generate function where is conso loging on the token I'm going to basically enter it into my uh editor so there is this function right so if I go down to my editor uh let me show you so this is editor. commands I can just insert the content insert the token into the text area the content field so now hopefully I should see right if I say write write me a poem you can see generate as you see you see my poem showing up in the editor cool so Bic what it's doing is taking the token one by one and inserting it whenever this on generate gets called in this uh token function right hope this makes sense right this is super cool awesome and then let me see set open to false right let's just put this at the end okay so now we got something working right I say hi it should be able to put in the token here right nice and I'm going to I'm going pass in the open and the open State here so that whenever I kind of uh reply I should close the model as it generates out okay cool this is starting to look very nice and so the last thing here that we need for this AIA button is we need to pass in the context of the email that we're looking at right so right now we don't know uh what this email is talking about so it cannot adequately reply for us so to do that right okay so let me show you the issue that we're having right now so if you go into our studio and we go down to one of the emails uh let's actually restart our Prisma Studio here so Prisma studio right we can see the email it has a body let me show you the body where's the body is this huge HTML document and there's no way we can pass this into open Ai and expect the results to be good because it's so OB obfuscated right there's like html text there like input tag there like images links it's just so long so to do to help us basically trim it down we need another library that is able to help us take HTML code and convert it into a normal markdown which means we extract the important text information out of the HTML document and just pass that stream down version into opening a for for the email context so to do do that we have this Library called turn down right which is a HTML to markdown converter right which allows you let me let's look at the demo so you can better understand so it takes this whole mark this whole HTML and strips away all the HTML text to leave the only the important text that we can then put into opening EG so let's see let's install this library right so let's come back here we sud sudo B install turn down right and we so also need to install at types SLT turn down so we can some get some types script types right then let's come down to our lip folder and let's create a new turn down. TS file right this is where we're going to put in some configurations for the turn down okay so I'm just going to copy this in let me show you so what we're going to do is we're going to import the turn down service from turn down we're going to export this turn down so this turn down API is what we're going to be using to kind of strip the HTML out into markdown right and so if you look at how turn down is being use you basically require the turn down service you initialize a new object from it and then you can just call the turn down service on the HTML and here you can pass in more options you can add rules to kind of filter out text that you want to replace it with right so if you come down here we can see that I just basically put in some uh styles that like you know like we add a rule we remove all anchor text we remove all style text remove all scrip uh script text and we remove all image text so this just to make it so that when we pass the HTML the output will be even cleaner so now we got this turn down service right let's go back into our uh let's go back to AI compos button so here is where we need to get the context right so you see right now we're passing in empty context here so let's just initialize the context to be a string right so we'll passing the context here so how do we get the context remember we can get the list we can get the threats from use threats so we're trying to get the list of emails from the current threat right so we also need the current threat ID so we can get the threat to be threats. fine where the thread ID goes the currently selected thread ID okay and in here we want to check right if props dot is composing so obviously if I'm I'm writing the email there is no kind of there isn't necessarily any context I need to pass in here therefore I can just directly skip it but the thing is if I'm not composing that means I'm trying to reply to an threat here is where I need to compose my threat so I'm going to do my context right uh what I'm going to do is um for conss email of thread. emails right so I got this individual email I'm going to get the content sorry this will be called context and the content will be um let me show you let me show you I'm going to just concatenate this stuff so I'm going to pass in the subject to be email. subject the who is it from who is it to right I can just pass it I can join um email. to doesn't exist it's fine we just have the email from and then we can do we have the email sent at let me show let me see okay we have to send that so we can also have the send at new date do local string and then here is where we can pass in the body right so the body we can we can import the turn down service that we just has we just found so let's import that import turn down from / turn down and we can just pass in the body right if not we'll pass in the email. body snippet right if not we're just passing the mty string so basically this will strip off the HTML of the body and and put it in line here right and then finally finally we can then concatenate this context equals to plus equals to content so we're just looking through all the emails and com putting it into this context and then finally we can then uh put this context into here and one more thing is I need to tell the open who I am so I'm going to say cons uh I get my account back so I can say finally I can say contact plus equals to I I my name is account name and my email is account do address like this so they just basically appens a small the small information at the end of the context so opening ey knows that I'm emailing from Elliot so hopefully now if I go back here and let me actually show you the content so I'm going to just console to lock out the context before we actually send out the email so let's go back to our code here so for example let's look at this so this is a HTML email right so hopefully if I come down here and I'm going to make this smaller I'm going to sa reply and press generate you can see this is the context and you can see that we successfully got the context being written out here as well as have the of I have my account information being put in here so I can see this is the the context we got the subject we got the body and you can see the body now is part into the nice markdown format to be inputed into open Ai and then at the end I can say my name is Elliot and my email is this and that's how open AI gets context of the entire email threat and is able to give very accurate information when it comes to replying the email yeah so I hope you understand the reason why we're doing this right cool so now we got this kind of AI chatbot going so now let's actually finish this by writing the command J function because it's extremely similar to the AI complete feature okay so let's come back down to our email editor so you remember here we're doing meta J right so let's actually check that when I press command J I can see a it being logged out so if I press command J you can see that meta J is being logged out that means my shortcut is working cool so we will actually also come back to our action. TS this our server action right it's going to be a very similar function in fact it's actually the same but with a slight Swig so I'm going to copy this in because it's the same function I'm going to come down here right I just wrote a new function that also does create streamable value and we're also going to hit the gbt 40 sorry this gbt 40 and I'm going to say that you are a helpful AI embeded in here that is that is used to auto complete sentence blah blah blah and then we're going to stream in the tokens in the update and pass back the output so we're going to call this generate function whenever we press command J so let's come back to our email editor and then whenever we press command J let me show you here I'm going to call AI generate uh sorry I'm going to call a generate and then this function con a generate will be an asnc function and so what I'm going to do is I'm going to get the output from a weight generate so this generate from our server action and we'll pass in the the value the value which is the current uh thing that I'm typing in the the editor right because you can see the editor uh what's the editor the value is what I'm typing in here so I'm passing in the value into the context here and then what I can do is for await cons token of read streamable value so it's the exact same thing we did just now we are basically looping through each token that start streaming back in and we're just going to insert it into the command and so if we do this now if I press deer and I press command J you can see hopefully uh yeah I generate let's see why is not calling ah a generate um so let's just try console the lock out um AI generate let's see if you can see the value here first so say here oh it's still meta J let me see why uh oh maybe we need to refresh the page to update the editor so let's try again I say here Google press command J and okay I see AI generally being conso locked out but this doesn't seem to be a coming back okay so I think there's a issue with putting the editor command in this Asing function right so I know I'm getting back the tokens it's just that I need to have an external cons token set token to be a string and I'm going to do is I'm going to just set the token to be token and then outside of this function I I can just have a use effect that reacts to each token change and then I can just call this here right and I can bring this down here I'm going to reinitialize this every time the editor changes so let me walk you through what is happening okay let's just see if it works first if it doesn't work there's no point right so let's try dear Google and Okay cool so now I can see that it's working if we press command J things are Auto completing so what's happening is every time I press command J here we're going to call AI generate we're going to pass in the the the text content of the editor and then once we're in this AI generate we calling the generate server action then We're looping through each token that is streaming back and we are setting the token up here in the state and then because this use effect has the token as the dependency array it's going to detect that the token has been changed and it's going to call the editor command insert content in which it will then insert the token in here cool so so this makes sense this makes sense so now that this works and we now got the command J Auto Complete working as well as the chatboard working right so these are super cool features that will help basically you write your emails faster give you like a base template when you're actually drafting your emails yeah so cool so now that actually completed with this reply box we can then now go on to making the compost button and just putting this email in here and once we have that we can start sending out the emails okay so now let's actually make the compos email button which is this cool sliding sheet that comes from the bottom right so to do that let's just come back here to our theme toggle so we're going to put it right beside our team tole right so let's come back to our code let's come back to our page mail page right and where we put this team toggle you know what we can also put user button here so this user button comes directly from clerk so they really give us a nice user button that we can add beside it right let's actually also have a nice kind of center. Gap to going on here so you can align them side by side cool and so we're going to put our we're going to put our kind of sliding sheet up here for the Emil compost so come down to sh cnii come down to search for sheet right so you can see that this sheet that slides in from the left and from the right here right and so what I'm going to do here is I'm going to copy this uh soorry this not a sheet actually it's a drawer yeah so this drawer is what you can see you can like slide up from the bottom and from the and you can drag it down like this with the with kind of the handle so let's copy the actually let's create a new component first let's just C this compos Das button. DSX use Cent TS RFC uh actually just RFC is fine compose button so we're going to import the drawer from Shen and let's copy the definition and now let's just paste it in here to start with let's import button and let's come back here and let's just import the compos button oh not this compose button so let's save this two files so if we come back down here we should hopefully see this compost button right that we can like slide up and down cool so let's come back to our compos button right so this button let me see yeah so this button let's actually put a button that says compos here right and let's actually add a pencil icon beside it so the pencil icon will obviously come from the from Lucid react so pencil and let's give you a class name of let's see size of four and margin rate of one cool so now that we have this um what we're going to do is let's see let's see come to the draw title let's just rename it to compose email and then the description let's just remove the description and we can remove the the footer as well so we don't need a footer so under the drawer here under the drawer kind of hater we can actually directly put the email editor so remember the email editor that we have crafted we can finally put it in here and we need to pass in all these props in order for this to work right so the first thing we should pass in is that's construct cons two values right it's going to be just an empty array of labels and values and we'll have the same for cc values right and then let's have the subject and the body we can ignore but the subject we can have that so then let's pass in the two values in here and then everything else on set CC values on set two values yeah I think it's fine so just passing in on the props here and then we also need a few more things we need handle send so let's just create a fake function here for handle send first handle send uh let's see let's see yeah this a Asing function that just console locks out the value that we get back here and then we can pass in the handles s and is sending let's just set it to false for now we're going to build out the sending feature in the next section okay and then I think that's pretty much it right what else do we need we need the two so the two is just going to be the two values do map right two value it's going to be a string and then we're going to have a default two bar expanded to be true right so that we can by default when we click on this you can see that this thing is expanded right so cool now we got everything working we got the tools and the CC's and we can drop the email here right and the contact still works the smart compos still works awesome so let's actually continue writing this out um you know that's actually that's actually all that we need to have the compos button work okay that was quick because you see we we basically have a reusable component here that we can use in both the replies and also in this compost so with that I think we can start moving on to the next chapter of sending emails awesome stuff so now let's actually start sending out emails right so from to begin let's actually clear out everything I'm going to go back to our orink file right so this is where we export out all our server actions and everything related to oringo so now let's come back down here and let's actually export a function to help us send emails okay so yeah sorry we're not going to come down to orink we're going to come to account. TS so this is remember where all we can put our methods for operating on accounts so because we're going to be sending emails from accounts I think it will be suitable to put it here okay so to do this let's come down to the account and then let's actually export a new function let's create a new function to send email so we'll just Define an asyn Asing function called send email and so this email is going to take in a lot of things it's going to firstly it's going to take in a from which is going to be an email email address from our types right and then it's going to need a subject which is a string it's going to need a body which is also a string we need to see who we are replying to right we need to have we need to know the references which is just going to be a a string as well we need the tool which is a list of email addresses we need to know if there are CC's which is also a list of email addresses and bccs and also a reply to right who are we replying to cool and so now that we have this let's destructure everything here and so this function is going to basically put be in a try catch so we're going to try cont response equals to await xus do Post right we're going to do uh https api. oringo doio want/ email/ messages right and we going to pass in everything we have here so pass in the from subject body is in reply to so this will be in reply to in reply to the references the thread ID oh I think one more thing we need to do is the thread ID as well so thread ID which is a string right and it could be optional ID when you pass in the threat ID all right the tools the CC's the bcc's and the reply tool right is going to be an array but the thing is we just want one person in there so we're going to just put in the array with a single item okay and then obviously we'll just have the authorization barer token so this basically is the remember the authorization token for the individual account okay and then and then let's actually also have some params that says return IDs to be true because we want to get back the email IDs and the tread IDs after we send this request and finally we just console.log email send and we'll lock out the response of data and return it and obviously we just have some error handling if it's EXC error we'll just log out the error. response. data cool cool cool cool so now that we have this um we got this send email function let's create a let's go down to our account. TS router so this is our router and now let's create a trpc endpoint to actually call that function right so let's come down here let show let me refer to this so we need to send email let me see um okay so let's put this what issue here yeah so we need to send email route endpoint so it's going to be a private procedure and as always we're going to have the um we're going to have the account ID and we're not going to have this email stuff here because we're just going to receive everything here that we need we need the body which is a z do string we need the subject which is also a string we need a from which is the Z uh sorry yeah the from which is the email address schema right that we imported from types so remember initially we have this types uh file I did defined a z schema for you which you have which you should have really copy in here so it's going to be literally just uh email and address right and then we need to know um who are cing so it's an array of email schema we need to know who are BC seeing um we need to know who is it to right and the CC and BCC can be optional but we need to know who are sending it to right so this can be optional and then after that we can have a reply to which is also a z email address schema right we need to tell when we send out email the other person should know who they're trying to reply who they need to rep reply to and then in reply to which is a z. string can be optional and lastly most importantly we need a threat ID which is a zero string but it's also optional okay so now we can just uh let's see let's authenticate the users account goes to await await await authorize account access passing in the account and the user ID and then what we're going to to do what we're going to do is we're just going to construct a new account so cons account equals new account so remember this is the class that we constructed passing in the access token import this and on this we can call account send email right right we can do await and here we can then pass in all the emails so we can put the body the subject the from the in reply to so I think in reply to this is should be let's see what's this issue in reply to um I think here we can Define in reply to as optional here cool and then let's see we need what else we need we need references and references actually no references we don't need yeah so I think references alongside in reply to can be optional therefore have to pass this in okay and so finally we can just call this function so remember that this is a mutation so mutation is basically a function you can call to perform an action on the server right so now let's come down to our reply let's come down to our reply box so remember we kind of have a stop like a fake function for Cle send now let's actually send the email so the first thing I need to do is actually get the endpoint right so send email equals to API the account send email to use mutation so now I can call this this send email function here on the front end to actually send out the email right so the first thing I'm going to check if there's no reply details I'm go return and then finally I can do send email. mutate I can pass in the account ID I can pass in the tread ID right so the Trad ID can be tread ID or it can be undefined right and then I need the body to be the value so this value is the HTML input in the editor and then we got a subject we got a from which is reply details. from we got the tool which is reply details. to. map for each tool we need to reply a specific schema because we need the address and we also need the name right and then we'll do the same for CC's so this address might be the the name might be now therefore you put empty string in replacement and reply to will be kind of who I'm sending it from so it's reply details of from and in reply to it'll just be uh reply details. ID so this ID is the internet message ID right that I need to send in so let's come down here so this is the internet message ID that I'm replying to yeah and so if you come down here let's double check so send email in reply to so now this will be piped into this fun this procedure in reply to and then we're passing it in reply to in here and then they send email in the account will then hit the orle API to send out the emails like this cool so I think basically that is all and so if you come down to the reply box after we s after we successfully kind of send the email out on success we just want to con toast uh toast. success saying that email sent right and if there's any error we just want to conso lock out the error okay and we need this tools and in order to for the pop-up tools to show up so let me show you what tools we're talking about we're talking about this serer tools on the here so they can pop in alert so to do that we need go back to our layout we need to add a toaster here that we can import from soner so that we can display the tools properly oh my God why is it that every time I come back this layout it crashes my entire vs code that is so weird that is so weird anyways okay I'll just wait for this to finish and just reload my window okay so after adding the toaster in the layout hopefully now if I try to send an email by replying let's just reply to Elliot using here I'm going to say dear Elliot this is a test message okay and by the way here this sending is sending we can also replace it to send email. is pending so this just shows that whether the email process is happening so let's try this out I'm going to press send and hopefully we should see a success message okay cool email sent that means that the email has went through oro's API and oringo has helped us send an email on the Gmail's behalf and so if I come back to my production here and you can see if I come back to my normal email client right so this is the email I sent in reply to right I can now see that I received an email from uh my other email account here right with this with the email that I just sent to this text message right so now cool we are able to send out email messages so now let's just hook this up to the composing here so we can also do this as well so come back to your compose button right let's actually hook that up as well so we just need to import the cons send email to be API do do account do send email use mutation and this handle send we already know what we need to do so obviously if we do have account uh let's come down here we need to get the account from use threat if not account we going to return if not we're going to do uh send email. mutate and then we going passing the account ID to be account. ID right we need the threat ID so the threat ID is going to be undefined because we're not replying in respond to a threat we're just starting a new thread so theat ID can be undefined the body will be the value that we pass in from this variable here and then from we've did done this before the name will be from me account. name so I'm I'm the account holder right or me right and the address hopefully will be account do email address right or me example.com so we pretty much know that it's going to always be here so it's fine the two is going to be two values. map two we need to give back in name to. value and address to be two. value cool and then we need to do the same for CC's and then we also need to do reply to so I need to reply to to the sender which is me right and fin we need to pass in the subject and we are basically not replying to any email right because we're starting a new email threat so in reply to can be undefined and finally we can do on success and on error all right and let's just import tost cool and then now we can replace the button here uh is sending to be send email. is pending cool so now let's try this out so we come down to compost and I'm going to compost an email to Elliots using q.com let's try test email composing I'm going to send a b message dear Eliot and I'm going to send ital size hello from YouTube so I'm going to send this out and hopefully I should see a to email sent and if we come back to my normal email client I can see that my test email composing has been received on the other email but it got put to spam unfortunately but the point is that we can now send emails receive emails and kind of reply to emails cool and so now we can now get the emails but the thing is now I want to sync my emails right like you can see that I replied to this email thread but I'm not seeing my the the email that I replied to so now we're going to do that in the next section which is now setting up the syncing so remember in our excal draw that we drew just uh at the beginning of the video in the explanation so if we come back here and let's look at excal draw remember this part here right so now we have received a new email in our external inbox but the thing is it doesn't exist in our our database so what we need to now do is we need to set up a uh we need to set up a web hook within Gmail and oringo such that uh we can sync our emails to here and so what I'm going to show you is let me show you this so remember we have this datta token that we kept all along so so you come down to account right you see we have this next Delta token I can actually use this next Delta token to call my get updated emails endpoint remember this first endpoint that we were talking about here/ email/ as updated if I pass in my data token I should be able to get back the most updated emails from my account so let's try that so I'm going to go down to my cursor I'm going to come down to my playground I'm delete this I'm going to import um uh let me show you what do I mean by this okay I'm going to just do account equals new account here and I'm going to pass in the token so I'm going to copy in my access token here so this just for demonstration just to show you how it looks like right so I'm pass my token here and this account I can then call get updated emails right and I can pass in my data token to be my next Delta token here and let's see the results of this a wait so let's see the result of this if I run this so by right what I should do what I should see is my new emails being in this response so I'm going to do BU run Source playground. TS and hopefully I should see the updated emails so let's wait and boom do you see there hello from YouTube so this is the emails that I need to get right you can see that these are okay there's a big HTML payload but the point is that now you can see that uh when I I pass in the next data token and so this is the new the new token that I need to store in order to update my bookmark so remember this part here after receiving the new data token I should update my bookmark so when I pass in the bookmark again I can I will receive the updated emails so I can I should be able to I should be storing this in my after saving the the list of new emails in my database I need to then I need to then store this new data token right so now let's actually do this synchronization I'm going to write a function in here in this account right I'm going to write a function called async sync emails right so this is going to hit the updated emails I'm going to store it in my database and then update update my next data token so I'm going to come down here give me a second I'm going to look at my reference so get updated email sync emails okay so the first thing I'm going to do is I'm going to find the account that I'm looking at so await uh db. account. find unique where the access token equals to this. token right and if I have no account that means it's invalid that's through a new error right and if I don't have account. next Delta token that means something went wrong as well that means I have no bookmark so I can't sync it without a bookmark right so now what I'm going to do is I'm going to get response equals AIT this. get updated emails which is the function that we call just now in the playground right we get updated emails and we passing this data token so we going to pass the Delta token to be the account the next Delta token and so we're going to get the list of all the emails that we collect right because this Delta token right it might this response let me to you the response might have a next page token so it's basically the same process as the initial sync I need to keep checking the the updated email if they have a next page token so I have to keep going to the next page until I get to the end of the pation so the first thing I'm going to do is I'm going to let the the most updated data token the St data token to be response the next data token if there are any right uh let's see do make uh sorry account the next data token and then I'm going to look at the response so if response the next data token I'm going to set it to the put to be the store data token and I'm going to do a a for a while right W response. next page token so the same process I'm going to set the response to be await this get updated email passing in the P page token of the previous response and I'm going to take the emails to be I'm going to concatenate it with the records so I have like batches like let's say I have five pages I'm going to go through each page I'm going to concatenate the records into one final emails array and if I see any next data token I'm just going to make sure that I store it again and so finally finally what I'm going to do is I'm going to uh try catch I'm going to try to sync my emails to the database so I have this function member super useful I'm going to pass in all emails as well as my account ID right and this if there's anyr I'm console do error and then finally I can do await db. account. update what should I update I should update my next Delta token right because I need to update my bookmark and then I can just return this list if I want cool so now we got this nice utility function where we can just call on an account to make sure that we we synchronize all our current emails so I'm going to try this right so if I come to my playground instead of manually calling this I can just call A account. sync emails right and hopefully hopefully it should write to my database and I should be able to see my reply showing up here because we have successfully synced our emails so let's see what's the issue here okay it's fine so let's try this I'm going to run this I'm going to run this Soo bun run so we can see that attempting to sync four emails to the database and you can see I'm uping the emails and so if I go back here and boom now the emails are in sync and I get to see my updated message right you can see my reply right I can see the threat so everything is working basically perfectly so I can just update my emails so the thing is isn't this very Troublesome every time I want to sync my emails I have to call this function and to like get my uh to to get my emails here and so what I can do is I am I can just call this sync email function every time I fetch my emails so I'm just going to make sure okay I'm going to make sure to copy this so if I come down to my account. ts routers so every time I am fetching my get uh get threats here I'm just going to make sure I synchronize my emails so it always stay up to date so this is a polling polling infrastructure so I'm just going to have account equals to account access token and I'm just going to do account. sync emails and then I can say if there's any errors I'm going to console. error so what is this what this is going to do is every time I fetch so every time I'm on this page I fetch this it's going to attempt to kind of sync my emails you can see that attempting to sync emails to database zero that means I I don't have any new emails so what this does is every time I fetch it every time I fetch this page it's going to run my sync emails asynchronously in the background while it returns me the information and so anytime I have new emails hopefully this I can pull for new emails and then it will update my kind of database accordingly yeah so it's a super kind of convenient function that we can run now to basically make sure that we can always keep our emails updated cool that's awesome stuff so let's fix this hydration error I think I know why this is is because button eval I think it's because of our um what's it called our compos button right so let's go down our m page sorry page mail I'm going to just make this cons compos button I'm going to do the same thing I'm going to render it I'm going to disable server side rendering right so hopefully okay cool now my my hydration error is gone yeah cool so now everything should be in sync I can send emails and hopefully my emails will be up to date okay we're doing absolutely amazing and now finally we can get to the most interesting part which is the full Tex search as well as the AI retrieval augmented generation model all right so now let's move on to the full Tex search plus the AI functionality and to do the full text search we'll be using orama which is a kind of fast dependency free full Tex Vector search engine right and so we can just self Host this and just use the library out of the box right so it's like really simple to use you just like import the create their insert their remove move and the search functionality and you basically create a database with a schema and you just insert documents in here okay and so I walk you through like we're going to experiment with it and play around with it before we are comfortable with actually using it in the code so the first thing is let's go back to our vs code so obviously we need to install a few things we need to install so we need these two packages install orama orama and orama plugin data persistence so the plugin the data plug data persistence is for us to save the the basically index into our database and so that's what we're going to do we're going to come down to schema the Prisma and I've already added orama index column into our account and this is going to be a Json field so after installing this you're going to basically try to run sudo buan Prisma DB push to add the new column into our database so each account will have a list of index the orama index to store basically all the document and so let's actually play around with this by in by by importing a few things from as orama SL orama and we need the create we need to insert we need to search right and we need the type of any orama we just need this type for the use later and so I'm going to Showcase a few example right so the first thing we're going to do is let's just do uh cons DB equals to a weight uh create right and we're passing a schema and the schema have a um subject right which is a string so and then we'll have a body which is also a string we'll save the raw body right we have a from who is it from who's it to which is a array of strings what time it was sented okay and then we have embeddings but we'll save that later so the embeddings is for the embedded the retrieval augmented generation it's going to be a vector of Dimension 536 but let's ignore this for now I explain this later on and we need a tread ID as well okay and so what we're going to do is we're going to just uh let's just rename this to orama okay we need to get let's just try to get the list of emails right equals to await DB from Prisma email. find many right and I'm just going to select a few things we just need the subject we need the body we need the food is it from and and this stuff okay and for now for now I'm just going to Loop through for cons email of email I'm just going to console.log the email do subject okay and I'm going to run sudo buan run sour as playground. TS and I should see a list of kind of email subjects being logged out in my console so what I'm going to do now is for each email I'm going to insert them into this orama database okay and to do that I'm going to do a wait a wa it's insert right orama and then for the document uh is going to be let me see what should I do here um yeah I'm going to put in the email but the email is not going to be so we have to give you some type so let's see how what's the the Shipe like so let's go to the orama documentation okay let's look at some example so what they're doing is they they created a database of schema and they're inserting the DB and this is the actual stuff here so we can actually just put the first thing is we need the subject which is the email. subject we need the body which email. body uh let's see if this makes sense yep uh email let's see what issue here okay yeah just a type error I guess the from who's it from so email. from uh the address who it to email. to do join uh sorry we can just map it from two let's just get the address out and then we'll join it into a with a comma and what time you'll send that you'll be email. send that will convert it to a to local string and then obviously the thread ID will be email. thread ID right email. TR ID cool so let's just ignore the type error for now here ignore as long as you know that it fits is fine let's look at this uh why is this not working so body doesn't exist email. body we have this right email do email do subject and then the body oh because it's a comma colon em body okay yeah so we'll insert each of this document or sorry each of this email into the orama database right and for now you can see that nothing's going to happen right if I run this again uh let's see create error a schema validation because okay two because we're trying to insert an array of strings right so we don't need to join it right we can just directly send this in so let's try again and cool so we locked out each email subject and we've inserted in so now let's just try to query it okay so to query it right what you do is you can just do con result so let's search by orama okay and we'll search by the term of let's say uh class pass or or wait let's see what I have we have we have like fashion okay so if I search fashion I should be able to see uh this email right from Tommy Hill future because this is related to fashion he has some fashion in it so I'm going to then console. logout search result dot yeah I'm going to console search results so what's happening is I created a database I'm fetching all the emails for each email I'm inserting it into the orama the text engine and I'm going to search it by the term of fashion so hopefully I should be able to see a record you see I get one hit and I get the document so let's console the lot of the document so let's get the document uh the hits right let's do this so you see I get back my my document here is a little long but you can see I get back my my ID the score is how similar of it is to the term and I get the document right so what I should do is for consit of search. hits I can conso lock hit. document do subject okay so I'm going to get back the subject of everything that related and now I can see must have fashion pics and if I search for like let's say Elliot Elliot I should be able to see these are the emails I'm sending to myself and and from myself right and let's say if I search for Google if I just search goog let's see if this works and now I can see all everything is from Google so cool we now officially have a very cool uh full text engine that we can use in order to insert in the emails and search for it in real time so I'm going to convert all of this functionality into our own orama class so come down to lip and come down to Let's create orama .ts so this is going to house our our orama manager so orama client so we're just export a client and this is going to have like a few stuff we're going to have a private orama database so this is going to be a type any orama right so let's just import the type here and we'll just ignore the the TS ignore for now because I know like this going to get initialized anyways and we have a private account ID so we need to know which which which account we actually operating on right so let's do Constructor we'll take in an account ID and we'll set the account ID to to this we're going to bind it to this class or sorry to the object and then we're going to run ASN initialize right the first thing we're going to do is going to find the account right await db. account. find unique where is equal to account ID right and if this no account I'm going to show an error right and I'm going to check right uh um what I'm going to do here is let me show this if account do orama index so if I already saved this orama index to my column here if I already have like a save version I'm going to reuse the save version so therefore I'm going to set this to orama equals to await restore so I get this restore function from at orama plugin dat persistence right I'm going to restore it in the Json mode from account orama Index right and this can just be as any but the thing is if I don't have Aroma index column or if there's no value in there I'm going to create a new database so I'm going to call create insert and search I'm going to create a new database and the schema is going to be what we Define here right makes sense okay and then after we create it we're going to immediately save the index this do save sorry this do we're going to call Save index and this save index is another function that we can write here Asing save index so we're going to save the the orama database to our database so to do save index we just have the index to be await persist so this persist also comes from data persistence persist we'll just call persist we'll pass in this orama and we'll just put it in j on mode right and then what we can do is just await db. update we update the account ID and we'll save the index into our orama index Okay cool so after we we have written the function to save to our database and initialize it right we can let's write more utility functions like a search function search so it's going to take in a term which is going to be a string and so this term we're just going to return await search so search is from orama here okay and we're searching let's see we're searching by this orama and the search term is going to be whatever is passed in here cool and lastly we should have a asyn insert function here and so this is going to take in a email um actually no you'll just take in a document of any and we'll just await insert into this. orama passing the document and then we'll just await this. save index so immediately after we insert it we're going to save the index into our database and so let's actually use this orama client in action in The Playground so I can just delete a lot of this stuff here right we're going to have a cons orama equals to new orama client from our class right and we're passing the account ID so let's just take an example here here account ID to be 68589 right here and I'm going to just await await orama initialize right so this will create the index if it doesn't exist if it already exists it's going to restore it from our database so the first time is always going to create a new one and then for each email I'm going to do a wait orama client. insert and I can just insert this email here and then here I can just call orama search with a term and I can log it out so hopefully if I run this I should still be able to see the same result uh or client is not in function let's see why is not a function uh let's see orama do insert let's see a wa insert oh that's my bad so you see orama cent. insert is not a function of course because we need to be calling the insert on the orama instance itself not the class so we're calling on the instance not the actual client itself so let's clear again let's try again so I should get the same result right I get all my Gmail but this time look at this if I go into my database and I refresh I should now see a new where's my kind of uh Json index so I'm going to restart the the Prisma Studio and hopefully we can see orama index is like this big Json block that you can display but the point is that you look look at this internal document ID store so it's this huge Json block containing all our information and we can just load it in and search it up every time we need to use and so now we can actually just we don't have to insert this anymore because we can just uh remove this right so we don't have to insert it anymore we can directly initialize it and if I try to search again let me show you because we have it from safe sorry because we already have it Sav in our database if I run this again I should be able to see my alerts immediately I don't have to insert it anymore so I can search like Elliot right and this should give me the results right so because I'm reading it directly from our database this time without needing to populate it every time I need to run this cool so now we have our own kind of internal search engine happening right and so what we do is one more thing is um go to our sync to DB right so every time we are syncing an email to the database every time we add a new into email into our database we also want to uh put it into our orama Index right so that's what I'm going to do I'm going to do um cons orama equals to new orama client passing in the account ID and then I'm going to a away orama initialize and for each email I'm going to a orama insert right and what we're going to insert we're going to insert this here so let's just do this let's uncommanded out so we insert the subject the body the from the addresses to send that and then we're going to insert into our database so this way every time a new email comes in or we sing our emails right uh we're going to also write it in orama database such that now we can perform the full text sech here cool so now that we have that let's actually create the UI for the search bar all right so now let's actually work on the search bar okay so let's go back to our component where we are doing our search functionality so I'll just clear everything first come back to our mail page sorry our mail. TSX so this search bar let's actually create a new component here search dasb bar. TSX use client we have our fce right we name it to search bar let's use this component instead search bar cool so in this search bar what we're going to have is so everything's still normal uh we're going to just return a div here right let's see let's see how will be work how we do this okay uh search bar yeah okay okay here's what we're going to do we're going to have a div here the class name will be relative we'll have a input right and also have a search icon from lucd react this search icon will have absolute absolute left of two top of 2.5 size of four text muted foreground for the input um for the input we're going to have a placeholder of search dot dot dot let's give it a class name of padding left of eight right and then let's also have a search value okay but the thing is this search value search value we put it a use item so that we can because what we got to do here is let me show you uh boom boom so this search value here is as we are typing here you can see that this is a different component so we also need to have access to this component right here so we need this value here so that we can access it in in here so let's have an atom here with a string and so we have search value here to be used ASM and then in the input right here we can have a value to be search value and on change we'll just set it to e. Target value okay and then let's see I'm going to destructure um use threats right remember we have a we have a use we have a e fetching hook over here right so this e fetching is basically whether the get tras is matching so let's just display a UI here so uh what I'm going to do here is I'm going to have another diff here class name of absolute right of two top of 2.5 Flex items Dash Center gap of two and in here if it's fetching we're going to display a loader to right with a class name of size of four animate of spin text grade of 400 and then in here we have a button here so the button will have an rounded SM hover I want to have a BG gray of let's say 400 okay and then here we have a x icon from Lucy react with a class name of size of four Tex grade 400 right and then for this button when I click on it on click I'm going to clear out the search value so now if you come back here now I got this oh where's my local 3000 here okay so let's go to SL mail and so now you can see I have this thing that's showing up here and I got the the cross button right and then for this button one more thing I need to do here is R let's remove this padding okay so now it looks nicer and for the whole search bar I'm going to give it a padding of four so now it aligns better actually I need to put this padding of four margin of four here cool so I got this margin of four I can search this is updated and I can cross it out here to like clear it out okay cool stuff so now now as I as I'm typing here I want to make sure that I can see right I can see uh my message my search results being show up here so I'm going to have another state here which is the use searching State because I need to know whether I'm on I'm still searching or not so I'm going to have you is searching atom to be a Boolean atom and then here I can just have a I'm going to use atem here and so one smart thing here I'm going to do is on on blur on this input here whenever I click on this input right on Focus I'm going to set the a surging to be true okay and on blur I'm going to have a function here I'm going to call a function that says handle blur right and I'm going to say handle blur but the thing is for this handle blur I'm not necessarily always going to set searching to be false right if I have if such value equals does not equals to empty string that means I am currently searching something and I basically click out of it so imagine I'm like searching something I click out of it I don't necessarily want to disable my searching mode because maybe I just want to refer to another email right so when I focus out of it if there any value inside of it I'm not going to set it to false okay hopefully this makes sense to you it'll make more sense to actually code out the the display okay so now that we have this value here let's go into the to the threat display because this is what is being shown here right no message selected so now come down to our our threat display right the threat display and we're going to just import the is searching value from use atom is searching atom right I think we need to export this yeah let's export out the both items here so that we can go into display item and then we can import it okay and then we can also have a search value here uh actually we don't need a search value we just need to know whether it's searching or not so we're going to come down here and you see this threat thing here so if we are searching if we is searching we're going to show a UI that says searching okay but if we not searching then it makes sense for us to display the thread information here so I'll pull this thing up hope this makes sense uh here make sense so now we can see if I focus in here you can now see that it say searching right and I focus all of it I'm no longer searching but if I press here and I I have some stuff in my search value and I press all of it you can see I'm still always searching make sense only if I clear away so if I clear away I should also prbly set to be false so if I click on here I should probably set searching to be false so if I click on this and I press cross now I reset everything cool so now that we have this we can now have a we can display the information that we need to be searching for okay so come down here let's actually have a search- display. TSX so this search display is for my for this for this stuff over here okay the list of search results so for the search display I'm going to have RF FC sech display okay and I'm going to have my search value to be used atom search value atom right and I'm going to display this instead of this component here I'm going to display the search display component right and over here what I'm going to do is I'm going to just display the search value here so now if I come back here you can see that as I'm typing here on the on the right search display I can show what I'm typing here okay and so I'm going I'm going to delete this I'm going to return a diff here that says padding of four Max height right Max height I only want it to be a calculation of uh 100 VH minus 50 pixels then overflow y of scroll right right inside here I have another diff here with a class name of flex items the center gap of two margin bottom of four and I have a H2 here that have a class name of text Gray 600 text- smm when it's dark mode I want text Gray of 400 if I this H2 I can say your search for search value cut came back with dot dot dot okay so let's just replace this with quotation marks so now I can see here I can see this UI being show out here cool and now I should actually do the actual searching right so let's actually create an endpoint here under my account router let's go all way down here right let's create a search uh search emails and point right this going to be a private procedure right the input I'm need the account ID that you're trying to search for trying to search in and also need the query that you're trying to search for okay and so this is going to be a mutation right so this mutation is going to do we're going to first authorize the account access and then we're going to have a orama be a new orama client here new orama client passing in the accounts. ID and finally we can then do or. search passing in the term to be the input do query and then we can return the results hope this makes sense so we're just basically inting the orama client so what this does is it goes in there it calls oh I should call away orama initialize as well so when I call initialize it's going to find the account related to account ID then I'm going to restore the orama index if I have it in my database it's not it's create a new schema right so once I restore it in here I'm going to call or. search which as we saw just now just search just through all the emails with the query and then I'll return the results right so now if I come back here I'm going to have the search endpoint to be API do account. search emails do use mutation right and then every time my search value changes I'm going to console.log searching for search value so let's just try this first right so every time my changes here you can see that I'm searching for something but you see it doesn't make sense for us to search on every input because it's very kind of uh consuming on the back end to be spamming our endpoint so what I'm going to do is I'm going to debounce it which means that I'm only going to trigger this function every few seconds only after you stop typing for a few seconds so imagine I'm typing like Elliots like very fast after I stop typing for like say 500 milliseconds and then I'll call the search function so to do this I'm going to have another function another thing called D bounc search value and we will call use debounce value which we can import from use hook. TS and we can pass in the search value and this second argument here right you can see that it Returns the BCE version alongside a function so I'm going to only update this every only if I stop typing for 500 milliseconds and so now if I try to do debound value search instead right you can now see as I type you see alot you see nothing happens but only after I stop typing for 500 milliseconds it will display time Elliott if I stop typing for 500 milliseconds it show OT right but if I keep typing like spamming you see it doesn't show up only if I stop typing and then it will show up so now I can ensure that I will hit the API uh in an appropriate amount of um of time so let's do that so I'm going to do if debounce if not debounce search value that means Mt value I'm going to return okay and I'm going to do search. mutate that means I'm going to do search I'm going to get account ID so we already done this before the account ID we can take a from use threat threats right passing in the account ID as well as a query right and then we'll just get the the the sorry account ID out in the kind of dependency array as well so if I do have account ID I'm going to return as well do an El return and lastly let's put in the search into a dependency area so now if I am uh if I am searching right it's going to give me back the results right and I'm able to access the results of the search to the search data so I'm going to come down here okay and and I can do search. data so this data is the result after I mutated right so if search. data do hits uh hits. length equals to zero right I'm going to show I'm going to show an indication that says uh hey no result found mhm like this all right but if I do have results what I'm going to do here is I'm going to say for search. dat. hits for all the hits I'm going to map over it right for each hit I'm going to return a list item with the key being the hit. ID right and then I can have a basically let me show you what I shall do here uh the class name the list will have a class name of border round the medium pading of four when I hover over I have a badging background G of 100 cursor pointer transition that's all right and when is Dark mode and I hover over it I want to have a background of 900 okay and inside this list item I can finally have a hit tree there a class name of text base font medium right and in here can have a hit do docum title right and then have a a paragraph with the class name of text smm text grade 500 and here I can display the sorry hit do document subject and here I can show hit. document dot uh from oh sorry from like this I'll copy this down I can say who is it to so I can say two do join with a comma and finally under the p tag here I can have a class name of texts imagin of Two And this is where I want to display the raw body right so if you go down to let me see s to DB remember we also putting a raw body raw body to be email. body right uh yeah wait let me let me see okay there's one issue here that we need to resolve which is that whenever we insert this em body right uh we need to I okay so uh let's actually change one thing here that we that we're going to be adding to the orama database okay so the first thing here is let me just go back down to my Prisma Studio okay let's see here what's the issue oh yeah let's just delete this first let's go down here so I'm going to count down to my account and I'm just going to clear out the orama index first so I'll just set this to be now and save this okay so let's come back down here so I'm going to reset my orama index because one thing I need to change is okay you see this schema right I'm going to have the body I cannot be inserting the HTML body here doesn't make sense because the HTML body is like with the HTML super messy right I'm going to use the turn down service that we have just now I'm going to have the body to be turned down do turn down passing in the email body or email. body snippet right we can also import from here like this so this will convert the body into the markdown format after we clean up all the text basically and then so down here the body we can just pass in this clean body and then for the raw body we can put in the body body snipper instead right so I hope this makes sense to you so the body is the cleanup version of the HM body whereas the raw body that we're inserting is the body snippet okay and let's go back into the orama in client here to just update our schema a little as well so raw body and body string okay that's fine actually with everything here so now let's actually run playground. TS to insert it inside here and hopefully okay looks good and now let's start up our development server again okay so let's go back to our search display right we're going to be displaying the body the raw body okay so let me show you here uh search display okay for this P we're going to do dangerously set inner HTML the HTML is going to be the Dom purify which we have used before do sanitize hit do document dot raw body so this raw body is the body snippet right and then use profiles right HTML to be true okay so hopefully now we should be able to search for Stuff let's see oh something is stuck in a recursive Loop and I think I know why oh it's because we have this search here so let's remove the search I think this is what is causing the recursive Loop so uh so about death okay let's refresh so I think the reason why is because every time we change it the function was changing this search function was changing and therefore it kept spamming the end point so let's try again hopefully this will this time it will work cool so let's search Google and boom we got it back right so I search Elliot you can see things are happening right if I search YouTube like you see hello from YouTube right so if I search YouTube right it works hello from YouTube cool so now we all got the search results coming back in right so that's all perfect it working is working fine do subject let's see yeah so let's actually see why there is no subject here uh hit. doent subject uh oh it is here but we have one more oh because here the list item here right so what I'm going to do here is just remove this list description wait list none y so I remove the dots on top here cool and I'm going to add some spacing into this I'm going to wrap this in a unordered list here Ur and then I'm going to have a class name of flex flex-all gap of two so it space it out nicer and cool now we have a full full Tex engine working you see it's working perfectly and I'm going to remove this as well course if there isn't I'm going to just clear it away Elliot if I clear it out like this boom so now it's working perfectly if I search for a result like test right normal human awesome so looks to be working perfectly looks to be per working well and one more thing I'm going to update here is uh remember the syn to DB here I'm going to do the same thing for the body here right so I'm going to come down to playground and because I did the body here I'm going to come down to this so I'm going to import turn down and I'm going to replace this body with this and the raw body will be the body snier okay I think with that we have completed our full Tech search engine and now we're going to move on to basically creating our uh ask AI button which is the most exciting part of this awesome so we're not going into the final part of the application which is the uh retrial augmented generation right so you know how you can like so you can ask any question to the AI right and is able to answer with the relevant information and so how it works is here's how it works so on High level overview so imagine you have like let's say you have this bunch of emails right and then you ask a question to the AI like when is my next flight okay so obviously CH GPT doesn't have information about when your next flight is because it doesn't have access to your inbox but in this case we do have access to our inbox but the thing is we don't want to Feit in like our entire inbox and stuff all the bodies into GPT right because we're going to run off contact tokens and that's just like not efficient way of doing it so the high level overview is when you ask this when is my next flight right we're going to pull in the in the relevant information into the prompt the context window of GPT so we're going to put in like let's say this email is like a booking confirmation and then this email is like uh online checkin information right we're going to put in this two emails into our GPT context window so this like GPT right so it will have this booking confirmation email and this online checking email and then partner along with this question then CH GPT will then finally be able to give us a a relevant answer to what was in our email so the high this is a high level of RW so now how do we actually pull in the relevant information so this is where the idea of embeddings SL vectors have to come in so a vector is literally just a a line an arrow in a 2d space so imagine this a cartisian coordinate plane we can have a arrow that's pointing let's say the coordinates is 1 one so one unit to the X Direction and one unit to the Y Direction and we can have another another arrow pointing somewhere like let's say this is like 0.5 and let's say 1.9 so is 0.5 units in the X Direction and 1.9 units in the y direction so if you have two this two of these vectors in 3D space I mean in 2D space we be able to calculate how similar they are by looking at the the angle between them right so the closer the angle the more similar the vector is so imagine if we are able to make this concept but in terms of semantics meaning like text so that's what embedding means right embedding like when you say you I'm going to embed a text what you mean is you're going to take a paragraph or you take a text a sentence or something and you convert it into a vector which is just a converting text into a array of numbers right blah blah blah blah blah you can like have up to however many dimensions there are so in this case this 2D Vector will only have is a dimension of two because there only two dimensions and two numbers right but uh embedding when it comes to text it could be up to in this case 1536 Dimensions if you're using if you're using open AI or open AI Ada embedding Dimension right it goes up to 1,5 136 text embedding space so what this means is when you input a text the output Vector is going to be an arrow in the 1536 Dimension space which humans cannot understand but to computers it makes sense to them and what this embedding contains it contains the meaning of the text so imagine I have a a text that says cat and I convert this into a vector so imagine this Vector is what represents the word cat and then I can have another embedding called let's say animal right so this animal imagine the embedding Vector will represent like this so you can see it's very close together because semantically the meaning is very close together a cat is an animal so you expect the meaning to be very close together when you embedded into a high dimensional space but then if you have another word like let's say computer right a computer would not be some was not be semantically similar to it so maybe a computer's embedding Vector is like this and so imagine you have like different embedding of all different sizes right here right and so then when I ask let let me say like for example this where like uh Mouse which is also a animal right and I have another thing like dog which is also similar to animal but everything like something that's directly opposite is let's say something like pillow or something like uh AI right so it has different kind of embedding so maybe computer and AI is more similar but pillow is completely separate in terms of meaning from everything else right and so when I search for animal Vector I'm able to get back similar vectors around it by looking at the cosine similarity meaning looking at the angle in between the different uh different vectors and so then I'm able to retrieve the similar vectors to my query and then I'm able to get this uh vectors and put it into my LGBT context and so this is exactly what we're doing imagine each Vector here is uh is an email thread right so you have email thread talking about different things maybe one email threat is talking about a booking is is talking about booking confirmation a threat is talking about online checking right so let's just take this example so let's say we have this email and once we embed it it means uh booking confirmation right and we have another email that is for online checkin right right so what we're going to do is whenever we ask a question hey what is my when is my next flight we're going to put this when is my next flight into the a function called get embeddings right so this get embeddings will take in text and it will split out a vector which is an array of numbers right and so we're going to put when is my next L into this function and we'll get back a numbers array which is an embedding and with this embedding we'll put it in the 3D I mean in the in the vector space so let's say the embedding Vector is like this right we're going to then look at all the emails with the most similar vectors uh in terms of degrees like the cosine similarity and this is how we can then pull out the relevant information it's all to this uh uh this kind of retrieval embedding kind of method right so we're just going to basically put this into our code and so every time an email comes in alongside our orama storage so see orama Vector right Vector search so you see being a vector database orama allows you to perform Vector search natively so what you have to do is instead of searching for a term you need to provide a vector object to search right so you can see is exactly what it is so you give it a title and you give it an embedding which is a high dimension representation of the text and so you just have to insert everything in here and then what you get back is a index of factors and so when you ever you want to query for a new question you just embed it and then now you can search through orama for all the relevant a text so I hope this makes sense to you and now let's actually begin coding this out so the first step is first step is let us actually go to lip let's actually create a file called embedding dots right so this embedding dots is going to expose a function right it's going to export a async function called get embeddings right which takes in a text right I'm going to try catch and I'm just going to hit the open a API so the first thing I'm going to do is I'm going to install so B install open ai- H right so this will give me access to the open API I'm going to import open EI API and the configuration object from open a-h right and then I'm going to have a config to be a new configuration passing the API key to be my open a API key which I have in my EnV file right then I can actually initialize the open AI variable with the API and what I'm going to do is I'm going to get the response to be await open AI do embeddings sorry create embedding right I'm going to pass in the model to be text embedding Ada O2 right and the input is going to be the text. replace I'm going to replace all the new line characters with the empty space okay and finally we get back a a number the result to be await response. Json Json and then we can just return results. data index0 do embedding as a list of numbers and if there's any errors I'm just going to call console.log error calling open AI embedding embeding API with error I'm going to just uh throw error cool so with this we not successfully get back this function and I can actually try out so if I can if I run console. loock await get embeddings with hello world right and I can run this function run s/ liip SL embedding hopefully I get back an array of numbers and that's what I get so this is basically the 1,536 AR array I can try this out by doing uh I can just do this and let me just conso the lock out the length of this aray and I should able to see 1536 so this is a vector of 1536 dimensions and this is what we're going to be used to search up the orama index space okay so before this right let's go back to our sudo buan Prisma Studio we're going to just reset our Vector space again so I'm going to go back to account I'm going to remove orama index so I'm going to just remove this set it to now and save it because I'm going to do is going to go back to playground and I'm going to Define one more thing in my schema which is the embeddings right sorry not embeddings here I should be Define it here embeddings so I'm going to store the embeddings here and where is my okay or my client here and embeddings to be a vector of 1536 and that's why just now we have a vector of 1536 Dimensions here and then I'm going to put an embeddings and how do I get this embeddings I can do this by doing get embeddings passing in the filter out body right and I can just put it here cool and so now if I run this again if I run solo Bun Run sour as playground. TS I should be able to insert everything in here with no problem so it's basically going through this one by one wait actually no I need to stop this one sec I need to stop this okay I'm going to say console.log anding length here let's run this again to populated so now I should be able to see my emails starting to populate in one by one right so it's just going to take some time because for each email we need to populate it by getting the embedding and like storing it into our insert and then saving it um let's see and one more thing here actually we need to save it right so after we have embedded everything here we can just do a away orama index to save this properly and one thing here I can do is I can just make this a wait promise the all to make it a to make it run in parallel so that we can speed this up okay so let's just wait for Mr CLA to do his thing cool all right so let's finish this and let's run it again so it's going to hit everything and hopefully it's going to be done so if I search for my Elliot I should be able to see my results after it has saved it to the database cool and you can see after we have entered it inserted it we can see now all the all the results coming back in okay so I'm going to do the same for our syn to DB function right so I'm going to get con embedding equals to A we get embedding with the body I'm going to embed this in here okay so we insert the embeddings in here as well cool and so now let us go into our orama let's create another function called async Vector search that takes in uh a search term right so what we going to do here is oh we're going to embed the search term and then we're going to do the we're going to search it within orama so uh here's what I'm going to do I'm going to do cons embeddings equals to get embeddings by the search so this is like me searching for uh when is my next flight so hopefully the flight the emails that is related to flights will be pulled out right and say cons results equals to await search this orama right I'm going to search by the hybrid mode right so hybrid mode means I'm searching for both the text and also the the vector I can say the term is a prompt sorry the term is the term okay and then the vector Vector the value will be the embeddings and then the property we are searching on is embedding so this name has to match this embeddings schema and then the similarity let's do 0.8 and then the limit let's just limit to 10 results at a time right and then we can finally just return the results cool so hopefully if I come down here and instead of entering in inserting all this here let me comment this out I should be able to search I should be able to do a Factor search by let's say uh Google and I still I should still be able to get back interesting data hopefully yeah so now I should be able get back information about Google right and hope and obviously like because I got some sensitive information I I don't want to show what my real inbox looks like that's why we're going to deal with some junk data right but hopefully if you have a real inbox with real data when you search for when is my next flight it will be able to pull down all the emails within your orama database that is related to flight information yeah so now that we have this search index we can actually build this UI out right so let's do that so let's come down to our um let's come down to our local host and let's finally come down to the ask AI part okay ask AI part so let's come down to our code let's come down to mail ask AI so let's just replace this with a real component called ask ai. TSX is client rce ask AI so let's import ask AI in here save this okay and then in here we're going to what we're going to do is um uh okay here's what I'm going to do I'm going to return the first thing I'm going to return here is a div here uh actually know ask eii will receive a prop of whether it's collapsed right because this is in the Side Bar right so if it's collapsed here I want to just hide this so a class name of padding of four margin bottom of 14 okay so if it's collapse first in the first place let's just return now okay and let's ask AI so now we have created a spacing here so it's floating up here instead and then we want to have this oh uh let's see here so we want to have this Premium plan Banner for our stripe Integrations but we'll work on that later but let's first set work on this AI thing here okay so I am going to have a so okay one more thing you want to notice here is if I say hello you can see there's a smooth animation that's that basically floats up whenever uh whenever kind of we enter the email right so to make that animation work what we need to do is to have frame or motion so we have that so let's import motion from frame or motion cool right and then we'll have a motion. div in here with the class name of um Flex Flex One Flex Flex One Flex Das call right items that's end ping bottom of four rounder of large background gray of 100 Shadow inner when it's dark I'm going to have background gray of 900 cool and then in here I'm going to have another diff here the class name of Max height of 50 view height overflow y of scroll with a full Flex Flex call Gap two ID of message container okay so this is the message container I'm going to have an animate presentence which is from frame or motion and I'm going to have a mode of wait okay and then I'm going to have a list of messages right but for now I'm going to have a con messages uh messages is going to be empty array right so I'm just going to Loop through messages messages map for each message right it's going to be any for now but it's fine uh I'm going to return a motion. div the key is message. ID uh let's see implicitly let's just do any for now the map uh will this work let's just restart this messages. map for each message I'm going to return a motion with a key of message ID okay so we'll get the messages later when we're interacting with the chatbot but for now I'm going to have a layout of position and then in here um let's see we have a class name so we're going to have a CN right so we're going to have a z index of 10 margin of two Max width of 20 50 pixels mhm break that words rounded that's 2 XL BG G of 200 when it's dark mode I have BG grade of 800 right and then I'm going to have a conditional I'm going to align it to the n and have a text grade of 900 right this will be if the message. row equals to user so you can see here oh let's refresh this oh that's weird so you can see if I'm is a user message I'm to push it to the end and if it's a assistant message I'm going to push it to the start and it's going to be blue color so that's what I'm going I'm doing here so if is a AI message I'm going to do self start right I'm going to have BG blue of 500 so the blue background text of white and this is if the message. row equals to assistant cool and then I have a layout ID to be message uh ID okay uh no actually I can have container Dash message. ID like this and then uh I can have a transition object so this transition object will be the type of e out and a duration of 0.2 cool so this just for the animation right and then inside this motion div right this is where we can actually put our message. content with the class name of padding X of three padding y of two text of 15 pixels and leading of 15 pixels cool and so let's see if this works um yeah should be fine so if we come back here we got not this background but the thing is we don't have any messages so actually let's let's link up the message but before that let's have the form input here right so underneath here let me check so motion div that should be fine so outside of this anime presence but inside the motion div let's have a new div here with a class name of with full so this is where we're going to put our text input to submit right so so inside this with full we'll have a form okay the form will have a class of with full and flex okay and then in here we're going to have a normal input with a type of text okay and then we'll have a very long class name of padding y of one relative height of nine Place holder I don't have a text of 13 pixels Flex grow okay rounded full border border gray of 200 background of white padding X of three right we want to have text 15 pixels and then outline of none okay and then will have yeah I think that's okay let's see how it looks like cool that looks fine and then let's see let's see yeah then let's have the button beside it so beside this input we're going to have wait sorry we're going to have a placeholder that says ask Ai and then under the input we have another motion. div and then the the key will be messages. length Okay and then yeah interesting layout layout equals to position and then we have a layout ID to be um so the layout ID has to match exactly what we put here here mage length and actually change here this should be messages. length uh minus one yeah this makes sense messages. length uh okay so once we have this layout ID um let's have a transition to be the same here and then initial initi show so this just for animation guys you don't have to follow this if you just want to skip the animation that index of negative one and the same thing for the animate property okay and exit with oppos of of one and Z index of one cool and finally inside this motion div here we can have a class name of padding X of three padding y of two text of 15 pixels leading of 15 pixels text grade of 900 dark texture of 100 and here's where we put our input right and we'll put we'll have the input variable here later on but then finally outside this motion div let's commment this out first this button with a type of submit class name of margin L of two Flex size of nine items Das Center justify the center rounded full BG G of 200 when is Dark BG G of 800 and in here we have a send button from Lucid react with a class name of size of four text grade of 500 when it's dark text of 300 so let's see how it looks like cool it looks starting to look good right we just need to have the email content on sorry the messages content up there so how do we actually get the messages right the messages come from this library from the the AI library that we installed just now right from AI react and we have a hook called use chat right and so we can actually destructure from use chat so use chat will basically allow us to interact with our our API and handles everything with the streaming handles everything with the input Etc I'll show you what it means but for this use chat I'm going to pass in an API to be/ API chat so later on we need to make this API npoint in order for this library to interact with the open API right and then we have a body when to pass in the account ID which we can destructure right cons account ID equals to us Str because we need to pass in to our our endpoint so that we know which orama database index to be looking for right because if we are on this account we don't want to be searching through emails on other accounts and then on error right error let just console do lock error for now cool and then finally finally initial messages initial messages going to be empt array and here we can then get our input our handle input change our handle submit and the messages array so this is where we're getting our messages we can now finally fill up the information so uh this is where the input lives and here for the form on submit we can do handle submit and for this input we can have the value and on change we have to handle input change and all this being controlled by this Ed chats okay so cool so one more thing I to add here is like this by default messages you got this nice UI here I'm going to just add this real quick so uh come down here sorry to ask AI so come down here inside this uh above this withd full so if messages. length is greater than zero I want to have a div with the class name of height four just a spacer diff right and then come down into the WID F and if I have messages of length of zero that means it's empty I'm going to display that UI okay I'm going to display a div with class name of flex with full okay and then uh sorry not classing of with f so this is margin bottom of four okay and then inside here with another uh let's see what's the issue here oh we have a missing diff here cool so then this inner diff with a classing of flex items that Center get a four okay and then in here we have a sparkles icon from Lucy react with a class name of size of six text Gray of 600 okay inside here we have a div what's in Auto completing that's weird okay anyways we'll have a thing with a P tag text Gray of 6 900 dark text Gray of 100 it says ask AI anything about your emails okay and with a P tag here it's actually the same thing but here uh no actually no it's different text gr of 500 text- XS text when it's dark I want have a text G of 400 I say gets answers about your emails to your questions about your emails cool and underneath this D we have a height of two that's a spacer and then finally we have a div with flex items the center right gap of two Flex of rep and then in here is where we'll put in our badges so the first one is spend and let's just do class name of padding X of two padding y of one BG gr of 800 text gr of 200 round round the medium text Xs and here I'll say what can I ask and then so now I should be able to see this okay and there's obviously a buck here so let's take a look at issue y okay so we have this div so one mistake is you see this div this from height to should be outside here okay now it looks right now it looks right okay cool um and then when I click on this span what do I want to do when I click on this span I'm going to do handle sorry handle input change right I'm going to pass in Target value to be what can I ask like this so when I click on this you can see that it populates it like this nicely cool okay obviously there likebox with the styling but let's fix it now so one more thing here is on this outer div here padding after padding of four we need add padding of four here so it looks more right cool right so let's see why this is expanding unnecessarily um input so let's come down to motion div here right so this motion div that's housing this enimy we need to give it a class name of pointer events none absolute z- 10 Flex height of N9 width of 250 pixels items that sensor right overflow hidden break dasw rounded full background gr of 200 right we have word- break colon break-word and when it's dark mode BG gr of 800 so hopefully this looks more right cool so you can now see you see you see this cute animation B okay but something is falling obviously we don't have the end point to actually handle it that's why it's breaking but now at least we got something working so I'm going to copy this down this B A few more times right and I can see when is my next flight and the last one is like when is my next meeting cool so now we got this nice bat UI that's the same here okay and for the placeholder let's change it here cool so now we got something more similar to our production app okay so now when I enter it when I enter information what it's going to do here you can see that it's going to try to hit an end point it's going to try to hit my/ API chat endpoint as we Define here right so let's actually create the endpoint say apss chat route. TS so let's go down to route. TS under the chat endpoint okay here and so this will correspond to/ API SL chat okay and so it is we need to import a few things really so import configuration from open AI that's it import a few things from the AI module we need the message we need to open AI stream sorry open AI stream streaming text response so don't worry about it being decator it still works it's just that I prefer this style okay okay and then let's actually export a a sync function called post taking a request okay we're going to do a try cat block okay in here so the first thing I'm going to do is I'm going to get out the user ID await off from clerk obviously if I know have no user ID I'm going to just say it's unauthorized if now I'm going to get back the uh kind of messages and account ID from request Json so this messages is something that the use chat hook will always give to us right and then what I'm going to do is I'm going to get the orama to be new or Rama client so I'm going to search for the relevant emails right I'm going to pass in the account ID okay and then I'm going to await orama do initialize cool and I'm going to get the last message so the last message is the message that I'm going to send in to this endpoint right so for now I'm just going to conso the lock last message I'm going to return a new response like this so hopefully I should be able to see this last me message being sent over so now if I enter like high test message and I press submit I should to see post ra my index you see post uh ignore this but the point is I get to see my my post request being sent through here okay and there's only wrong with the kind of indexing so let's fix that orama index doesn't exist let's look at the issue together so it's saying that unknown argument orama index so let's go down to orama to save index is saying that orama index doesn't exist I wonder why that is the case orama index okay I know why maybe you just need to restart our server okay so whenever you need to you add a new column to Prisma sometimes you need to restart the the nextg server to make this work okay so I'm going to refresh this the page and hopefully I I should say hi test I should be able to see my last message right I get to see my role and I get to see the content cool so now we verify that we are getting the last message Here and Now what I'm going to do is I'm going to try to get the contact to be await orama do search Vector search and the term is going to be a last message do content okay and then I'm going to just control. loock context. hits. length hits pH so this will show us how many relevant emails we found okay and then finally I'm going to have a prompt which I'm going to copy in here in here which you can copy from the repository but just long but it's just the point is that it's a assistant embedded in here your purpose is to help the user uh please keep in mind to be respectful blah blah blah and I'm basically just putting in the list of hits into the document into the context prom here okay so after with this prom I can then do cons response equals to await open AI chat sorry create openi dot uh where is openi so I need to do cons openi itals to new new configuration right open ey. chat create chat completions passing the model I'm going to do gbt 3.5 turbo okay let's see uh create open check completions model 3.5 D Tero cool messages so the messages is going to be the prompt that passed in and the list of messages right where the message message. row equals to user so this message is of type message right so basically what I'm saying is I'm going to pass in the whole context I'm going to pass in all my emails where I am the one who sent it so I don't to pass in the assistant email back into itself right I'm going to say stream equal to true so I'm going to stream back the response right and finally I'm going to say con stream equals to open AI stream with the response here okay and then I'm going to say on start actually no it's fine um yeah so the the power is that whenever we start a function when we start a stream I get to say stream stter and on on kind of completion I get to to lock it here as well like this so this just to kind of have a pass in a callback function here cool and with this we can return a new streaming text response with a stream right and so hopefully if this endpoint works I should be able to get back the response from B so I say hello let's see hey look it works so it starts the streaming back and let me show you which should able to see the console happening here so let me exit this out so I'm going to say uh tell me about Google emails so you see 70 hits found so say seven hits found then stream started and the stream completed right tell me go emails so it basically found all my emails related to Google and it I put in the context and responded so I can say tell me about Elliot's emails so I should be to see that nine hits found that means he found nine emails related to Elliot and then you can see that based on provide context OT has received security alerts blah blah blah yeah so now we can see that AI has context about entire email inbox and so I've already tried this on my other emails where I have kind of flight information and meeting information it works flawlessly is able to kind of extract out uh my information and respond to me accurately so I found this to be super helpful and so I I I want to push you to kind of try on your own email inbox to see if you also find it effective on your email inbox but cool so now we basically have built out the entire system we got our search functionality right we got the search functionality working we got our kind of uh AI Rec model working right so I think we're doing really really well and so I think we're done and the last part now is to kind of link up the stripe account so can we start we can start processing payments Etc and so I'm going to teach you everything there is to know about strip I'll see you in the next part cool we're officially basically done with the app like I'm super happy of what we have accomplished so far and now let's actually teach you how to add stripe into this so you can start charging users and make money out of this software as an as a a service application okay so here's the kind of high level architecture of what we want to do with our services so by default we want users to only be able to have so we have a free tier so you have a free tier you can only connect connect One account and you can only send 15 messages to the to the AI to 15 messages AI per day right and so but the thing is if you subscribed to the pro tier right you get to connect to up to three accounts connect three accounts and then you can get up to you have unlimited AI messages per day right so this is kind of the structure we're going for right so we're going to basically add kind of guard rails to help protect this right and so the first uh thing here is let's actually install the stripe API so Sudan install stripe okay so we're going to initialize strip here and we have this super simple way of doing it so let's come down to to our lip folder lip Okay stripe. TS and I'm going to import Stripe from stripe okay I'm going to export a con stripe object equals a new stripe okay pass in process. EnV do stripe Secrets key as string right and we have the API version to be W version you see in here so I think it's going to change for you guys but I'm doing 2024 June 20 June 20th okay and how do we actually get this stripe secret key so I want to come down to your stripe.com here and you're going to try to register for an account or sign in if you already have account so go to Dash Okay so once you have loged into your strap dashboard what we going to do is come down here to your and create a new account right you can name me anything you want name normal human- YouTube choose your country of operation all right and then you're going to basic wait for it to load and now you in test mode so let's go into developers and we should be able to see our API keys and so this is our our secret key so let's save this so what I'm going to do is I'm going to come down here I'm going to put stripe uh secret key so never expose this to to public I'll delete this afterwards okay and then come down to stripe publishable key should be this like this okay so once you have this you should now have your stripe secret key okay so leave the dashboard open because we're going to come back here for the web hooks okay so now that you have this stripe secret what we're going to do here is we're going to then uh start creating some stripe actions to actually trigger the the checkout portal okay um so the first thing here is okay let's come down the dashboard to the STP dashboard come down to your product catalog okay you going to add your product and here we're going to call it normal human Pro right or what you going to name it so here is like the description unlimited access to AI messages okay and you can just charge it whever you want I'm going to charge let's say 15 bucks I'm going to build it monthly right I'm going to press at produ so now that we have this we can now go in here and let's go into the price let's go into here so we're trying to get a price ID so this price ID is super important for us so make sure to note it down here okay come down to create a new file called stripe Das action. TS and put use server so we're going to export a bunch of server actions from this stpe action okay the first thing here is I'm going to export Asing function create check out session okay and so I'm going to get the user ID from o from next J from clerk I mean and if I do have user ID I know you're unauthorized right I'm going to show a new error saying that you're unauthorized and I'm going to create a session which is a wait stripe which is we initialize from our F just now trap. checkout. sessions. Crea we want the payment method sorry payments method types to be array with just card right and then line items we want to have the price right the price which is whatever you whatever you have the price ID there and then the quantity is one so one of this item the mode is subscription and then the success URL is let's define I think we have the next public URL right next public URL do we have that okay we do have it yes so you'll be process. env. nexu URL right and just come back to mail so after they successfully paid we want to redirect them back to the mail page right and the cancel URL let's just go back to mail as well cool and we need a client reference ID so later on how this work is okay let me just show you how this works oh okay like this Cal draw okay so here's how the strap API works so we have this is our app this application our next JS server we have the stripe uh stripe server here okay so what we're going to do here is whenever they click create checkout session we're basically basically hitting strip's endpoint and stripe will return us with a with a URL check out URL so this checkout URL here we're going to then redirect our users to the checkout URL so here this is the create um checkout session function that we're calling here so create checkout session will return us with a checkout URL and then we're going to redirect our user this is imagine this this John John will be redirected to Stripes page which is the checkout URL okay and then after John has finished right he will be redirected here so you see based on this success URL he'll be redirected to our slma URL but the thing is stripe is going to start sending us web hooks right and remember the concept of web hooks right so stripe okay let me just copy down here strip is going to start sending us web Hooks and where we can Define it we can Define our web hook to be like SL API stripe web hook cool like this and so it will send us back the web hook and it will send us different events so we'll be mainly listening to invoice. payment. succeeded which means that uh John has successfully paid and we'll listen for uh subscription. updated so this is whenever he updated update subscription so we're going to listen for these two events and then we're going to basically write a record into our datab pleas noting down that John has successfully paid us and we're going to therefore upgrade his account okay so this is the high level overview right so John creates his checkout sections SE sessions right and then he would deal with the the processing in stripe and then stripe is going to then send us uh event saying that John has completed his transaction okay so what we're going to do here so at the end of this we're going to basically redirect the user to session. URL as string so this redirect we can get it from next SL navigation cool and so we can call this create checkout session so let's actually do the UI for this so let us come back to Local Host let's have the stripe banner up here okay so let's have the stripe button uh St button here let's go back to our code let's create a new component in mail called strip button. TSX use client RFC so the STP button um will Lally just be a button component okay and then uh let's see let's see it has a variant of outline and then a size of large on click let's create checkout session so cons create checkout session equals to await create checkout session sorry uh handle click so we can import this from our stripe actions right so we get back we just basically get back uh it will just automatically redirect us there okay and so if we now come up to our come up to our ask AI here right let's actually create that Banner right you see that premium Banner let's create that premium Banner so let's create a new component called premium Das banner. TSX RFC premium Banner let's import it just right above here and let's have a height of four here to space it out let's save this so I should be able to now see a premium Banner here and this premium Banner is going to have just a few things right so let's just return this um so we're going to render different things whether they subscrib or not so we're going to have a e subscrib variable right by default it's going to be false right but this is thata we're going to pull from the database to check whether they're subscribe but for now let's just go to be false so uh yeah so if not subscribed I'm going to return a different UI so I'm going to return a div that a class name of background of 900 relative padding of four rounded large border overflow Das hidden Flex Flex call medium flex medium flex Das row right gap of four okay and in here I'm going to have a image right so this is going to be the image of the bot oh my God here this bot and I'm just download this bot I'm going to just inspect this okay and I'm just going to can I just open new tab cool and I'm just going to save the image in my downloads and then I'm going to just copy this into my public directory let's name it let's rename it to be bot. webp and here we have the source source equals to/ bot. webp okay and then this image will have a class name of medium will be absolute when is medium screen we have a negative bottom of six in here when is medium with have a negative R of 10 height of 180 pixels and then width of Auto so we should be able to see this B happening here but we do have any text to fill up the height so let's put some text here so we a div here and with have a hit one that says um sorry inside this D we have a um do Flex item SL Center G of two well in here we have a H1 with a class name of text white text XL fonts both that's says upgr that says uh basic plan right and we have a paragraph that says text grade of 400 text- smm right when it's medium I a Max width of I need to calculate C 100% wait so actually no that is just that is just full Max with full yeah and then what we're going to do here what we're going to do here is we're going to put the remaining credits right so imagine we have a remaining con remaining credits of five right and let's say we have let's actually create another for file in Source called constants TTS so this constants dots will export a few things so export cons free credits per day equals to 15 so 15 messages a day and then export con free accounts per user equals to one and finally export cons Pro accounts per user equals to three so this is what we describe right if you're a free user you only get to connect One account and you only get 15 messages right so we're going to have this variables we're going to use throughout so let's come down here we're going to have we're going to display the uh we're going to show that you have remaining credits out of the free credits per day okay cool messages remaining so let's save this and let's see how it looks like okay cool something is starting to happen right like this okay so now let's actually add more styling um let's see let's see let's see let's see and then in here under this paragraph let's have a spacing of four and have a paragraph tag of class name of text Gray 400 text justs medium on to Max width of I need to dra a calculation of 100% minus 150 pixels inside I can see upgrade to Pro to ask as many questions as you want cool and let's see why is it why am I in this uh sorry yeah this should be outside of this div okay now it makes more sense and then let's see here um yeah after this paragraph tag we have a height of four and this is where we put our stripe button okay and this is our stripe button basically cool so let's go into our strip button and let's actually customize this okay strip button let's give it a text of of so if you subscribe where variable cons is subscribed by default it's false but if it's subscribed we're going to allow them to manage the subscription if not we're going to ask them to upgrade so now if I press this button I should be redirected to the stripe checkout page and I do and I get to see my super human normal human Pro to be $15 and I can obviously pay okay and so after I finish paying what strap is going to to send me some web hooks telling me that I have successfully paid so I need to be able to handle that uh um I need to handle this web hook right so before that let's actually go into our schema because we need to keep track of who paid and keep track of their payments right of their subscription so let's actually have a model model called stripe subscription right we have the ID okay where is linked to a user right let's have a Creator ad is linked to a user and we will have a customer ID so this is a stripe customer ID we have a subscription ID we have a price ID and we have a current period end right so this tells us when the subscription ends so that we can check based on this to see if they still subscribe or not and we have just updated ad here so you can see a user right now should be just linked to one so let's just do subscribe subscription so that is singular and to do that right we need to go in here and we need to make this unique like this and save it so now there are one to one relation so each user will only have at most one subscription one stripe subscription in the table so let's do Prisma DB push to actually push this new column this new table up cool so now Al let's also restart our database to make sure that our changes Tak Okay cool so now what I want to do what want you to do is um let's see um come down to our stripe and let us go to developers let's go to web hooks okay and now let's actually test in a local environment okay so the first thing is you got to download the command line interface right so if you're on if you're in different OS you can try different sty but I already have it installed so I just need to run strip login right so let's open my terminal here I'm going run strip login so I need to just open this URL to confirm okay so here is where we decide where we want to do the web right so remember in the drawing if I can find the drawing let's see yeah so it's saying that we can we can Define the web here so this is where we going to Define it so in this this case I'm going to do I want to put it at/ API strip web hook okay so it's going to listen for my web hook here and on to note down this web hook signing secret so I'm come to my EnV come down here I'm going to have strap web hook secret I'm going to paste this in cool so now if I go back to my stripe dashboard it should be listening stuff and here is where I can trigger events right so in this case I'm going to set up a new endpoint so go under your app SL API so create a stripe SL web hook SL roots. TS so this is where we have export uh Asing post function post right and then basically this web PO is where we receive stripe stuff okay and so to do this we have to actually make sure that the the web pool is coming from strap and not from anyone else so we need to verify the stripe web hook signature so the first thing we need to do is we'll do uh request we we'll get the body to be await request. text and we going to get the signature which is request do sorry we going to get the haers from nextjs to get stripe signature right and this is going to be a string and we have a event to be stripe do event okay and then we're going to try catch right so we're going to try to get the event to be stripe. web hooks stripe which import from our own Library the web hooks do construct events right passing in the body passing in the signature and we also need to pass in the process. EnV stripw hook secret that we have here right so this stripw hook secret comes from the C in our testing environment cool and so if there is no event or there's an error we're going to return a new respon saying that there whitebook error because probably they F the signature okay and then we're going to we're going to conso we're going to construct the session to be event. dat object as stripe. checkout. session okay and we can console. loock uh receive stripe event right and we can get the session do events sorry we are able to get the event. type and let's return a new response saying 200 and we can actually try this out if you look into our console hopefully oh one more thing is we need to make sure that the strap web is a public route right so after this if you come back here and I can just manually trigger a let's say I can trigger invoice. payment succeeder if I press I can just trigger this copy this right and I'm going to write track trigger I should be able to see you can see you see this 200 100 request I can say received stripe event invoice. finalize invoice. item. created so I can start receiving this events that strip is going to send me right you can see event payment method attach invoice Creator blah blah blah so this is the power of strip right it will send me events based on the life cycle of the users payments okay so we're going to just look out for a few events right the main three events right so we're going to just look at the main events right if event. type equals to checkout. session. completed that means they have complet a session right we'll do something right else else let's see else if event. type equals to invoice. payment succeeded right we also do something else um let's see and then and then the last thing is wait and the last thing we're checking for is if event. type equals to customer do subscription. updated right we're also going to listen to the event so let's just start with the first event which is check out session completed okay the first thing is we're going to get the subscription equals await stripe do subscription do retrieve they retrieve the subsection the subscription s string okay and we're going to expand Out items. dat. price. product okay and then we're going to check if not session do client reference ID so remember we passed in this client reference ID when we did the strap action we're passing the user ID here so now we can finally see which user ID we are actually receiving the event for okay so if I have no ST session client reference ID that means I don't know who is actually subscribed here I'm just going to return a new response saying that I don't know who it is okay if no I'm going to find the plan which is the subscription do items uh. data index zero right do price here okay and then I'm going to say if there's no plan I'm going to return new response again right if not I'm going to get the product ID equals to plan. product as strip. product I'm going to get the ID of it and then if there's no product ID obviously something filled again and then finally finally we know that he has succeeded so we're going to just await DB do stripe subscription so we're going to then write create a r in our database do create right the first thing we know that is the data is the user ID is the session the client reference ID we know the product ID to be this we can the price ID to be plan. ID so let's see ID product do I not have product ID well I actually don't need it anyways so the price ID with have the customer ID to be the subscription. customer we got the current period n which is new date and we get the subscription. current period and multiply by 1,000 right and then let me see do we need anything else we need the subscription ID to be subscription. ID I think with that we have everything we need cool so now this should create a subscription with us and let's go to the next event so if invoice payment succeeded it'll be super similar right so I'm just going to copy in the code because it's basically the same function right oh after this I need to return a new responsing that received right so if payment succeeded I'm just going to retrieve the subscription exactly what I did above here I'm going I'm getting the plan I'm getting the product ID and I'm going to update the strip subscription where the ID is here and to do this you can see it's got to true and error right because I didn't say that the subscription ID is unique which it should be so I should just do this so Prisma DB push so what I'm saying is that the the subscription ID in the table should be unique and that's why if I do this now the type error will go away because I can then uniquely identify the subscription ID I can just delete the product idid right and then I can can return a new response saying that I've received it and last but not least is the customer subscription updated so if it updated I'm literally going to do the same thing which is just get the subscription right St the subscription to retrieve I'm going to update the session ID uh sorry upgrate the subscription ID with the current period end so this current period end is basically saying where the when the subscription is ending so every time they update or the subscription updates they pay new round the current period end is going to be pushed back by one month so imagine today is September right the the current period end will probably be sometime in October and then when they reach October and they their sub renews then this end point is going to be hit again and now the current period m is going to be November so they're always going to be one month ahead and that's how we're going to check whether their subscription is active so now that we are we are able to receive this web hooks right let us then come down here okay so hopefully what's going to happen is once I press this button to upgrade my plan I'm going to go to my checkout session page right so this exactly the diagram that I'm drawing here so I'm going to go to my session checkout page and after entering the test information 424242 here right and press subscribe it's going to lead me back to a mail page but hopefully my browser as should be able to see here you can see boom do you see this okay there's some errors stripee not found but but the thing is I can see my charge exceeder customer Creator subscription Creator blah blah blah right and so with this if I come down my database right my my Prisma studio right I should be able to see a new subscription being created and I see it so subsp subscription I can see it creat for this user right which is me obviously I can see my customer ID my subscription ID and the price ID okay and I should be able also able to see when it currently ends so you can see that it's ending basically 1 month from now which is October 26 now is September 26 right so the subscription ends in one month and that's what we're going to be checking for to see whether they currently actively subscribed okay and so with this information with this information let's deal with this error with saying that uh because inde dependent a mod that was required but not found record to update was not found so it's okay because what I'm going to do is let's go down to the stripe uh web hook API so over here we're updating right so before that we're going to first check uh first check it so got existing existing subscription right so if there's no existing subscription it's okay we can just say it's fine subscription not found we just return to 100 it's going be okay so with that we can now have a subscription so now we're going to basically allow them to check right because now it's still saying upgrade plan even though I already upgraded my plan so how do I actually check whether I'm subscribed so I'm going to come down to my stripe action here I'm going to export an action to check whether I am subscribed right so I'm going to export a asnc function get subscription status right I'm going to get the user ID from o if I'm not if I'm not authenticated obviously I'm not going to be subscribed I'm going to return false false and I'm going to find the subscription so I'm going to await DB do strap subscription I'm going to find where my user id here is so I'm going to find the corresponding subscription Row in my database and if I have no subscri subscription obviously I am not subscribed but if I am I'm going to check whether the subscription current period end is greater than the new date right so it's saying that uh is the is the current period n bigger than when I am now because if it is that means that the ending is in the future which means they are still subscribed but if this ever becomes false that means they stop came the subscription that means the current period end is no longer in the in the future right that means they're not currently subscribed anymore cool so with this get subscription status I can come back to my premium Banner so you can see where we are seeing subscribe is false right let's make it a uh hook right let's delete this and then whenever this component first mounts what I'm going to do is I'm going to just get the subscription status to be uh let's do this async I'm going to invoke this so I'm going to call subscription St equals to await get subscription status so I'm going to invoke This Server action so this going to return me a Boolean and then I can set the subscribe to be the state of that so then I can check if it's not subscribed I return this and then I can check that if it subscribed I can return it subscribed and so now you can see I am successfully subscrib so that's amazing so now let's actually write the the UI for whether is subscribed so let's see so if it subscribed it's going to be basically the same UI right so I'm going to just copy this down right the only difference is going to say like this is the premium plan right and then here I can say ask as many questions as you want exclamation mod cool and then uh what I can do here is I just delete here because this it should be down here and this can be just gone actually no let's go back here let's just delete this let's see how it looks like on my production so we can see ask as many questions as you want this belongs down here so just remove this ask as many questions oh cool but let's just give it a difference so instead of 150 pixels let's just 70 pixels okay yeah that looks more like it so now this is me being subscribed and obviously if I'm subscribed I don't want them to upgrade a plan what I want them to do is to come down here and I want them to create their I want to lead them to the uh building portal where they can manage our subscriptions right so I'm going to just export another another function where we says create building portal session right here I'm going to get the user ID con user ID equal to off obviously if I'm not no user ID I'm going to return unauthorized but if not I'm going to say I'm going to check do I do they have a subscription right so if they do have subscription say we say it's not found because if no subscription what are you going to try to manage right so then we're going to create a building portal session create right customer passing in the customer ID right and then we'll return them to/ URL to SL mail and then we'll just redirect them to the session. URL okay and so inside the strap button here right I can do the same thing for the premium Banner so let's just copy this right get subscription status so if it if that subscribe I'm going to manage subscription if not right so I'm going to say if it subscribed I'm going to create billing P section but then if they're not subscribed I'm going to create checkout session just like what they did just now so now you can see instead of asking me to subscribe it's asking me to manage my subscription and if I press this button it's something is going to error out but it's a easy fix but it's expected so it's going to error out it's saying that if you're in a test yeah in a test environment you can't create a portal session until you uh save your cut dashboard setting so I'm going to go in here I'm going to press activate test link okay and with that if I go back here again and test it again I should be then able to be brought to the building portal where I can then cancel my subscription Etc cool so now that we have the premium CL and the premium CL settled right let's actually just delete it first so let me just delete the row because I want to show you how we're going to deal with uh the situation where they're not subscribed so if I refresh again now it's going to say upgrade plan okay so how do we actually manage the five out 15 messages right the first thing is let's actually handle the situation where they have no account right and they're trying to add more than one account okay so let's go back so down into our code right account let's see account searching um um let me see let me see let's go into oreno right so oreno dots is where we are help helping them create a new account right so the thing is now we're going to ver check right we're going to get this is subscrib so cons is subscribed it goes to await get subscription status right so if so we're going to find the list of all the accounts it have so if they're trying to get more than one account and they're not subscribed we're going to just show an error so if a wait get uh sorry await CDX DB sorry db. account do count where user ID equals to user ID uh let's see like this right and then I'm going to say if is subscribed right and if the accounts is bigger or equals to the pro sorry if it's bigger than Pro accounts per user that means there are more than three right I'm going to through a new error saying that uh you have reached the maximum limit accounts right like this uh sorry you have reached the you have reached the maximum limits right else so if they're not subscrib if the account is greater than the fre accounts per user we're going to throw an error as well okay like this and so now you can see I'm a free account I'm a free user but I already have one account right so if I press add account is a true and error right saying that you have reached the maximum number of accounts okay and I'm going to handle this error as well right so if you come down to account switcher all right we can see that we try to add the account here so I'm going to put a try catch block try catch block so if there's an error I'm going to check if error. message equals to you have reached the maximum number I'm going to just do toast. error actually you know I can do I can just toast the I can just toast a message right error. message so let's try this if I press this you can see you have reached the maximum number right I'm going to move my face out oh not like that you can see you have reached the maximum number of accounts for your subscription okay and if I upgrade my plan obviously I can then subscribe to it because I am within the account limits okay so now that is handle right we have block off the account switching so now let's actually go into the messages right every time they kind of interact with this chatboard I want to basically minus uh their credits away right you can see five out of 15 messages but this five is obviously fake data so to keep track of that the last thing I need to do is come down to my schema I'm going to create a new uh I'm going to create a new model uh let's see uh chatbot interaction okay I'm going to create a new table called chatbot interaction to keep track of how many times they have message the chatboard so obviously we have ID we're going to keep track of which day they message the chatboard and how many uh messages they have already sent so by default there's only one whenever it gets created there's only one and then we have linked to the user ID so it's link to a user okay and we want to make the unique index the day p with the user ID and let's T it as well like this like this and let's go to the user let's make the user a unique view here so that we can have a oneto one mapping of the chatboard interaction like this cool so now let's P pris TB push this up okay so now what this means is okay let's restart server as well so now each user is linked to a chatboard interaction and now this chatboard interaction keeps track of the day in which we send a message as well as how many messages we have sent that day okay and with that we can come down to our chat route so remember this is the route that handles whenever you press you try to interact with this chat Bo right so the first thing I'm going to do here is I'm going to check whether they subscribe cons is subscrib it goes to await um get subscription status right and then I'm going to check if it's not subscribed what am I going to do I'm going to find the chat boo interaction ital a db. chbo interaction. find unique okay by the way we should we should Define what today is Right cons today equals to a new date dot to date string right and then we can find the unique where the day is today right and the user ID is the user ID here cool and so if there is no chat interaction let's actually create one for them so let's create a CH interaction for them where is today's day there's one user and there's exactly one message because when they hit this Endo that means they message once right so they have at least one chatboard so and then what we're going to do is um let's see let's see so if they are if they are not subscribed we're going to check this and then uh we're going to else if that means they have the chatbot interaction right else if chatbot interaction. count that means they have message more than the free credits per day right we're going to then return a new response saying that you have reached a free limit today and we have the the status of 4 to 9 which means rate limit right so if they are obviously subscribed right this won't run right and obviously if you are subscribed you can have unlimited access we won't check for anything right so with this we have finished checking like this and what I'm going to do is I'm going to come down here and I'm going to try to send a message I'm going to say test right it's going to reply and if I come back down to my Prisma studio and see do I see my chatbot of course not C STP sorry Prisma is just so annoying okay so I should be able to see my uh chatboard interaction I should see one row right I should see today's day the count which means I interacted once and the user ID and if I try again so I say test again right oh I need to inter increment it again so you can to see that obviously it's not going to increment it right so to increment it I'm going to come down to my um on completion so on completion is after it has finished streaming right so which is perfect because then we can hear we can get we can just await dp. chbo interaction. updates where today's day and the user ID and can just increment this by one so now let's try this again if I say test again right and it finish sub finish generating I should now see my count increase to two right so I have two messages cool and with this we can now get like their rate limit uh rate limiting so what I'm going to do is let me see come down here uh let's see let's see so I'm going to come down to my router my account. ts router I'm going to get the kind of chatboard interaction data so get chatboard interaction right so it's a private procedure I'm trying to get the account ID I'm going to authorize them I'm going to get today's dat right I'm going to find where today's dat is and the user ID and lastly I'm going to get the con's remaining credits equals to the free credits per day minus the chatboard count or zero so imagine like the free credits is always going to be 15 right and let's say I have interacted it with it for five times so my remaining credits is going to be 10 right so I'm going to return this remaining credits here remaining credits okay and then in the front end here to get this premium Banner let's go back to the premium Banner to get the data of the remaining to get the data we can do API do do account do get chatboard interaction. use Query passing in the account ID which we can get from use threats right and then we can finally here come down here and remove the heart coding so instead of here the remaining credit we have data do remaining credits left so now I can see if I refresh okay cool you can see I have 13 or 15 messages remaining right so what I'm going to do is I'm just going to once they ask it so C I can remove this hard coding and so if I come down to the chat bot uh chat wait sorry the the what's this ask AI interface right I'm going to come down to the message right here use chats blah blah blah so on finish right I'm going to try to refret the information because after you finish it I want to update to I want to refresh it to see that the number went down so I have a utils equals to API do use utils right so this will give me the ability to do u. account. getet chatboard interaction. refetch so this will fetch the data and hopefully I should see updated so let's try this so if I say hello so hopefully if everything goes right I should be able to see this becomes 12 so see hello and now you can see boom 12 remaining so it's working perfectly and let's actually try it out so let's go to my constants and if I say the free credits is four okay four wait yeah three um let's refresh this so you can see I have zero to zero out of three messages remaining if I try to send a message I'm going to get an error I'm going to get an error saying is 429 right so if I see that console let's try again test you can see error you have reached a free limit for today so I can actually listen for that so I say if error dot um cost dot do I have cost actually no let's see you have the free limit so I'm going to check uh I'm just going to toast do error error. message like this cool so now if I if I exceed the limit as you see hey you have reached a threee limit for today cool cuz I have Z of three remaining so this constant is like super convenient because you can just literally um you can just uh set the limit however you want and people will just kind of uh use the free credits remaining oh my God okay cool we have completed stripe integration and the payment gating Gateway and we can finally deploy this we are almost at the end all right let's do it okay now let's deploy this so it's actually not too much of trouble because we have already deployed this before right like initially when we did the clerk so that's actually really good because now all we have to do is come down to our settings and just fill up all our kind of and environment variables and then it should work hopefully let's pray so I'm going to come down to our EnV okay I'm just going to copy everything here okay let's let's check what is actually missing so we got database URL click okay so we only have the database URL and the click right so we can just copy everything here right we'll copy in here okay and then we should be able to save this and and it should work okay so we have successfully saved it right and now I'm sure there's going to be some other errors but if you just Commit This and push this up it should deploy and everything should now work fine and dandy uh let me think yeah I think that is actually all that it is right okay let's just pray and let's just try out if it works okay so let's wait for it to deploy so you can see has it's it's building now and it we should be back okay so it has deployed so now if you go down to normal human- yt. for.app we you see something happening okay so now let's talk into our uh account and let's pray that things are working and not breaking okay let's let's like pray that it that works okay so we're still on the landing page with there's nothing but if you go to SL mail right we should hopefully see some stuff cool okay so let's select account right let's wait for for it to load I know why it's loading so slow 100% I know why because you need to go into our account go to your settings go to your functions and you need to select a region that closest your database in which case I'm in Singapore so I need to save this and let's just put a function Max duration of 300 and save this as well and let's go back and redeploy this sorry and let's redeploy okay so let me tell you what I just did there so uh here the functions right so the reason why the functions is this is the region on which the serous functions will execute in by default so this is basically where your app is being served out from default and so it should be close to your database because let me show you why so imagine this is the world okay my geography is bad but deal with me okay so I know Singapore is somewhere here okay and I know the US is somewhere here okay so my database right on neon DB is situated in Singapore right but the thing is just now my versel was situated in us east1 so us east1 is somewhere here okay so every time someone makes a request the datab the the the server will have to make a request to my database here fetch the data and then finally return the response and you see this whole big round trip this is what was making the app super slow and so all I had to do was I had come down here I changed the function execution region to Singapore so now now my server is near my database and so now you can see my database and my server will communicate basically almost instantly so that's why it will make the app faster and for this function Max duration cuz I just want to make sure that my servers are not my my functions are not timing out right so um yeah this should be good cool cool cool cool so now let's just wait for it to redeploy deployment okay it's ready now let's check it out again sorry not normal human it should be this right ver. app let's come down here let's go to/ mail okay I should select an account that belongs to me so let's see oh you know what you know why there's no account you know why there's no accounts I know why because I was not loog in with the email I was loog into to the other email so I'm going to sign out actually no this is a perfect way of of of displaying yeah it's fine so I'm going to log into uh my Google account this right sorry sorry Google account okay so I'm going to log into this and you can see that I have no basically no accounts link here okay and so what what I'm going to do is I'm going to test out the the email linking stuff so I'm I'm going to press add account and I should be able to be here I'm going to link this account so it's saying it hasn't verified my app but it's okay continue and so hopefully if I go back into my deployment I see my logs and I see um my live data hopefully I should still I should start seeing like email popping in right let's see normal human let's go down to deployments oh I know why because we don't have next public oh we do oh yeah but you see my next public I set to look go 3,000 okay that's silly of me so this should be really htps uh normal human- yt. verel app let's save this and finally if we redeploy this this should work okay so now it has deployed right so let's try again so I'm going to just add an account right let's try again let's add this account cool and hopefully it will redirect us to normal human-y youtube.app SL mail okay it does it does that's good and I can see I have an account linked and if I go into my lock box I should start seeing emails kind of start streaming in one by one right where I'm writing to the database right so let's see okay see cool we got it attempting to snc 23 emails and we can see that it's starting to upsert emails one by one right cool so let's do one by one let's wait for it and then in no time in no time we're going to see yeah see our emails are starting to come in that means the syncing works right we can see the the emails are working we can see that the the kind of access grun was also showed up in this email right I can also basically say um hello and hopefully you respond to me yay so I opening airworks as well so everything is perfect oh my God first try deploy actually not first try but yeah that's cool let's see if this works reply and yay awesome awesome awesome stuff so we can see our email are able to be sync properly one by one that's cool that's cool that's cool and everything seems to be working perfectly nice nice nice awesome um let's see that is super cool I'm going to say when is my next flight it's going to say that I don't have a flight cool I'm down to 12 tokens now okay that's amazing guys so I think this basically concludes the video okay awesome guys so I am so proud of you to have finished this video with me I have no idea how long it is I've been filming for 2 days I'm guessing it's probably like at least 6 7 hours right but you have basically built out an entire kind of email clone with Shen xgs you have learned t uh the TR T3 stack trpc you have learned a completely new API oringo you have learned how to do proper database management with web hooks you have done full Tech search with the open source library right you have learned how to do Rec you have basically done co-pilot you have learned how to do strip and you have done learned how to deploy this to a production app on ver sale so I'm super proud of you guys for completing this with me and so I hope you have enjoyed the tutorial and if you actually learned something I hope you can give it a thumbs up and subscribe and share this video with anyone you think that will benefit from this video and as always I'm Elliot and peace outin this full stack tutorial you'll learn how to build and deploy a feature-packed AI powered email client using NEX js14 Elliot Chong will teach you how to create everything from scratch integrating powerful tools like open AI API for Aid driven email composition and stripe for seamless Payment Processing along the way you'll learn to style with Tailwind CSS leverage the nextjs app router and use Prisma for database management by the end you'll have a fully functional email client deployed to vercel ready for real world use hey what's popping I'm Elliot a software engineer from Singapore and today I am so absolutely excited to Showcase what I have kind of built for the past few days which is a minimalistic AI powered email clients built on shet CN and open Ai and nextjs and all the good stuff right so I took heavy inspiration from two two ideas one is one is superhuman which is another kind of minimalistic lightweight email client right that has like shortcuts you get to have command bars and like it's a fully functional email client right so I I wanted like experience like that uh to replace my current Gmail client and I took the UI inspiration from the homepage of shet cn's kind of uh email showcase which is this right so I just took the UI and just put in the real data which is the email client data right so let me showcase what I've built so let's press get started here and it's going to lead us to the log in page right so then we're using clck you can log in with any kind of method I'm going to log in to my Google account here and once you log in you get to be in the email dashboard and you can see I already linked three of my email personal email accounts to this kind of platform this normal human platform right so the first thing I want to showcase is this a fully functional email client right so I can see that um I receive an email from my other email account called aliot atuse incubate tocom and what I'm going to do is I'm going to reply to him I'm going to say U dear other Elliot right and this is a fully featured like text editor I can say like I can bold it I say um Love from YouTube exclamation mark right I'm going to say best Elliot right I'm going to press say send right so it's going to ship out this email and so there's a lot of stuff happening on the background Shing out to a dedicated email server and it's going to Ping a web hook to kind of synchronize all the emails and in a few seconds you can see that we have received this email and in my sent inbox I can also see uh the email that has been sent out actually no it's just in this tread so all the emails are being logically kind of separated into threads right so a thread is just a collection of email messages right so I can see that I send this out to to add in incubate and if I go to my superum app right I can see that I received this email from my other email account official not @gmail.com right so we can see that it's fully featured like email client and there's also this chatbot thing right where we can kind of it will take context of all the information here I can say draft a reply uh to thank him for help me for this demo so I'm going press generate and open is going to use this right to automat generate this stuff I mean it's a little janky like but it's usable it's usable and to Showcase it let's say we want to reply to this email uh actually this email maybe I can press this I can say reply to this email right so this will take in the context of the entire email threat and generate me a nice email for the person and I can obviously press send to send this out but I'm going just ignore that for a while okay and obviously it has a fully featured CC and kind of tool format right and you can edit the edit the headings okay but that's not the coolest part right so we have established that it's a fully featured working email client right but the most important part and most magical part is that we have build an entire rack model which stands for retrieval augmented generation model into the email client so so I can ask the AI anything I want about my email inbox and is able to reply to me with the context so I can ask like tell me about the email from insta field. I'm going to send this over and hopefully so you can see the email you receive is from insi AI announcing a new update to that they've made to their service right so it probably put in this email so how this works is when you ask a question to this chatbot it's going to find all the relevant emails uh that might CH your question then we take all the email contacts and we put it into the prompt of open Ai and then we send it out and then it will give us back a response so that's what retrieval augmentation retrieval augmented generation means right so like that is cool right I can ask more questions like these are useful because I can ask like when is my next flight right in this case like because I don't have any flight information emails here it's going to say it doesn't have enough information but imagine that I sync my entire inbox here I'm going to have like my flat confirmation emails in here and this AI is going to be able to tell me when my next flight is without me having to go dig through my email right so other than this retal augmented generation we have a full Tex search so you can have shortcut so I press I'm pressing slash and I can search um aliot and so this will give me a full text search of my entire inbox I can search for insta field insta field let's see this right it's going to find the insta fill I can search for like let's say class pass it's going to find all the class pass related stuff here right so the the full Tex search is incredibly fast and Incredibly versatile right and the whole thing is kind of command based right so I can navigate around using J and K I can select highlight email tracks here all using my keyboards if you see at the bottom left corner and I can press kind of D to mark it as done right I can select like like all the items here I can press D this will mark it as done which then will be transferred into the done folder right you can see that these are the done emails that I just kind of cleared away and if I want to ever move it back into my main inbox right I can just select everything again I press U for undone undo and it's going to basically move it back into the inbox right let's just wait for it to refresh and yeah all my emails are then basically back into my inbox right so I'm going to just clear out some emails like death I have read right so these are cool um and by the way there's also another AI feature in this email box uh if I start typing like dear insta field right I can say like hello if I press command J you can see there AI auto complete so I'm going to press command J right see it's going to automatically complete my next sentence for me so this super helpful if you're like typing some stuff and you need to quickly just ask AI to complete your sentence this like a GitHub co-pilot kind of experience you can just press command J I can say like best best regards right I'm going to press Comm J hopefully okay maybe that's a little weird but yeah the point is that there are some like AI features here where you could help you automate and make it make your email experience much faster and cleaner and I mentioned that that is light mode and dark mode so there's like light mode dark mode here you can toggle it everything is responsive and when you press command K you see this I have never seen someone teach this on YouTube yet I'm going to teach you all of this today so when you press command K we're going to bring a command bar right here is where you can go to your different sections so you can see I have a draft page I got a send inbox right so I can quickly navigate around by using command K and typing inbox it goes my inbox I go to drafts it's going to lead me to draft I go to send it's going to lead me to send and if I want to go to the done I can search for my done emails so this will switch me to the done tab I can see my pending email so switch me back to the main inbox so everything super fast and if I want to like switch my email accounts I can quickly do that by doing command K I can search for my Gmail account right so then I can quickly switch to my Gmail account as well as my other like incubates email so everything is streamlined on the keyboard for developers by developers right and of course you can compose emails right and you can press command C to quickly bring up the compost box and this is basically the same stuff you can search through all the email recipients that you want to send to cc's work subjects work and you can also just say quickly draft in a apology email to John so let's see what it does um yeah I mean that's a good start I guess and I'm going to take this and can just continue writing my email yeah so you can then obviously send this message out right so this is a lot of stuff that I want to teach you today and I'm going to teach you everything here today and this is a fully featured SS application what this means is that you can receive payments so you can manage subscriptions right so people can kind of pay for the service to upgrade it so by default by default if you do not have a pro version of this you are only limited to have one email account connected and you can only have a maximum of 15 AI messages to be sent um every day so then once you kind of allow users to upgrade by paying $1 you can then uh make the account kind of the pro version which then unlocks all the features of normal human yeah so I'm going to teach you the entire strap integration for this as well so I think without further Ado let's actually get started there a lot to learn a lot of apis to learn today so let us get rolling before we continue on in the video I would like to share a short story about myself for context I started to learn how to code about 3 years ago when I began I learned the full stack M so that was mongodb Express react and not GS I started to build a lot of this kind of small projects with the mertech slowly expanding my tool set I started to build my own authentication library to be reused with all my personal projects then slowly I added more email modules then I learned about stripe and I was starting to able to charge people for the S products that I built and in the past year or so I've actually been working at a YC back startup where I was the founding engineer who led the entire full stack infrastructure and seeing that startup grow from zero with the stack that I used there it really showed me that this stack has been proven to be scalable developer friendly and blazingly fast and on top of that you know from my channel I love building and trying out new set ideas so I've been building this products for years and I keep running into the same problem every time I start a new project at waste weeks setting up the same infrastructure authentication database file storage payments you name it and I thought there just got to be a better way and that's why I created this start s project is the starter kit I wish I had when getting into the game we're talking about the full stack nextg setup postgress SQL with Prisma we got the all solutions S3 file storage stripe Integrations all the essential ials to get your SS off the ground fast now I'm not going to you right this is not magic you still got to put in the work to build your actual product but start says this I hope that it can handle all the boring infrastructure stuff so you can focus on what matters most to you which is the unique ideas that you're bringing into the world I plan on building a community around this idea where I'll be posting updates blogs and newsletters about all the experiences I have learning over time where I continue building out this s projects and I've got over 1.5 th000 over stars on my GitHub projects and even some YC startups are building with it why because it works it's battle tested and it scals if you're building aess and you're tired of Reinventing the wheel do check out start says it's going to be a culmination of everything that I have learned failing building succeeding and scaling so the link is in the description and I'll be having a Discord Community where if you're interested in about building s or you just have questions about the videos that you're seeing do come join us in this community we I'll be there to answer questions all right enough about that let's dive into today's topic on building an email client okay so before I get started I'm going to tell you what you're going to be exactly learning today so by the way the text T here we're using a lot of different apis today so we're using clerk for authentication nextjs as our full framework Prisma as our object relational mapper so this kind of helps us interact with our database we're using neon DB as our database provider so neon DB is a kind of cloud provider that gives us uh serverless postgress database uh that they will handle it for us right we're using tawin CSS and shed CN for styling right so it's a component Library we're using trpc so trpc is a kind of um type safe a way to do type saave apis so imagine you have the traditional rest apis where you us like post get requests to another server right this is an alternative to rest which allows you to interact with your front end and your backend through a type typescript based type save uh API layer so I'm going to explain more about this uh during the tutorial so just going to give you a quick recap okay then postgress postgress SQL that's the database that we'll be using obviously we'll have stripe integration to receive payments and upgrade version upgrade users to the pro versions uh we're using typescript of course we're going to be deploying our stuff on verell we're using open AI for all the open AI features like the AI sending AI composing features and these are the two very important kind of apis that we're going to be building on top so orama is a fast dependency free full text and Vector search engine so this is what we're going to be using to to do our full text search so what it does is it allows you to store documents inside the database and then allows you to kind of operate on it by searching through it by filtering and stuff like that so you can see that how it works is you create a database schema right with the different uh Fields you can also have embedding field so we're going to be using this embedding field for retrieval augmented generation later and then what you can do is you can just insert documents into the database right so imagine this is we're going to be inserting the emails right like the the the subject the body the who is it from who is it to Etc and after it has been reinserted you can then search through it uh using like just a term and what it's going to do is going to pull down all the relevant documents that matches the future so that is how we're going to kind of uh fulfill the full Tex search right so this is using orama okay so other than orama this oringo is the main API for today because oringo is is what is powering our inbox API so it's like a very pretty Niche API that I found but what it does is allows you to have a unified email API so whether people are trying to connect that Gmail they're trying to connect their Outlook account right what oreno does is it gives us a centralized API for us to operate and sync our inboxes and send out emails right so you can see that it allows developers to quickly build Integrations with many mailbox providers like Google Office 36 five Outlook Etc and I'm going to go a lot more in depth into all of the apis in this oringo later right it's super powerful you can like receive emails you can send out emails you can sync inboxes Etc so I'm going to be teaching you all of that in this tutorial right and so this is kind of the main text stch that you'll be learning today it is more intermediate in terms of skill level so I expect you to have kind of a general knowledge of how to use next GS react you you have to understand and uh the data flow from database to the back end to the front end to be able to kind of follow along so if you're a beginner I have a lot of other beginner tutorials uh like a chat PDF SS product that you can watch to kind of catch up with your skills before coming back to this video yep okay so what you learn is how to build a Rec Pipeline with a custom checkboard so Rec as I mentioned is retrieval augmented generation which means you have a database of documents and when you ask a question the the chat boo is going to search through all the relevant documents and then put the context into the chatbot before kind of getting the response back so this allows for a very powerful uh kind of chatbot inter interaction because a chatbot will have then custom knowledge about your entire email inbox we'll have full Tex search with orama we will build a full email client clone right so it's basically a Gmail clone right where we will sync in the emails from your current account we'll be sending and receiving emails we'll be composing emails replies Etc and we'll teach you how to make an AI smart compos feature right like a email writing co-pilot which I've shown you just now I'll teach you how to uh add a command bar feature to your user interface and you can use this anywhere in your app in your other full STP projects which makes it so much more professional once people see that you have built a command bar uh feature right and lastly we'll teach you how to set up strike payment to receive money for people to kind of upgrade the account to use more of your inbox features yep and so I'm going to break it down into mainly 12 parts right so that I can set some expectations for you so the first section of this video is going to be really understanding the email client functionality with or rinkle right so I'm going to break down everything you need to know about how the email API Works how do you sync the emails what's the difference between an email and a thread the different terminologies used when creating an email client right and then the second step is we're going to set up the the B barebone structure with nextjs SH CN Clerk and setting up the datab base for ourselves and then we're going to dive into the oreno API to start receiving and syncing emails from your current Gmail inboxes orever inboxes you have and then we'll also do some data based engineering and web hook management so this is important because when you're trying to sync your email with Gmail when Gmail sends you a message you need to be able to receive the notification and basically sync their servers data with our our database so that we can always have up-to-date version of the email inboxes okay so the first kind of first half of this tutorial is very back and heavy so I hope you deal with me I'm going to be drawing out everything so you have a perfect understanding uh and before you actually code it out okay and we're going to hook up the full text search with orama and then finally we're going to start kind of building out the initial UI to display the email and threats we're going to show you how to build the search UI and then we can start doing some interesting stuff with AI by building the rec Pipeline with Q&A using theel AI SDK and then we'll go into setting out replies and composing and sending out emails alongside the email co-pilot and then the final step is setting up Stripe Right so that you can start receiving payments I'll teach you how to set up subscriptions and all that good stuff and then we can then deploy to verell and I'm going to show you how to quickly build a landing page to start uh showing people your awesome project right so there's a lot of stuff to be to be done today uh it's a very backend heavy very kind of data heavy tutorial but if you are able to build this out you will really truly understand how email clients work how o off Works how a lot of these backend processes are are kind of the you can see the data flow from all these different data providers into your centralized UI platform okay so without further Ado let us jump right right into understanding how an email client really functions okay so this is going to be the hardest part to understand of the whole tutorial so if you listen closely and you truly understand this part of me explaining how the email client API works you'll be good for the rest of the video okay so let me going to first introduce you to the problem of white oringo exist so imagine if we don't have a centralized API and we want to kind of directly read emails and write emails on behalf of of another person's email account so let's say we have a Gmail inbox and this Gmail has two accounts right so John has a Gmail account and Jane also has a Gmail account and in this case like let's say Andy has an Outlook account so imagine like we want to build an email client email client so what what what would we try to do we're going to basically try to access right we're going to try to access and be able to read SL WR to John at gmail account gmail.com so we need access to their account in order to kind of read all their emails to kind of put into our own database so that we can display in a custom UI and then when they they want to try to send an email from our UI we also need to be able to have access like a right permissions to their John gmail.com in order to send out emails and so imagine what you have to do is you have to do the same for all the different Gmail accounts and all the different providers and the different different providers are like Gmail Outlook Hotmail whatever other inboxes there are right and the thing is Gmail has a set of different API so Gmail uh Gmail API so Gmail has a set of apis where they have different specifications where you can read and write but then the thing is Outlook has another set of apis that completely different from Gmail and their data their schema is different how they represent that their emails are different how they represent their threats are different so imagine having to write and kind of write all the code in order to integrate with uh not just Gmail but also Outlook right it's going to be Troublesome and Gmail is like super hard to work with their apis right because they have like weird kind of syntax so this this is the reason why a service like Orco exist Oro basically kind of imagine it as like a central proxy that sits between the Gmail API and the Outlook API so when when you want to say I want to send an email all you have to do is you tell oringo hey I'm going to issue you a command like api. orn.com mail send right I'm going to send I'm going to ask to send an email with I want to send it from this email called John gmail.com and I want to send this with the subject of test and the body of test right so then what is going to happen is I send this post request to oreno oreno will then detect that I want to send Using John's Gmail account then Oro on the back end like they will handle it they will basically uh use Gmail's API to help you orchestrate the entire process and send it out uh basically using John's Gmail account so you don't have to kind of think about Gmail you don't have to think about outload all you have to do is think about oringo so they kind of abstracted away this entire API service for reading sending and composing emails all that good stuff including authorizing access because when John wants to give us access right we have to basically John will have to give us consent so I'm sure you have seen the Google consent screen right it looks something like this right son so John will have to say like hey uh oringo wants to access your Google account uh will you do you want to allow them to read and write emails for you so in this case John is going to press yes because they want to use our email client so then basically John give gives oringo sorry John gives oringo permission to use their inbox to send and read emails and then we use oringo to access John's inbox so I hope you understand why now oringo is good and why oringo exists right so then we will our application will then interact with oringo to basically authorize and save Account Details save user details and basically read and write the emails and threats okay so now let's actually walk through the entire the initial process of John like see John he's in our application I'm going to show you step by step how John will give us access to his Gmail account and how we're going to use or rinkle to kind of read and sync his initial messages in the inbox okay so uh let's let's just clarify some terms right when I say when I say uh inbox right what I mean is the messages within a certain email account right so an email account is just uh uh how do you put an email account I guess an email account is just associated with an email address so imagine John has email address as John gmail.com so this will be considered one G one email account right and the inbox refers to the entire kind of messages slash threats within the inbox account the within the account okay and so what do I mean by messages and threats so um message is fundamentally an email message so email message is like when you send out email that's like a atomic like a singular form right but then you know how you can like reply to emails and like forward emails right so when you do that all these different emails is going to be put together inside one thread so we group them together and when you have a group of emails that are related to each other we call them a thread so if you go into let me show you for example this so this you can see that there is two emails two email messages because there the first one and the second one but the reason why they are in one threat is because I replied to the first email with this other email therefore Gmail is I mean what mail service is able to kind of identify they're related to each other and put them in One Singular threat okay so that's what the email message and threat means and that's what inbox means and an account is just an email address associated with it okay so the first step John is going to come to our application and it's going to try to Grant access to oringo so he's going to press a button on our screen that is that is going to show the content screen and saying like hey John do are you sure you want to give access to oringo to read your inbo okay so for example here when I'm in my inbox I'm going to PR add account so this add account is like me trying to give access right so I'm going to press add account and so what it's going to do is going to lead me to the uh consent screen so it says choose an account continue to or so this is the exact like kind of permission that Gmail is asking hey Elliots are you sure you want to give or rinkle access to your Gmail account so in this case I say yes I'm going to press this and it says okay it says haven't verified this app but it's because like we're in development mode so it say sign to oringo right it says by continuing Google share my name press continue so you can see oringo if you if I give permission to Oro is saying that hey oringo will have the ability to read compose send and delete email from Gmail and if I am willing to do that I'm going to press continue and in this way right oringo is then going to have access to my Gmail inbox right so this part so the first step here is granting access to oreno then oringo will send a request to Gmail and Gmail is going to ask hey are you sure you to confirm so let's say John confirms by pressing yes please authorize Oro to have access to my inbox right then Oro is going to say that hey Elliots uh uh John has give us permission to use his inbox so then step two Oro is going to send us back a authorization token so remember this word token he's going to send us back a token alongside the users's email information so then oringo we can set like a call back endpoint so let's set it at/ API oringo callback we're going to receive the information we're going to receive the token we're going to receive the email of John right and then we're going to take this information and we're going to step three we're going to save it to the database so what this means is we're going to have account table so remember an account is just a a kind of it's just a information related to one email address right so we'll have an account ID which is like identifier from oringo site they're going to give an account ID and they're going to give us this authentication token so this off token is important because we need this token to be able to make requests on behalf of of the user okay and we're going to obviously save the email address as well in the in the accounts row okay so after saving step four is we're going to get the email right because look at this this is Gmail server right that's this John's inbox right so imagine this his inbox inbox and John has like 12 emails sitting there and what we want to do now is trying to get like read this 12 inbox and attempt to save it in our emails table right email message table so therefore step four is I'm going to take the authorization token and use it to sync the inbox so I'm going to take this authentication token and I'm going to go through oringo API right so then I'm going to send the token I'm going to say that hey I'm going to hit this endpoint with the account ID of 06498 and I give you a token of token d a b CDE e f so with this two information right the account ID is the identifier for John's email address and the token is the authorization token needed to make a request on John's accounts behalf so with this two information if we send it to the oreno API what it's going to do is this oringo API is then finally going to give us back a list of all the emails that currently exist within the John's email so imagine we are basically copying these emails and oringo will give us give us a list of the current emails in a Json format and then we can basically save all these emails into the email table right and then the email have the ID the subject the body and the front okay and I'm going to actually show you the shape of this response back because once you see the Json shape it will make complete sense to you so let us I'm going to show you this so I'm going to show you email. Json right so this is kind of the the shape that we're going to be getting back so we can see we're going to get back a list right a list of stuff so each item in the list corresponds to one email so each email message will have a unique ID right it will have the thread ID that the email belongs to course remember related emails like an email will always belong to a thread right and it's possible to for that a thread to have only one email message right so this is entirely possible so you can see that the email here belongs to a thread ID there's a time where it was created it it was a time where it was sent at okay so if we go back to the example of here let me show you my app here so you can see there's this sent at about 18 hours ago so this field is the one that maps to be sent at so this is when it was received at this is an internet message ID which is another identifier used in the SMTP protocol we have the subject this is pretty self explanatory we got a list of system labels so these are the labels that has been automatically CL classified by the SMTP protocol I think right so then we can use this to it to basically display you see this unread and inbox this is coming from the system labels right and we have a list of keywords we got a list of system classifications we got the sensitivity we got who is it from right we can see this email was from notifications at account. def with the name right and you can send in who is it to so you can see the to is a list of addresses right it's a list you got a list of CC's so these are carbon copies a list of bcc's which are a list of blind carbon copies right and there's a list of reply to so this list of reply to is like when I send an reply back to this email who should I send it to right this is where this field will tell me who to send it back to and whether the email has attachment right and then we have the actual body of this email which could be text or which could be uh like HTML right so later we'll deal with the HTML bodies to be rendered nicely here because if you see you see this fancy images and buttons the body here is made of HTML right and with have the body snippet the body snippet is just like this part of the text here so it's just a shter summarized version of what the body is we have the list of attachments and then we have Internet haters so internet haters are what you call metadata on the in the on the email so that's like who it was delivered to who was received by uh the the source of the SMTP who was received and so these are like uh kind of metadata we don't necessarily need to look at this we just need to know that that it exists on the email itself right and then we have the folder ID so they can be inbox it can be draft it can be sent and then this is like omitted which is like another oringo field so you can see that it's pretty self-explanatory right we get back the list of emails from oringo oh my God I keep losing it we get back the list of emails from oringo after hitting the email/ sync endpoint so we take this this list and we then just save it in our emails table and once we have this emails and threats table we can then finally have a nice UI to display everything we have okay so that's pretty cool pretty cool but now I to explain to you how do we actually keep the details in sync how do we keep the details in sync so this entire process right of them authenticating and stuff like that this is what we call the initial onboarding process so the initial onboarding process involves getting consent getting the token storing the token creating the account after creating the account sending the initial syn synchronization request to oringo to get back the current list of their emails but now that after the initial onboarding process is done we now want to keep the the inbox in sync how do we do that okay so now I'm going to go to a different part of the canvas actually I'm going to copy this right okay so now imagine we have our database so I'm going to copy this here so let's say this is normal humans server right and then we have our database okay so imagine we have the database of emails right so we can have the subject the body the from and other fields right the other fields like when it was sent Etc but we'll ignore that for now so imagine John Suddenly receives another email it's like hey and then it's like Jane Jan gmail.com send an email right so then Gmail will suddenly have an draw email here but the thing is we don't have that email so we want to kind of uh continuously update our database based on what Gmail has and this is where the kind of web hook concept comes in so if you're not familiar with web Hook is basically a okay let me search the definition of web hook together uh how do I explain this um web Hook is a event driven communication that autom sends data between applications ah that is too complicated of a definition okay so my my kind of definition my simple explanation is Imagine uh it is exactly like a call back where whenever Gmail gets an email we're going to give Gmail an API so imagine we have a domain is like https www.n normal- human.com API um callback right callback so whenever Gmail receives a new receive a new email on behalf of John's inbox he's going to Ping us right GMA is going to ping ping this URL with the actual content of the body so we have the subject with have the the body Etc right and then because we control this endpoint right we will able to then know that hey there's a new email and therefore I should probably write this kind of we should probably write this new email into our database and therefore we can take whatever Gmail sends us to the web hook and write it into the database so imagine have 1 2 3 4 we have the body of like pong we have sorry the subject is pong the body is Ping and it's from j@ gmail.com and so now we have successfully kind of uh kept our database as well as the Gmail uh gma's inbox in sync right so this is the concept of a web Hook is basically I give you an address and whenever something happens on your end just make sure to Ping this address so that I know that there's an update for me to synchronize our database okay so that's the entire concept of web hooks okay and so we're going to basically or Rinko gives us the ability to set up web hooks to listen for new events in the Google inbox right so we're able to connect create a connection between the John's inbox as well as John's account in our database So then whenever John receives an in email oringo is going to Ping us with an API to the web hook telling us that there's a new email and then we're going to take the Ping we're going to then uh sync up with Gmail's inbox again and therefore keeping the emails in Sy yep yep yep um I think it's going to be much clearer to you once we start coding and once I start showing you how the data is flowing and then you will understand but for now this is the general overview of everything that we're going to be doing throughout the um throughout the whole tutorial so we've covered oringo we covered emails and threads we've covered the metadata we've got accounts I talked a little bit about syncing and I talk about web hooks so if you understand the high level overview of these topics we can then actually go into the code and I can always reference back to this diagrams as I code along step by step and explain to you which parts we are coding now so now let's actually start uh getting the project up and running so we can start seeing some progress cool so now that we understand the high level overview of the email clients let's start setting up the code so I'm going to open my terminal okay and let's expand this out a little bit so we're going to be using the T3 stack so if you're unfamiliar with it the T3 stack uh is built by Theo and it is a type save next year starter kit that has nextjs typescript trpc Prisma TN CSS and we're not going to use next off but we're going to be using click okay so to actually uh create a new project I'm going to go into my coding folder I'm going to do um too bu X create Das T3 app at laters so throughout the tutorial I'll be using bun uh in replac F for mpm and if you're using mpm you can just replace it with uh MPX create T2 app and another note is there are some people asking me why I'm using Pudo is just something wrong with my MacBook that they don't allow me to run commands unless I'm in Pudo mode maybe there's something wrong with how my user is set up within the computer but most likely you will not be using Pudo throughout the tutorial so whenever you see me using sudo you should just kind of ignore that so you should be just running bu X create dt3 app at latest okay so it's going to resolve it it's going to download down the repository it's going to then ask us what our project is called so I'm going to name it normal uh human- yoube we'll be using typescript right um we'll be using TN CSS press yes we would like to use trpc we'll be using clerk so we'll just press none for now we'll be using Prisma for orm and yes we would like to use the nextjs app router and then we'll be using postgressql because we're using neon DB and let's actually initialize a g repository and run BN install for us I'm going to press allias for the import alien for now and then it's going to go ahead and set up the repository for me cool so it's going to run but install and in a few seconds time I should be able to open it up in my code editor so I'm going to CD normal- human- yoube I'm going to press code Dot and this should run so I'm going to just delete my normal repository and okay cool we're now in a fresh new repo what I'm going to do is I'm just going to run sud sudo bun Dev so this is going to run on Port 3000 right and let's open the browser to make sure that we can actually see what's happening so let me just close down this things here first uh actually I need my excal draw but let's go to Local Host 3000 and I should be able to see the next GS app running and let's see okay in this case we can see that uh we can reach our database server at local host 5432 so what I'm going to do is I'm just going to come down to our page. TSX so this is the main first page that we're running and in this case they're trying to I'm going to just delete everything within the code block we're going to start everything fresh I'm just going to return a hit one that says hello world I'm going to save this and you can see the issue where I'm saying that I do have enough permission so it's a purely me problem so I need to run change owner to make sure that I own the repository for some reason you probably don't need to do this I'm going to run Bev again right and then I'm going to save the file and I'm going to refresh and hopefully yes cool we see Hello World running so our next GS app is running and what I'm going to do is I'm going to bring this to the side here so I have a split screen view of what's happening okay so uh let's actually try out Tailwind so if I give you a class name of text - r- 600 I can see that it's rate so T is working perfectly cool so now I'm going to try to set up set up shet CN which is this very nice component library and this also where we'll be stealing this email inbox UI from right so to set it up come down to your command line I'm going to open a new terminal here okay and I'm going to run sudo bu x uh sh cn- UI at latest in it so this is going to help me initialize a new Shen uh configuration right so it's going to resolve um okay there's issue with this I need to run PM PM PX PM PM vrx sh CN it latest add so I'm just kind of initializing shet CN right uh it's asking me what components I want to add in this case I'm going to press a to select all I'm going to press enter right so I'm going to press confirm so what it's going to do it's going to download all the components from sh CN into my repository I'm going to choose a New York theme with the the neutral I'm going to press yes to all right so it's going to install all the dependencies right so it's asking me if I to overwrite some stuff I'm going to press yes just overwrite everything here and cool so let me exit out the terminal and now if I see into my my folder I now have a under my source folder I have the components - UI sorry slui and this is where I have all my components from Shen so I have this button component which they have redefined some class variance Authority like some stylings for me and I can use the button directly so let's test it out I'm going to return a button right so I can automatically import it at/ component SL ui/ button I'm going to say hello world and boom our button is here nice and pretty cool and now let's actually set up up our authentication provider which is we're going to be using clerk cool right so clerk is a manage authentication server for us to use um it's up to you if want to use your your own authentication provider I just cler because it's so easy to set up I'm going to continue to create a new application so register for a CL account first if you have not but then what I'm going to do is I'll give you a new name normal humans YouTube let's ask for their email Google and allow them to loog in with their GitHub I'm going press create application right and then what I'm going to do is I'm going to choose next GS I'm going to follow their quick start guide so the first step is to install at cl/ nexts so let's do that together I'll open a new terminal and I'm going to run sudo bun install right sorry bun install at CL SL nextjs so this going to install the component for me the library for me and then I'm going to copy the environment variables into myv file so let me see if I have a EnV file I do cool so I'm going to copy and paste it down here copy paste right so you can see my cler publishable key and the cler secret key is now pasted in my EnV file and then I'm going to update my mle we TS so come back down to your Source folder so under the source inside the source folder create a file middleware DTS right and what I want to do is just copy whatever code they've given you we're going to change this later by the way so what this does is we import the CL middleware and it's saying that whenever you see a route uh whenever you see a route here you're going to make sure that cler kind of protects the route or make sure CL runs it for us so that we can get access to the user information okay and then finally we need to uh wrap our app in the CL provider in order to use the clu hooks like use user use off Etc so come down to your SL Source slapp layout. TSX so this layout. TSX is wrapping out entire application right so I'm just going to copy um I'm not going to copy the whole thing because we have this trpc react provider that don't want to overwrite so what I'm going to do is I'm going to copy this part here and I just want the CL provider right and then I'm going to wrap my entire HTML with the CL provider going wrap it down here and everything should work fine now and finally if we go back to our app you can see that nothing is happening which is good because by default default clerk doesn't protect any of your pages so in order to make it such that uh the the kind of landing page is protected what we can do is let's go down and let's utilize our own page for authentication so let's continue down to nextjs guide okay so the first thing is we're going to build a sign up P sign up page right so lit have to follow their instruction so go down to your app folder and then create a folder called sign-up then create a uh Open Bracket dot dot dot sign- up and then do page. TSX right and you can copy in this into uh the into the component so you have to import react to kind of remove this error and we'll do the same for the sign-in page so let's come down to app let's create sign dashin let's create Open Bracket do do do sign- in and we'll create page. TSX let's copy and case it in we also have to import react from react and save it so you see by default click middleware so if you go to the middleware this click middleware function makes all Roots public and so this is only if you want to make all Roots protected by default which is what we are trying to do so what I'm going to do is I'm going to just copy this code I'm going to replace our middleware TS so what's happening here is we're going to Define a variable called is public root right which is saying that these two Roots should be public so this is a reject a regular expression expression so whatever rout that starts with SL sign sign in will be public and whatever root that starts with SL sign up will be public as well which makes sense right because if you're accessing sign in you're probably not locked in yet okay and then we're going to export the clock middleware so this middleware function is going to run before any kind of next year request uh request gets hit so if you're requesting for a page this middleware will run first to kind of protect our Roots so we're checking if it's not a public root we're going to protect the Page by kicking them into the signin page okay and then we're going to update our environment variables to tell our app where is the actual signin page so we'll just copy the EnV variables that they asked us to and let's copy it down so next public click sign in and sign up URL is exactly what we have written here which corresponds to the folder name called sign in and/ sign up cool and finally if we go back to our app so we go to Local Host 3000 we can see that it's asking us to sign in so if I go to the slash page because it's a protected route we can see that it automatically redirect me to the sign in page all right and to actually show you how to make a Roots public because we're going to have to do that a lot later right to make a root public you just have to kind of edit wait what's this okay you will have to add it to the list here so I going to make slash if I make slash a public rout if I now neate it to slash look go host 3000 slash you can see that now this is a public route but if I remove this right it's going to then protect the root and make it so that I have to sign in to access it cool and then the sign in page right what I'm going to do is I'm going to just wrap it in a to center it so I'm going to return a div here and for this div we have a class name of absolute uh sorry I can just do F Flex just this F Center item Center High screen and it will Center the component inside for me cool and now what we're going to do is we're going to sign up right so another thing is whenever clerk signs up a user we want to kind of sync it to our database right so after they sign up we don't know that a user sign up because we want to create a kind of a a user table within our database so that we can keep track of which user uh is locked into our platform right so to do that I'm going to set up our database next so let's set up Prisma cool stuff and I think they already set up Prisma for us so if you go under Source SL lip actually not lip let's search for DB okay yeah so under SL Source SL server SL database. TS right we can see that by running create T3 app they have already created a prart client for us and one thing I'm going to do is I'm going to remove this query thing cuz I know it's going to get annoying it's going to basically FLIR our uh our locks with the query like SQL query stuff which is annoying so they give us a Prisma query client and so now we just have to Define our database schema in Prisma so if you go under if you search for schema schema. Prisma right so this is the file in which we Define how a database is going to look like so before we start designing our database let's actually have a database in the first place so I'm going to go to my neon console neon teag and just create an account in neon if you have not done so I already have an account so I'm going to log into the dashboard and so so neon will give you a free database by default so you can just create a project and in this case I'm going to go into a current project okay so I'm going to create a new database in here I'm going to call it um normal human- YouTube and I'm going to press create and so this will quickly spin up a new database for me right then I'm going to just copy the database URL come back to my end folder and let's actually replace this local host post grass with my new database okay so this is my new database right and then what we're going to do is come down to our schema. Prisma we can close down the neon console right we're going to have we're going to start designing the table we're going to start with obviously a user table so in order to define a table within Prisma you can to create a model so this will basically say that okay I'm going to create a new user table and then the user table will have a ID which is a string which is a a primary key ID and by default we're going to give it a CU ID as index uh the user will obviously have an email address which is a string and it has to be unique it will have a first name which is a string you will have a last name which is also a string right we will have a image URL which will also be a string but it's optional because it might not have an image okay and then I think for now it's fine so let's actually save this and then to push our Prisma schema up that means we have to sync our tables within Prisma schema the Prisma up to our actual database what I'm going to do is going to run sudo bun Prisma DB push so if I press this right and I enter my password you don't have to run sudo by the way you should just be able to run but Prisma DB push this will basically migrate our table so we can see that they loaded in our environment variables and it detected that we're using this database URL right and then now it says that our database is in sync with our Prisma schema right right and to confirm it if we do bun Prisma Studio this will open up a kind of database view for us and we can see that we have a mod user model which means it's a user table with the different fields we have ID email first name last name and image but we have no rowes yet okay and then I let me show you uh if I come down here I'm going to create a new file called playground. TS within my source and this is where we can kind of experiment around with typescript so I'm going to say await DB uh sorry Prisma sorry yeah where is my Prisma TB yeah where's my DB though it should be here okay I'm going to reload my window so that I can get access to my database okay so I'm importing my database from SL server DB which comes from this file here so you can see we're exporting database I'm going to say await db. user so I can see that I have this user object here do find uh sorry I'm going to say create right the data will be so I can see if I press control space I get like the type sense intell sense of all the fields I can create with I'm going to give an email address of test at gmail.com I can give a email I can give a sorry this will be email address I can give a first name of Elliot I can give a a last name of Chong I can have a image URL if I want but I can leave it out because it's optional right and you can see the ID is also optional because it's automatically generated by default and so to run this file what I can do is I'm going to quit off my Prisma Studio I can run Studio Bun Run playground. TS so when I do bun run to the file that the typescript file what it's going to do is I'm going to say console.log done right I'm going to press enter uh okay it's not found because it's actually in Source playground. TS so you see I run it it's going to say done right because it gets console. log out and if everything successful I should be able to see a new Prisma user table I'm s new user Row in my Prisma table so let's run pris PSE sudo buan Prisma studio so then I should be able to see my user now has one row with the test Elliot and Chong inside so that's super cool and you can see the ID has been automatically generated so this is how we're going to be interacting with our database from now on so I'm going to delete the row right I'm going to start a fresh I'm going to press delete right and now what I'm going to do is I'm going to set up a web hook with clerk so why do I need a web hook with clerk so this is what is happening right so it's a very similar kind of structure with us trying to sync the emails we have to sync our user with click okay so imagine I take click over here so click is here and we have our application our nextjs app here right so when a user comes so imagine we have a user a user comes to our app right and then they sign up to clerk so clerk has the actual data right CL has has their own database right they have their CL database and they have the the user so imagine Elliot gets created in the first row of their database but the thing is we have our our own database right with our own user table right and the thing is I want I want the data from clerk to be inside my database as well I want to store the user in my database as well so how I'm going to do that is I'm going to use the same concept I'm going to give clerk a a web hook so I'm going to expose a web hook saying that hey clerk whenever whenever you get a new user new user created uh ping me so it's going to then ping my web hook which is like let's say https actually no I can say I can do SL loal host 3000 API clerk web hook okay and then whenever someone get registers a new account with clerk clerk is going to hit my endpoint here with the information about the user it's going to tell me the name the email the image URL Etc right so I can then take this information that cler has passed me and then I can write it into my user user table and that's how I can keep my data my user data in sync with cler's database so to actually orchestrate this process we're going to basically come down to the clerk dashboard here again right we're going to go down to back go back to the user configure so if you go down to configure under dashboard so we can see we have this web hooks session okay so this is where we're going to tell clerk to hey ping me whenever you have a new user so I can say I'm going to add a new endpoint right and in this case I'm going to do like HP loost 3000 and I'm going to say hey hey ping me when you have a new user right so I'm going to subscribe to events and this is exactly what I mean by kind of a ping me when something happens so the the phrase after when is the event that I want to subscribe to so I'm going to say whenever a user was is created uh ping me so this what this is how I do it uh you can leave the advanced configuration as default so we're going to see that this won't actually work why can't I put Local Host 3000 in this because imagine how CL is actually working whenever CL CL has this uh endpoint here right it's going to try to Pink Local Host 3,000 but the thing is they the clck server doesn't know what Local Host 3000 is to them a local host 3000 is the is the Local Host 3,000 that's running within their server The Local Host 3000 is not the Local Host 3,000 that is running on our server right so imagine like I'm trying to develop on my MacBook so this is my MacBook right my MacBook is a has a private IP that is within my house within my own router and then cler has their own kind of set of server and their kind of network within their own infrastructure so the local host on clu server is referring to a different machine than the Local Host within my server within my network therefore in order to make sure that cler can actually send information to our to our Local Host we need to set up what we call a web hook proxy so I'm teaching you a lot today I hope you kind of stay with me these are pretty advanced stuff that I've not seen anywhere else on YouTube but if you follow with me I'm sure you will learn a lot of new and cool stuff okay so a web hooks proxy is basically a external service that sits in between uh the clerk and our server so this proxy lives on our our on my on my MacBook so this is a proxy that listes on my MacBook and then what we're going to do is this proxy will expose a public URL a public domain like let's say test- web hook. cloudflare.com so cloudflare will help us will give us a public URL domain and then we can actually take this test web hook and we're going to basically put it in this end point URL so test- web hook do cloudflare.com So then whenever click get SE the new user is created it's going to Ping this test web hook and because this test web Hook is a proxy that is in my MacBook this proxy is going to then send forward the request to my local host so it it goes to this middle step where it sends to this public domain and this public domain then sends it to my uh logo 3000 so this is how I can kind of test web hooks locally and later on we're going to use this same concept when we are trying to test the web hook with oringo to sync our emails with the oringo server okay so to get this test web hook right what you going to do is does this service right is called you can run MPX uh un tunnel at latest and you can say tunnel HTTP loal host 3,000 so this command this command here creates a proy web hook that connects our connects the public domain to our computer so if you wait for a few seconds we can see that it's saying that started Cloud fa tunnel to logo 3000 and this is exactly what it is it is just a tunnel right because imagine here what it's doing is if you imagine like a a slanted rectangle right this is basically a tunnel right it's tunneling the request from the server to the to my to the proxy then to the proxy it tunnels it into my Local Host 3,000 all right so we can see that the tun is ready at https veterans do ESP blah blah blah TR cloudflare.com and if I copy this right and I paste it in my domain you can see that is actually accessing whatever is exposed on my Local Host 3000 right this is whatever is showing on my Local Host 3000 and if I kind of stop my server running and I refresh we can see that it's saying that 502 B Gateway because nothing is running on my local 3,000 that's why it's it's saying that there's nothing running so there's nothing to tunnel to so going start my server Again by running bndf and now if I refresh it's going to it's going to it's going to hit my server again you can see is is seeing the request and my request are tunnel to this public URL again cool so now that we have this public URL I'm going to copy this I'm going to paste it into this uh web hook endpoint URL all right and one additional thing I'm going to do is I don't want it to just hit the the base end point I want you to hit like something like SL API SL clerk SL web hook so let's actually set this up right so I'm going to just make this work first I'm going to press create so this will create the web hook right and then what it's going to do is we can come back down to our uh to our vs code and now let's actually create the web hook route so go to your your /a/ API so you can see this API already has some trp see routes that is being that is put here by create T3 app so what I'm going to do here is I'll just take this API folder I'm going to create a new route called uh Slash Clerk and inside here I'm going to create a new folder called Web hook I'm going to create a new route. TS so this is a special route this route. TS is a special file within nextjs that allows people to hit my next year server with/ API cl/ web hook so if you see the structure here/ API each folder represents to a route segment here so you see/ API SL clerk you see this clerk folder this represents the clerk segment and then the web hop is another folder which represents another segment and finally if you have a route. TS at the end of it this will correspond to the route Handler here and then over here I can then uh have my clerk web hook okay so the first step here I'm going to to expose a post end point because what clock is going to do is it's going to send me a post request to this this proxy and the post request is going to come down to my server so I need to be able to handle that post request so export cons post right equals to an async function or async Handler the first request is going to be a request object right and then the function what it's going to do is I'm going to first extract out the data from await request. just on Json right and then we're going to just first console.log out the data I'm going to say clerk web hook received and going to consol the data and then I'm going to return a new response to clerk saying that herey I received your web hook everything is good I say web hook received with a status of 200 okay and of course like I hate why this es L thing is always so aggressive so I'm going to do is I'm going to come down to my es lint and I'm going to just disable a lot of stuff here I'm going to disable this two stuff and I'm going to just disable everything here boom so hopefully this goes away okay and yeah so I mean this is just a personal preference if you want to have trigger type checking just leave it on but but I know kind of what I'm doing here so I don't want uh yes to be shouting at me okay so we're going to test this this endpoint right so we can see everything should be good here and how we're going to test it is we're going to come down to our next our click dashboard and then I can actually test it out I'm going to press testing here I can select an events type I'm going to say user. created right I can I'm going to you can see that there's a schema of how the the Json payload is going to look like so we have a data we have the email addresses we have got the name and the last name Etc right so I'm going to press send example and so hopefully this will ping my my tunnel Cloud flare my cloud flare tunnel proxy and then it will forward the request to my endpoint so if I press send example so let's look at the console carefully 3 2 1 press so let's see do receive it um in this case it looks like nothing is happening so let's actually look at why that is the case funds locks all okay so you can see that it says failed and let's look at the reason why it failed so it's saying that veterans do blah blah blah SL API click web hook so I think the reason is because we have not authorized this endpoint to make it a public endpoint so remember in our middleware dots we need to specify that all the web hooks has to be a public endpoint because people are not authenticated when when a request is being sent through a web hook so we need to do/ API clerk web hook right and so hopefully this will make it so that uh the request can be uh kind of tunnel true so let's actually test it again so let's do test I'm going to send a user created send example and boom we can see post /i/ CL returns a 200 request and we can see that the data is being locked out so it says CL web receive this is what I'm console logging out in the console and the data payload is whatever we have here right so we have got the email addresses we got the data blah blah blah and if I go down to my overview right I can see that it says the web hook succeeded cool so now that we Reed this email like sorry this data payload when a user gets created we can actually write the data into our own database so what I'm going to do is I'm going to say cons email address equals to data. email addresses the first email address I'm going to take that email address we have the first name which is data first name we got data. last name we have a cons image URL right so everything is from here you can read so first name is here last name is here we got image URL somewhere down here and then obviously we have the ID we can take data. ID which is like somewhere here here uh here this ID uh this one user underscore something something okay and with that we can then await DB we're going to import our database do user. create we're going to create a new data that has where sorry it's not yeah I'm going to create data that says the ID will be the ID that we received we have the email address to be an email address first name last name and image URL so with that we can save it and then what is going to happen is I'm going to test it again I'm going to test my web hook again and everything if everything works well I should see a new uh user Row in my database so I'm going to come back here um let's see can I test it hello uh let's go to web hooks here I'm going to go to testing I'm going to send a user Creator type right so if everything is correct I'm going to just console.log out user created with the user here actually no it's fine so I'm going to press send and I can see hey user is created everything is okay right I can just ignore the console loog now so now I'm going to close down the tunnel so the web will no longer work but I can check my Prisma studio and hopefully I get to see a new user that has been created with the information from Clerk and I do see it the user ID the the email the first name last name and the image URL perfect so now that we have this we can actually push this to production so that we can give clerk the production URL to kind of Link our our user uh our user data to our database okay so I'm going to deploy this right now together to versel and I'm going to see how it works okay so the first step is I am going to create a new GitHub repository so go down to github.com new right I'm going to say this is what's this normal human- YouTube I'm going to say a powered email client we'll make this private for now I'll public it later on so you guys can all reference it so let's create a repository cool and then I'm going to follow the example here so the first step is to get in it but the thing is we already have an initial repository so then I'm going to do get ATS all wa let me check my gting nor uh okay I should probably do get status I can see that uh everything here is not added here so I need to Stage it I'm I want to I'm checking if I have a okay the EnV has been ignored so it's fine I'm going to add everything to the stage environment and then finally I'm going to run get commit DM initial commit for clerk then I'm going to basically add the remote origin and then I'm going to push it up to me cool and with that if you refresh we should now be able to see our repository on GitHub I'm going to now go to verel I'm going to create a new project I'm going to import it from my normal human Dash YouTube import this right and everything I can keep it the same and I can then put in the environment variables right so I'm going to copy everything here command C I'm going to just paste it in here and you automatically populate and there's actually one more thing okay you know you know what it's fine we'll do it later but then I'm going to press deploy and hopefully it's going to kick off the deployment process and we're going to see it live okay so I'll be back in like 50 seconds and one more thing that I want to add before we continue okay okay it errored out by the way and I think I might know why yeah because it's saying that to compile because there is some type errors and like we can ignore it for now so go down to the next config.js and inside the confict what I'm going to do is going to press typescript and I'm going to say ignore build errors to be true right and for ES lens I'm going to just uh ignore doing builds as true as well so when we deploy this if we Face any type errors it's just going to ignore it and just continue deploying it which is fine honestly if you know what you're doing so let's do the same we're going to do get adds get commit I'm going to say uh ignore type errors I'm going to get push this up and hopefully just going to kick off a new deployment right so let's look at deployments um you can see start someone is starting build to build and then hopefully this will succeed because we're ignoring all the build errors for now okay and before we continue I'm just going to to go down back to my chisma studio and I'll delete the test user here just press this and delete the record and boom we got it deleted nice so after it deploys which it should deploy any time now it's going to give us a domain cool so you can see it has given a domain called normal human- youtube.app and it's going to go to the SL path but thing it's going to kick us to the signin page so now we are back to the signin page but the thing is it's deployed so good news so now that we have this actual URL right we can then use this URL as the production web hook for clerk so I'm going to just close this down so now instead of having to rely on a proxy here instead of having to rely on a proxy I can literally just have the actual domain right www.n normal humanyoutube docomomo so then this because it's already directly linked to our production database right everything will work perfectly so let's now go back to our clerk web hooks right so let's go back here I just refresh the page okay wait for it to load and then I'm going to press this I'm going to edit this I'm going to just remove the the proxy I'm going to do https sc/ www.n normal d. normal human that youtube.com so make sure that this domain is the one that kind of versel has given you here so make sure it matches this exactly and you need to you should add the www in front as well so I'm going to press save and everything should work fine now and so if I test it again so if I test my endpoint uh how do I test my endpoint uh and has all fail activity locks activity how do I test it come on testing okay if I press send example this should give me a 200 request which means succeed so send send example and hopefully please oh my God it failed so let's take a look at the reason why let's go back into our sell locks so if you go on to Locks let's look at the reason why it failed uh is it because ww w. normal human-y youtube.com oh because ww doesn't exist okay that's my bad that's my bad so what you have to do is come down here to edit and remove the www dot right and save right wait no I know why oh my God because it's a versa.com oh my God I'm so horrible at this yeah so let's try www. normal human- youtube. versel app uh what we remove the www come on okay this works so let's remove that we have normal human- YT do. ver. app so I made a mistake I told you guys to follow exactly and I didn't follow exactly right so if you go down to project you can see the domains this is the one that you need to copy right versel app so let's save this and hopefully hopefully this will work so come down to test testing and let me send a user created status send example and pray that it succeeds and come on succeed or fail yay succeeded so this means that if you go down to our logs we should be able to see a user created lock here and if we check our if we check our Prisma Studio let's restart this Prisma Studio we can see the example user being created again cool so our production works that's good that means that we can start actually using clerk in our Local Host so I'm going to do BND again right and then I'm going to do is just come back to my uh react on my next J Project and finally finally if I press continue with Google and I press my kind of uh email account this should basically send a request to Clerk and clerk should send a request to my production endpoint my production web hook and automatically create a user for me in my kind of in my database so let's do Prisma Studio okay and finally I should see yay I see my ID my email address my first name last name my image URL right and this image URL should correspond to a my actual profile image hopefully y the marble cool so we finally got our authentication our database going and you learn a lot so much about web Hooks and this web hook stuff is important the reason why I put it here in front is because it serves as a educational purpose so later when we learn about Oro web hooks you have a more comfortable experience with this kind of syncing between an external data source and our own data source right and if you come down to Clerk and go to users I should be able to see a new user has been created in the dashboard and with that we have kind of uh successfully set up our project our nextjs our Prisma DB our authentication and now we can start actually building some cool data end point stuff with email syncing so I'll see you in the next section cool stuff okay so now we finally get to go into oringo which is like the main cruxs of this video to set up the the email client okay so the first step is you have to go to or. and create an account and loog into the dashboard if you have not already right and so once you're in it you're going to basically come into the dashboard and you get to see you get to create a new application so let's do that let's give you the name normal human- youube and let's just choose the the mailbox API so I'm going to press save so this will create like the API keys and the API secrets for us blah blah blah and cool now we can finally see that they gave us like like test St for us to use and so what I'm going to do is I'm going to first kind of Copy in the client IDs the client secrets and the signing secret into my EnV first so let's just do that so let's go to our EnV and then oops that's not it let's go to let's do oringo D API uh sorry oringo client ID is this boom boom boom and wait I'm going to check I'm going to cross check my code actually no it's fine and then we have the oringo client secret let's copy this from here paste it in here so you should not show this St by way I'll delete this account after filming and finally we have orango signing secret which is this thingy cool so we got this three client ID client secret and signing secret so this two stuff are basically what is going to what we're going to use to help oringo identify our application and help us request uh authorization for someone's email account on behalf of us okay so now that we have this let's actually go back to the excal draw flow to see what's the step by step so remember the first step is we need John or whoever is trying to use our app to give us access to their email inbox right and so to do this right we're going to basically ask them to go to the Google consent consent screen right for for them to like give us access so how do we actually get the URL to go to the con conent screen right so I'm going to show you where I'm getting all this information from so if you go into uh doc uh oringo docs uh docs oringo yeah dro.com and you can see email API right and let us go down to to email messages okay so they have a very comprehensive API for us to use so the first step here is authorization right we need to start an account authorization flow which means uh helping us get access to their Gmail account okay so see what we need from what we need to start it is we need to hit this slv1 off/ authorized endpoint right and then this we have to passing out client ID so this client ID is the client ID we got from our dashboard and we have the service type which is like what do we want do we want to connect to Google uh to Outlook and they support all of the different uh services and then we need to pass in the Scopes which is like what actual information do you need so in this case we need to like read email write email send email Etc right and then this this is the response type so the response type is uh do we want to get back a code or a token so this is what they call the oo 2.0 flow which I have done a video before in kind of my previous authentication video so if you're not familiar with the O 2.0 flow do check it out to make sure you you understand what actually is happening so we're going to basically whenever we're going to hit this endpoint with our client ID and secret Oro is going to give us back a code and then with a code we're going to then exchange it with another endpoint to get back a token and finally this token is what you see here you see oringo sends us back a token so this is the token that we are aiming for remember without the token we can do anything and remember that a token is unique to each user's email account okay so we're going to execute this flow for us so let's actually write some code okay what I'm going to do is I'm going to just reset I'm going to come down to my lip folder I'm going to create an oringo dots file um yeah of course I don't have have permission to write to my own computer anyways after we have done that I'm going to create a oringo dots file okay and this oringo TS file is going to hold all my kind of a code to interact with Oro API right so the first step here obviously is we need to export cons um get or wrinkle off off URL right it's going to be a kind of a it's going to be a a server action so because it's a server action we're going to just do use server right and so what's going to do is we're going to accept a service type so remember this service type can either be Google or Office 365 right which is what they stated on their documentation or it can be any one of these right so with this I'm going to first kind of get the user who is locked in so I'm going to do cons us user ID equals to await await off and so this off comes from cl/ nextg right so next will tell us whether this user is logged in if we don't have a user ID that means they're not logged in because just true and error saying unauthorized okay and then after that we're going to basically hit this endpoint this/ authorized endpoint so what I'm going to do is I'm going to uh construct the perms so this perms is what is needed to pass into this URL right because of the get request so I'm construct new URL search params and I'm going to say client ID is the process. env. Ringle client ID which is what we have set up here right remember here client ID and exactly what we need from here so I'm just like using whatever is in the docs right but just I'm going to code it out you can follow along you can refer to the docs if you want right and then uh I'm going to say a string to solve the type error we need service type which is the service type password in the parameter and then we have the Scopes right so the Scopes is mail. read right I should just copy this yeah so it is deiminated sorry deiminated by space so we need to read we need permission to read we need permission to rate write we need to send we need to create drafts and we need to have all the permissions right and then the response type is going to be code so we're going to take this code that we return and exchange it for the actual token that we can use on behalf of the user and finally the return URL so this is important because the return URL is the call back so remember here it's saying that orino sends us back a token actually you could do token SL code uh code right it's going to return us to this call back URL so this callback URL is what we kind of Define in the return URL so I'm going to do is I'm going to just just template in our process. env. next public URL so we have not defined this URL let's do that so I'm going to do next public URL to be Local Host 3,000 so this is where we're running so when we when we deploy to verell this will be htps normal human yt. verell app okay so for now let's just keep it at Local Host 3,000 and then we're going to do slash uh what we're going to do is SL API SL orle SL callback so I'm going I can you can Define anything you want here so this I'm going to just fit here so whenever oringo send us back the token I want you to just pass it to this callback URL and finally finally I will have to hit the actual API here right so I'm going to just return https call off or sorry api. or wrinkle. iv1 off authoriz question mark and I get just make the parameters into a string so that it will kind of fit the format that oranging go is looking for cool so when they visit this URL oringo is automatically going to redirect them to the Google consent screen or the Outlook consent screen depending on the service type okay and so to test this out what I'm going to do is I'm going to come down to my pit. DSX I'm going to create a new button here so I go go to my components here I'm going to say link Account button TSX sorry TSX right I'm going to make it a client component and I'll do RC right so this a es6 es7 snipper from extensions that gives me a arrow function component so let's rename this account button so I'm going to return a button here um and I should be able to import this I'm going to reload my window again because typescript vs code is so janky finally I can import it from my Shen /ui button all right and say Link account so when I click on this what do I want to happen right I want to basically because this is a server action remember we defined oringo here is a server action we can actually directly call this backend function from our front end code so this is a new nextjs I think 13 feature server action I'm going to say cons off URL equals to away get orle all URL so import from this this server thing right passing in Google let's just test Google right let's make it this Asing function and then let's actually control the log out the URL first okay then what I'm going to do is I'm going to go back to my page instead of rendering this hello world button I'm going to render this link Account button cool so my server is still running that's good if I go back to my kind of local 3000 I can see my link Account button and so if I click on it hopefully I get to see my sorry I'm going to just expand this yeah Boop so if I click on this I should be able to see this is the this is the URL that has been returned if I actually press on this URL and redirect to it let's see where it goes it will go to okay it's going to show an error because look at what I say it says return URL doesn't match the configur URL in the app so it's saying that we need to configure this call back URL the configur the return URL so let's go into the dashboard let's go to settings and we need to find the callbacks right so this authorization code grand URL so we need to Define HTTP colost 3,000 SL API oreno callback we have to Define it exactly as the Callback that we want to use here if it doesn't match it's going to tr the error so uh alongside that let's actually also put in the uh normal human- yt. verel app API Oro call back so in case we don't forget it later I'm going to press save and hopefully hopefully if I try again so I'm going to clear my console press again and it should lead me to the Google content screen awesome so now it's going to ask me if I want to give permission to Oro right so let's actually make this automatic instead of having to click a link in the console so let's go down to our link Account button so I'm going to do window. location do hyper reference equals to off URL so this will directly allow my browser to navigate to the URL that has been returned from this so if I press this button it should now redirect me directly redirect me to the off screen cool and so we're going to go through this flow but before that I want to set up the call back right because once I finish the flow it's going to try to hit this/ API Oro callback so to do that I'm going to come down to my API folder I'll create a a wrinkle route segment then a callback segment so is exactly the same as the clerk web hook right the folder represents the route segment and I'll do route. TS so this will correspond to/ API SL sorry SL API SL or Rinko callback right you can see that the file names and the URL matches okay and then to actually process it let's actually just export cons get first right Asing request so whenever oringo is going to redirect us back to the this route let us just actually um try something I'll just do cons user ID equals to await off so I'm going to just check the lockin user first I'm going to just conso the lock user ID is is user ID I'm going to return a next response. Json saying that hello world so let's let just test this out and see if it works for us so I'm going to go to the flow and then after the flow oringo is going to redirect us back to this route and then they hopefully we can see the user ID being locked in the console as well as they see the response in the browser so I'll give access to this email right it says Google hasn't verified this app it's it's okay you have to go into a Google console and like make sure that uh you you pass this app through verification right can say go to oringo so sign in to oringo it's going to ask do you want to allow oringo to read and compose and send I trust myself so I'm going to press continue and then hopefully it's going to redirect us back to Local Host 3000 API or Ino and perfect that's exactly what we get we can see the message H has been printed out as well because this is what we return in the browser and they display the Json in this format cool and we can see we can see this this callback URL as well and we can see the code that's being returned so let's copy this and I'm going to explain it more in my excal draw so you see this oringo uh oringo callback URL this is the URL that return us oh there a huge payload but let's take a look side by side so we can break this down into a few things actually I'm going to just copy down here uh you know that's actually not a good idea okay but the main point is we get back a few kind of Route segments the first thing is we get back a code so you can see this super long code all the way until this request ID so I'm just K I'm going to just delete some stuff here I'm going to do dot dot dot right so we have a code segment and we also have a request ID segment right and then this request ID is also long so I'm going just trate it as well dot dot dot and we also get a status which say success that's good and then we have a type which account of result so we get back four things right we get back the code the request ID the status and the type so we need to do check two things the first thing is we have to check the status is successful if not we just true an error then secondly is we take the code that has been given to us by oreno and now remember we need to exchange the code for the token always remember the token is the important part the token is what allows us to make requests on behalf of of John gmail.com so if you do have the token we can't do anything we can read emails we can send emails so we need to now exchange the code for the token and if you go down to the documentation we can see that they have this URL that allows us to to exchange a code for the token so we give them the code and in return they give us access token right as well as the user and the account ID so you see this account ID and token this is exactly what we're looking for in order to save into our database so we need account ID which corresponds to the email and we need the access token which is what is actually the most important part cool so how do we actually get the token sorry how do we actually get the the code from the URL here what we're going to have to do is okay first we got to check if not user ID we just have to uh just redirect them uh actually not redirect let's just show an error saying that unauthorized with a 41 error we're going to get back the PM so we can do get the PRM by doing request. next URL uh next URL do search prams okay so actually we can replace a request with next request because that gives us more information and then we can do con status equals to perm. getet status so this status is what you see here which is Success so we're going to check if status not equals to success right we're going to just directly save to to link account and then now we need to get the code so let's do param get code so we need to get the code get the code to exchange for the off token sorry access token okay and then we're going to do uh we're going to try to get the token so let's go back to our oringo and let's write another function right to exchange exchange exchange code for Access token so we're going to get back I'm going to delete this I'm going to get a code as a URL right and then what I'm going to do is I'm going to hit this end point so the end point that we saw just now in the documentation is this off/ token code so what I'm going to do is I'm going to write that out give me a second so we're going to use exos for this so let's actually bu install exos exos so exos is like fetch allows us to kind of make HTTP requests let's import ex from exos okay and then let's actually do the request so the first step is we're going to do uh we're going to do a TR catch block so con response equals to A we x.g we're going to get https api. oringo doio slv1 SL account and then we have to pass in a few things which is the first thing is the haters and the haters um sorry yeah let me see actually no this is wrong because you can see they actually looking for the app authentication which means they are looking for uh we need to just pass in the third object so note that the second object is MD but the top object is where we can Define our off and this is where we put in our username username and password so the username is process. env. client ID and the password is the client secret so this comes from the EnV variable that we have here right so let me see why it's showing an error oh sorry it shouldn't be post right it should wait sorry it shouldn't be get it should be post so let's check yeah this is a post request my mistake so got post so the second object usually is represents the body but we have no body we're passing in directly an off object and then we're going to just return response. data and the shape of the data that respond has four things right account ID token user ID and user session so let's return it as account ID so I'm just going to copy this boom right go have the shape so this for typescript and then if there's an error if exos is exos error that means it's an ex error we're going to just show a new error actually no we just say console. error we're going just log out the data and then if it's not ex error just like some random error we'll just log it out cool so hopefully we can call this function here in this so let's do that so obviously if there's no code first we're going to just return there's a no code no code was provided but if there is a code we're going to say token equals to await get oringo play exchange code for Access token we'll pass in the code and we'll check if there's no token we go then through an error saying that fil to exchange the code for the token right and then lastly now that we have the token right we can actually access it we can exchange token for the account information so the account information will give us back the email their names Etc right so let's do that so now I want to show you where we are in the process we are here so we are still dealing with the token and email but now we get back the account ID and we get back the token but we are not sure what their addresses so the final step is exchange exchanging the token for the address and we should be we should then write it to the account table okay cool and so let's actually write the function let's go back to Oro let's write the function to uh get Account Details so for this I'm going to take in the access token as a parameter okay I'm going to hit the endpoint so I'm going to do a TR catch on response equals to A wait ex. get https api. rangle.io V1 account okay and then for the for the HS we're going to pass in the authorization to be Bearer and then uh templating in Access token so this will allow us to authenticate uh we are exchanging the users access token for the account information so remember the access token is specific to the user is specific to the account and therefore when you exchange the access token they will give us back the information regarding to the person who holds this access token okay and then finally we return response. data as we get back an email and we get back a name that's all that we need right and then for the kind of error handling what I'm going to do is I'm just going to take this paste this in right so I'm just checking if it's an ex error I'm going to just console lock error Etc okay and then now we can use this get Account Details in our R so then we're going to say hey let us actually cons Account Details equals to await get account details let's import that passing in the token right that we got soorry so this token we can see that we have the excess token within it right and finally we got this Account Details let's actually conso the lck out the Account Details um actually it's fine I want to write so now we at this step we're here we have all this information we have account ID we got the token we got the address let's actually write it into our database so let's actually create a new table within our data database for the account right so here this account table let's create this so model account we have the ID which is a string so an account is linked to a user ID so this user ID is a user ID within normal human is a user ID from clerk whereas this account ID is the email account on oringo side so you can imagine the structure where here let me draw it for you so we got normal human so normal human has a user so this user can be like uh it's just a user object so this user can have can be linked to many email accounts so like for me like I'm Elliot right I can have Elliot CH 16@gmail.com I can have another email I can have I can have three emails I can have so many more emails so this is each individual here is one account and each individual here is one user so one user in our system can have multiple accounts so I hope this makes sense so let's do that relationship okay and then so remember we have a token so this is access token right uh so I'm going to write access token here which is a string we need this and this is a unique string obviously all right we need a kind of email address which is also a string we have the name which is also the string that we get back from the account details and then and then let me see if this is enough uh do I want to include this um no no it's fine for now it's fine let's do it step by step okay so then let's actually link it up to the user ID and then now we can make the relationship a user can have money multiple accounts therefore it's put in this array right and then we have the relation which is the user ID cool so now let's actually push this up so we can do sudo buan Prisma DB push my hit is blocking so let me move this here so I'm running sud the but Prisma DB push so this will push it will create a new table called account within my database okay awesome awesome awesome stuff you know actually I can run Prisma Studio on another terminal so let's always be running Prisma studio so we can always see our database so now we can see that uh I can see now I have account table in here with no roles okay so let's go back to this terminal cool cool cool okay all right finally I can then do uh I can then let me see what should I write I want to insert into my database right I can say await db. count dot uh so I'm going to import database do upsert so what upsert is is a comination com combination of updates and insert so why saying that if the record already exists I want you to update the information but if it doesn't exist I want you to insert it therefore it's called upsert update and insert so I'm going upsert it so I'm going to check where do I already have a token do account ID right so if I already I if I already have a to like account with this ID I'm going to update it so I'm going to update it to uh make sure that the token the access token is the most is the latest access token that I'm getting back from this callback right but if I don't have a account within my database I'm going to create a new account I'm going to pass in the ID to be the token. account ID I'm going to have a user ID which is the user ID from from clerk right so let's just shorthand this and then we have the email address which is which comes from account details. email and obviously we have name which comes from Account Details on name cool and the last thing we need is access token but I do have access token oh yeah so the access tokens will be put in here and then finally after we have all this we are now in kind of this step so let me show you where are we we have saved it to the database and now the fourth step here is use a token to SN that inbox but let's not jump ahead so fast yet let's just make sure that this works right I'm going to just return a I'm going to return instead of uh normal URL I'm going to just um let me think yeah it's fine I'm going to return a redirect redirect to/ mail so this mail is the mail inbox you're going to see because they connected their account they should go to the mail uh dashboard now right and this should be fine so let's actually go to this flow and hopefully they redirect us to mail so uh let's see let's see so if this works I'll be super happy okay so if I refresh hopefully it returns us to SL fil to exchange code okay because it probably expired so let's actually try this again so let's go to Local Host 3000 let's press Link account so let's go let's link this account let's go to oringo Let's press continue allow and so what's this going to do it's going to give it's going to send me back to my URL and then oh fill to exchange code for exess token so let's look at the error here that's causing this uh okay okay interesting not found so it's saying that api. Oro SL V1 account something is wrong here okay it is my mistake here so I copy the code wrong uh so you see exchange code for Access token here this URL shouldn't be slv1 account right it should be slv1 SL off/ token right and then we want to pass in the code so let's actually template this in let's change this to back six cool and so because you see the endpoint to kind of search for the request exchange token is SL all/ token code so I made a mistake here this URL should be uh SL SL token code and so hopefully if I refresh the page I think it's expired um let's see yeah Okay cool so it works and you can see it redirected us to/ milil and what this means if I check my console everything should go fine cool it says you can see that my call back worked right it compiled and it ran properly and now if I go into my Prisma and refresh I can see I have account that has been created and let's look at this and cool so I have a account ID with the user ID so this is a user that corresponds to this you can see that it's the same user ID we have the access token so this is the access token that we need to make all the requests down the line we got an email we got the name and it's link to user very cool all right we're doing great we are doing extremely great so now that we have this access token we can now go wow we can start syncing emails right so now let's actually go to the final step of the initial syncing process which is syncing up the the inbox okay so if you go into the if actually let's just show something on the mail page first so let's go go to let's go to my page uh Pages sorry my app where's my app here let's actually create a folder called mail all right and then under the mail I'm going to do index. sorry page. TS X I'm I'm just going to export a um RFC mail dashboard and then I'm going to say return mail dashboard so hopefully you can see mail dashboard is being show cool so now have this midd page so now actually let's continue doing some backend work we got back the access token so now what we want to do is we want to exchange it sorry we want to sync it up sync use the token to sync the inbox in Gmail Okay cool so let's actually go down here to email messages sorry email sync okay so this is another part of explanation that we need to do together okay so let's go to sync um so let me go to to the docs. oringo doio and let me show you what they are talking about synchronization so let's read this together actually it just bring the same page anyways so we're going to start a new email sync what this means is we're going to send a request to G to oringo saying that hey we want to like uh get the first initial like all the current inbox into our system now right so we can see we can pass into two things we can pass in the days within which is how many days within the last few days we want to actually scan the emails so I can pass in a huge number like like 1,000 that means I'm getting all three years of the emails uh scanned to like scanned into my database right but for now for testing purposes we can just set it to like a few days so we get back the current inbox within the last few days of actual inbox and then the body type which is HTML text we're going to just pass in HTML to just get back the actual HTML body right okay so so so so after we after we start a new request thing you can see the response is we get two things sync updated token and sync actually that's the only thing sync updated token okay we're going to take this sync updated token that we got from this request then we're going to use that token to actually request the the actual emails okay which is which we have to get by this Delta token okay so this Delta token is a token received from the pre previous initial start sing request and we can also have a pagination token so because this request change email might have multiple Pages we have to keep requesting it until there's no more next page and then we know that we have gotten back all the information okay so this is how it's going to work okay uh let me draw this out for you so we have this initial sync endpoint what do they call it actually they call it uh slil sync SL email/ sync so this is the initial sync initial sync endpoint okay and then we have a request changed email and this the the sync updated so let's do that slash email/ sync SL updated right right right cool stuff so this is to to get the actual updated emails right so when we first hit SL email/ sync it's going to return us with a response of whether is ready ready or not it's a Boolean and a actual Delta token right that's why it returns us a Delta token right so if we we we see that the response is ready we're going to take this Delta token right this Delta token we can then pass into the email sync updated endpoint right we can see the sync updated endpoint here receives a data token so this is uh this is to tell oringo where do I want to begin syncing from so imagine okay this was like super confusing for me for when I first interacted with this so I really want to break this down for you guys to make sure you guys understand so imagine you have like a Gmail inbox okay and I have like I have like a lot of kind of emails okay so imagine I have this situation where I have a list of like emails in my my inbox account and I'm going to segment it by you can see one day ago two days ago and three days ago so anything anything above this is from one day ago anything between here is from two days ago everything here is 3 days ago and obviously here uh you can see it's 4 days ago right so we're going to say we're going to pass in the initial Sync here we're going to ask how many days in the past do I actually want to retrieve the emails so for example I can give a random number like two or like 200 depending on how much data you want to sync so if I say I want to sync two days ago what is what Oro is going to do it's going to look into my inbox right it's going to find the last email that was sent latest two days ago right and it's going to give me this uh it's going to give me like a a bookmark and the bookmark is what they call a Delta token so they're going to give me a bookmark SL token thing so we call this Delta token right and this token is important because then I can take this to token right I'm going to point this here I can take this token for example this token they say like ABC right this token points to the position in which I want to get the latest email from two days ago and then I can pass this token into the sync updated endpoint right so when I pass in the token like called ABC here the sync updated token will then start giving me will start taking emails from this in batches it will give me like let's say like five at a time so then it will give me this record so it will give me a list of Records which is a list of email addresses right and then it will give me a next next page token so because it's doing it in batches right right so here it's going to give me another page so it's going to move the cursor up here because it has already given us back the first like five emails here so then it's going to give us back let's say a page token the page token is like P1 123 1 4 so then we're going to take this page token we're going to then run this run this page token into this sync updated function again end point we're going to pass in p12 3 4 sorry p12 3 4 and then because the updated token not that we are now looking from this point onwards is going to give us a next batch of emails right and then the next batch of emails we can continue adding it in email addresses we'll keep on adding it in until there's no more next page token which which means that the update the Sync has given us kind of all the emails right so now the pointer is pointing at the latest point in time so imagine this like new 1 to3 this is the the latest Delta token right remember this the latest data token and we know now that we have completely syn sync up everything from the two days ago so it's a pretty smart book marking system to actually help us get it Page by uh pated one one batch at a time and with we're going to store the latest Delta token in our database the reason why we need to store this new one 123 in our database we need the latest Delta token because remember there can be new email messages that comes in so I'm going to just keep copying this so imagine there's like five new emails that come in here right what I'm going to do is whenever whenever Gmail gets a new email right Gmail is going to ping me remember the web Hood concept here Gmail will ping me uh will ping me saying that hey uh there's a new email there's some new emails what I'm going to do is I'm going to take that ping I'm going to take the latest datta token which is the pointer in which I last looked at I'm going to point this I'm going to put this datta token into this uh SL s/ updated and because this uh this to this Endo knows that I'm looking from this point onwards it will then give me the latest it give me the latest five tokens based on the data token that I've taken it will then give me the latest records from this new one 123 right and then it will give me back another new token to signify that this is the new n per se so then we have a new let's say new um 6 SI 654 and so we're going to continue using this process we're going to store the latest data token whenever we get a new sync request so that we can always have a pointer so whenever new emails come in again I can always pass in this Delta token to oringo to get back the kind of the latest ones that I have not seen yet because the alternative to this which is that we have no pitch token kind of concept every time there's a new kind of there's a new email what I have to do is I have to read the entire history of the mailbox and I have to load into my database which is super wasteful because I'm I'm not sure where I stopped that if I do have the the idea of bookmarks I don't know why I stopped that and that's why if there's no tokens I have to reread all the emails into my database which is super inefficient so I hope you understand this like more the most kind of confusing part of the entire process at least for me maybe it's like easier for you guys to understand so let's actually encode this process and sync up our initial emails into our database okay all right so we're going to continue coding here so remember here after we have gotten back our after we have linked our account what I'm going to do is I'm going to come down here here and I'm going to I'm going to basically start the initial thing process so I'm going to create another API endpoint called Initial D sync I'll create routes. TS right so this export cons post go to x async so this is the Endo that I'm going to hit myself after uh the after we have created account to actually begin the syncing process Okay so so let's actually import this so initial sync right so this is where we actually get it so uh after we done after we have done here okay uh I'm going to do is I'll do something like I'm going to hit hit uh trigger initial sync and point and I'm going to just trigger this and directly return the redirect because it doesn't make sense because the sync is going to take like a a few minutes like maybe a few seconds but I still going to take at least 30 seconds to do and so we don't want the user to be stuck in the loading state for 30 seconds not seeing anything before returning them to a UI therefore we're going to return the UI directly but we're going to run this asynchronously and to do that we're going to download another package here right called install ad verell SL functions so this just allows us to keep running our asynchronous code while directly returning a response from our import so let's import this function called wait until wait until so if you hover over it it should give you yeah you'll extend the lifetime the request Handler to the given promise so we're going to run a promise here so we're going to wait for the initial thing to complete before kind of shutting down this process but we're going to immediately return them a redirect response okay so we're going to do wait until what I'm going to do is I'm going to do ex. poost I'm going to post to my process do env. next public URL SL API initial sync so this is the initial sync that I'm building on right now so you see/ API initial sync this corresponds to SL API initial sync right so I'm going to hit this endpoint and I'm going to pass in a few things I'm going to pass in the account ID that I've gotten which is the account ID and then I'm also going to pass in the user ID that I've got from here and finally I'm just wa get. Den uh response I'm going just console. response. data and then if there's any error I'm going to just console. error fil to trigger initial syn so hopefully now after we connect the account they're going to redirect here but we're also going to see the kind of initial sync process starting to run okay so let's actually run the initial syn here so what I'm going to do I'm going to just console the lock I'm going to just get back the body I'm going to get account ID and user ID from request. Json right I'm going to check if there's no account ID or there's no user ID I'm just going to quickly through an error saying that we're missing one of them okay cool and now with that we're going to just search for the account within uh search for the account within our database so cons DB account equals to await db. account find unique where the ID equals to account ID and with the user ID equals to the user ID so there a unique uh compound key pair and so if it doesn't exist in our database we know that something wrong happened so we could just return a quick error here not found and now finally so we need to run a few steps here right we need to first uh syn uh perform initial sync right after performing initial sync we're going to get back of the the list of emails right we can get back a list of emails so hopefully we can do something like cons emails equals to await perform initial sync and then we can just do like a way sync emails to database and we can pass in the emails hopefully this is the the kind of end results that we're going to do we're going to get our emails from this and we'll then get the list of emails that actually write into our database Okay cool so let's actually write this perform initial sync function and to do this I'm actually going to create a rapper class on the account account object okay so this account object will allow us to have many methods on it like perform initial sync right I think it'll be super convenient so let's come down to our lip our library here let's create an account. TS so this account. TS right is we're going to export a CL class account so we have a private token here which is just going to be a string and I have that's a Constructor to take the token and pass it into this. token so then here I can just have a new account equal to new account from our lip and we can actually pass in the access token uh sorry access token do I have that uh sorry DB account. access token so now within this I have this token I can use this token to make requests on behalf of my my email inbox right so for for example right you can see that in the oringo API um for the email sync you see it it requires a it requires a account token so we need to pass this account token if you can see here uh let's see here what's this um right here you can see we will hit this/ email/ sync updated all right the initial sync and will pass in the account token to the barer off so this account token represents the authorization token for a specific user SL accounts that we are kind of syncing to okay and so let's actually have a function say um let's do uh yeah Asing per form initial sync so this is going to we WP in a TR catch block we're going to try to start the sync process that we describe inex Cal draw so let's just do let sync response equals to await um this do start sync okay and let's actually also write this another private function so this private method will only be accessible to this class okay and then this start syn function what it will do is it will um have a response equals to await ex. poost right we got to post to um What's this called This is https Epi rink. iv1 SL SN slil SL syn okay and then with this with this thing response we can then then uh we need to pass in the um the authentication so let's have an empty object because there's no body here right we only need to pass in the haters and for the haters we need to pass in the barer token for this access token here cool and then for the params right for the pams I can see right no this PM Is Here Yeah so the pams here we need the days within and the body type right you see the query param days within and body type so we have days within let's just put it as two days to begin with right obviously like if you want to sync more you can do like 3 days 300 days like the the whole history you can do that right for now just want to test with two days and I'm going to do body type to be HTML cool no space here and then I'm going to return response. data okay and it's going to return us with uh promise actually I'm going to I'm going to write generic so what the shape of this so the shape of this um okay you know what let's create a types. TS file because for this types you're going to hold all the typescript types that that's going to be needed from the response type of Oro so I've actually coded out already and so you don't have to kind of code it out again use typescript right so let's just go to Source let's create a types. TS and I'm going to copy in this whole thing okay so like you should just copy from the repository that I've linked down below but the main point is that we get a sync response which we get back a sync updated token a sync deleted token and whether it was ready right we have a sync updated response which the next page token the next data token and a list of email messages and we have the email address schema which has a name and address and this is how email message look like so I've already looked if I I shown you this just now if you go to the just Json file that I show you just now we have everything that in here we have the trat ID created time we have the system labels I rype everything so you have to worry about it we have the email address we got the email attachment the email haters and so this all the types you will need in the project so I don't to manually type this out so you can just copy this okay so with this uh I can then safely say that whatever this will return me with a sync response cool right and then finally you can see if I hover over this it's going to return a sync response and if I do a wait start sync this sync response I can see whether that's ready whether it's updated whether whether it's a deleted token so if you go back to our sccal draw I really want to make sure you guys understand this and not just blindly coding it out so remember the first step is hitting this initial sync end point right and we're going to check if it's ready if it's ready we're going to get the Delta token so this Delta token is going to point to the latest Mage we send here so The Bookmark at 2 days ago or how many however many days ago you pass into this right so it's going to return as The Bookmark at this point of time okay so so let's do this let's do that let's do that so start sync we get back to initial sync process um perform initial sync okay so we're going to check right while not sync response. ready right so while it's not ready we're going to keep hitting the end point until it's ready we're going to just await a new promise we're going to wait for 1 second before hitting it again right so to give it some time so we'll just set the sing response to AIT start syn again so at this point once we exit the loop that means that sing response is 100% true is ready so now we can access get the bookmark Delta token thing okay and then to do that let's actually have a store Delta token initially it's going to be a string and we'll get that to be sync response. sync updated token okay so this is the first ever token that we got here so now that we have this token we can hit the second end point which is this/ email/ sync updated to actually get back the records as well as the next page token okay and so to do that we have a let updated response right equals to await this do get updated emails so let's write this function to actually hit this second endpoint here okay so this get updated emails will be an Asing function get updated emails and then we're going to take in data token and the page token from uh this object because we can see here in the documentation uh what's this here the query programs require the data token and pit token so we'll just take this in as prms so that we can then uh we can have a prems this is going to be a record from a string to string right so because this can be optional like they might pass in one of these or both of these is possible as well so I'm going to check if Dela token I'm going to set the params to be this but if there's also a page token I'm going to set the params to this right and finally I'm going to check I'm going to just hit the response await x.g I'm going to get a sync updated response from the type here so this type is we're going to get back the next page token and next Delta token alongside with the records of email all the emails and let's actually hit htps uh api. or. email/ SN SL SN SL updated okay and then we can pass in the haters to be this and we can pass in the params to be that cool cool cool cool cool cool yep so we passing the access token here the p and can return response of data so let's continue writing the so we're going to get updated email we're going to pass in the Delta token to be the store Delta token from the initial sync response okay and then we can check if updated response. next Delta token so if there's a next Delta token that means that the Sync has completed Sync has completed and we need to take note of the latest one and this is what I meant by keeping track of the latest Delta token this is what I meant by that so if this has completed we're going to just put the St Delta token to be updated response on next Delta token and then we're going to have we're going to have a list of all emails to be um a list of email message right going be an array of them we're going to take the updated response. records right to be like this so This records is a of email messages and then we're going to keep looping in the next page because remember this API is giving us this messages in batches so we need to keep kind of checking whether this's next page under that isn't so we'll fetch all pages uh if there are more okay so while updated response. next page token I'm going set the updated response to be await this. updated emails passing in the pitch token okay and then I'm going to just concatenate everything together I'm going to do all emails concat concatenate the updated response. records records and I'm going to check again if there's a next Dela token that means this has SN sing has ended I just want to make sure I keep track of the last data token that I've seen in all this request and finally I can do console.log initial sync completed uh we have sync all emails. length email so it's like 50 emails 100 emails Etc and then obviously to store the latest Delta token for future increment incremental things right so yeah so for example for example if you want to con like get the next batch of email that comes in in the future we can just do await uh await get sync updated email a get updated email and pass in the data token to be the last scene data token and this will give us back like the next p the next kind of results right so hope this makes sense and finally I'm just going to return the list of emails to be all emails I'm going to return the data token to be the the latest data token so I once I get back this stuff I'm going to just catch the error right so if it's a exos error I'm just going to console lock it out like this cool okay so now that we have this perform initial sync I'm going to come back to my initial sync endpoint and I'm going to do I get to do cons email accounts. perform initial sync right so now there's this method on it and it returns us with a A list of emails or data token so I'm just going to check for the response so let's come back here blah blah blah blah blah cons response so if there's no response that means there's an error that has been shown I'm just going to return um sorry if there's no response return next respon saying that fil to perform initial sync as a internal server error if not I am going to then do I'm going to get this uh emails I'm going to write it to the database so I'm going to just and destructure the email from response we're going to extract the emails as well as the latest data token right and then I'm going to write this function to actually start syncing my emails to the database so uh before that I can also do await db. account. update right so we need to now keep track of the latest data token we've seen what I'm going to do is I'm going to come down to my schema. Prisma I'm going to search for my account sorry not this is schema model account here I'm going to keep track of my latest data token so I'm going to say next da token so it's a bookmark it can be optional I'm going to run Prisma DP push so this will update my database column it will add this column and with that I can come down here I can update where the ID equals to the account ID all right uh and then where what do I want to update on to update the next datta token to be the Dela to the latest data token that I've got back from my syncing response okay make sense and then at the end we can just return a next response of success equals to true and I can say console. lock sync completed and I'm going to log out the next Delta token uh sorry Delta token just to be just to be itic okay so now let's actually uh let me see okay you know what I'm going to do I am going to just remove the update right and I'm going to just console. lck out the emails for you guys to see okay so let's try this and see if it works so remember the flow is first it's going to go to oringo is going to send us a call back in the call back in the call back oringo we are basically uh inserting a new account and then we're going to trigger an initial sync so then this initial sync so hit this endpoint and then this endpoint should then uh use the the method that we have written to get back a list of emails from their current inbox and they should conso the log it out so let's see if this will work uh but before that let's actually add this initial syn route to the public RS so let's just do /i/ initial sync so that we can actually hit it from our other endpoint okay and hopefully hopefully we should able to see a list of emails okay so let's come back here I'm going to go to my to my slash account I'm going to do Link account I'll try again I'm going to link to this blah blah blah let's actually continue so this going to hit my callback then the Callback will create a new account then after that it's going to hit my initial sync so let's see so you can see blah blah is getting and boom see initial syn triggered success It Go true the syn has completed and this is the next data token that we got right this is what we conso the L out s completed and now we get a list of emails wo so you can see the email has the ID Trad ID created time send at received at blah blah blah has the body which we can pull so that means everything works perfectly we got now a list of emails right I mean the body is huge but at least now we can write these emails to our database okay so let's actually start doing that let's actually create our email table first right if not it's kind of worthless and you know I'm actually going to I will have to kind of paste this in because it's a lot of code but I will walk you through it don't worry okay uh okay so I copy in a few tables I copy in uh one table copy in threats so we have a threat table we got email table we got a email label table we got email address table email attachment and sens and we have some enums to represent sensitivity and the meeting message method okay and there are some errors here because you can see it's linked to the account right but really for the account we need to have uh uh the threads to be a list of threats and we also need a list of email addresses to be a list of email address so I'm want you through each table to show you what is actually for so you not confused by it okay let me see if there any other stuff no I think we're we're doing really good we're doing really good Okay cool so let's start with threats okay so a threat as I say is a collection of of messages so a threat will have the main subject so the subject is probably the latest email in that in that that threat and then the last message date we need that we need the kind of the the participants the list of email addresses that's within that's participating in this threats obviously uh uh the thread is linked to an account right an account can have a list of threats and then there's a Boolean whether is done so that we can like classify them done or not done so we inbox data so is this TR in the inbox so if I show you my code here okay let's take a look you can see I have inbox draft and send section so how I'm classifying that is uh whether it's inbox whether it's true whether it's in draft whether it's true and whether it was in sent which is a Boolean as well and yeah so this is how we classify the threats and obviously a threat has a bunch of emails and we're just putting the index so that we can faster index then because we have a lot of threats and make sure you have these indexes because if you don't have these indexes your query speeds are going to be absolutely like horrendously slow okay and then an email is pretty self-explanatory so these are what we have seen in the types which I've explained and I'm just putting in the database right we have a list of who is it from so each individual person is link to an email address so u a from has a list of is from one email and it could be to a list of email address it could be cing many emails it could be Bing many emails and it could be in replying to many emails so that's why you have this email addresses and then it will have a list of attachments right and an email label which says whether it's in inbox send. draft right and the email address obviously is like pre- self explementary it's just a pairing of the name and address right and obviously it's linked to account so the reason why we need this account ID linking is because if you go down here and let's on the compos an email and you see all this emails that I'm seeing here this is the list of email addresses that has been linked to this account so each individual role here is one email address rle in our database yep so that's why we get back this and then emot attachment we're actually not using this you could use it but I'm just putting it here for completeness sake cool so now that we have this let's actually create the table so bu Prisma DB push okay so it creates a new columns okay cool awesome we are doing on track so now we need to actually write the function to get the emails and sync it to the database so I'm going to create a new file uh new thing called sync to db. TS so this is just uh a file where we're going to write all database stuff to write the emails to our database so it's going to be a huge file a lot of code but once we're done with this is like the bo of the project right okay let's actually start coding so we're going to export export a asnc function called sync emails to database right so we're going to be calling this function in the initial Sync here right we're going to sync a list of emails and so we have to import the email address email address from our types our custom types right which is this oh sorry not email address it should be a list of email uh email wait what do I call it let's go to types uh email message my bad yeah so it's a list of email mess message and can now see the types are lined up right and then now that we have this email message what I'm going to do is I'm going to console. lock uh attempting to sing syn email to database I'm going to just log out how many I'm actually sinking okay and then what we're going to do is we will import another function a library called p- limit so because we're going to try to like okay imagine we have like 100 emails right we're going to try to do promise to all emails right emails. map email process uh save email right you can imagine like we are basically sending 100 concurrent database connections at once to our database and if there's more than 100 emails like it could be more we're going to basically cook our database if we don't have some kind of rate limiting function and that's where P limit comes in so p limit stands for promise limit so we will just help us send this uh kind of concurrent request in batches to our database uh sorry concurrent batches in promises so that we can better handle like concurrency and even crash our database and crash our server so I import P limit from P limit and then what I'm going to do is I'm going to define the limit to be 10 so I'm going to send promises 10 batches at a time right so even if there's 100 emails here right it's only only going to have 10 batches at batches of 10 at a time okay and so what I'm going to do is I am going to just wrap the whole thing in a tri catch block right and then if there's any error we just console.log oops with the error but we can just kind of ignore this for now so let's actually write the function to save the email okay so obviously obviously obviously uh apart from the emails we also need the account ID right cuz we are we need to link the email the emails and the threats to a specific account so we also need the account ID to be passed in so let's go back to our initial sync and let's pass in the account ID as well because we have that anyways so let's actually write a function here called upsert email passing in the uh email and the account ID so let's have a Asing function upsert email this is going to take in the email and the account ID and you know what I also want to see the index of this happening so I'm going to take the second program of map and I'll pass it into the index here so I can get the index so this is just so that I can say cond console.log upserting email index so if I have 100 I get to see like upserting email one upserting email 2 blah blah blah all the way to 100 right just for like some sanity checking cool so here let's actually write the logic of of kind of determining the label type so let's wrap everything in a big Tri catch uh then we're going to say let email label type so when you label the email so it could either be in in inbox or it could be in sense or it could be in draft so by default let's put it in inbox right so we're going to check if the email. system labels do includes inbox or the email do system labels includes uh important important what I'm going to do is I'm going to set the email inbox type to inbox right then I'm going to copy this here then we're going to write the else if if it includes send the email label type is send if it's in the draft we put in draft right and then we going to do is uh we want to then get the list of email addresses like individual addresses to be written into our database right so we'll just get back like so we're going to get uh cons addresses to upsert so remember upsert just means update or insert so if the email already exists in our database we're going to update it if that isn't a current row we're going to insert a new row okay so it's going to be a new map so like I'm just trying to like filter it out so that imagine you have like a lot of you got a list of emails I want it to be unique so that when I insert the database it is like purely unique and it doesn't cause any issues okay so just follow along with me so I'm going to do for cons address off it's a big array of email from so this email. from is going to be a email address view right and this email address view is going to look like it has a name and has address right this is the shape of the email address right and then we need to also spread out the email. to right cuz that it might be to a lot of people we also spread out email. CC and BCC as well as email. reply to okay so we're just trying to get back a list of email addresses and then finally we're going to set we're going to map it from the address to the actual address here so this is going to be a mapping from strings to an object so you see this object is email address which is this field and this address. address is a string so I hope this makes sense and then we'll have um we're going to do four cons cons we want to upsert uh an address right so let's actually write a function to upsert an address so we have Asing function upsert email address so it's going to take in an address which is an email address and then we have the account ID which is a string so we're going to then link it together so we just put a TR catch block so if there's a error just do it like console block fill to upsert email address we return now right but if if not we're just going to just copy this in so I'm going to import database right so we're going to first see if there's existing address by looking at the compound key account ID and email address right we're going to see address um what's this email address wait a minute oh right okay you see this email address type it shouldn't be from clerk this email address should be from our our types okay so remember to import it correctly and then we will then check if it has already exist if there's a row we're going to update it if not we're going to create a new email address VI okay and then we'll use this function here to actually create the email address blah do set and then we're going to do cons uh sorry let's do H for cons address of addresses to upsert the values so we get back the list like the filtered list of items here and we're going to do cons upsert address equals to await upsert email address passing in the actual address and the account ID right and then this will return us with the address right we want to save we want to save the list of database transactions that we got because we're going to use it later on so what I'm going to do is have a list a new list called cons um address mapping sorry address map it's going to be another new map but this map uh sorry not this map it's going to be a list right so uh I'm going to do is Con uper the addresses it's going to be a list and what's the type of the list the type of the list is going to be awaited so it's like typescript stuff so awaited return type we're going to take the return type of the type of upsert email address so let me explain what is happening here so upsert email address is this function that we have written so you see this email address upser email address we get back we're trying to basically insert this into the list right so we can see that it returns a promise of this shape so we're taking the type of the return type of this function and we're going to await it so this is for typescript to like unwrap the promise and so we can now see that this has the exact same type as here so then we can finally do upsert the address address. push into this array cool and then from this we're going to get the actual cons from address because we need to know who is this email from this is important to us so we need to do address map. getet uh sorry uh we need address map yeah address map equals to the new map right and this is going to be from upser address uh dot kind of filter do filter Boolean so we you filter out all the stuff that is not that is like knowledge in value and let's map the address to the address do address we know that it will always exist because of this Boolean right and finally we can get the from address equals to address map. getet email. from. address so I know it's like super complicated but honestly it's just code that needs to be written to kind of insert stuff and fits data into to like from our typescript schema into our database schema so there's not much like really interesting code you can just copy it from the from the geub repository and like in fact that's actually what I'm going to do I'm going to copy in some code uh cuz all you have to do all you really have to know is that this function is going to take the the response from oringo and it's going to insert it into our database into our tables so like nothing much that needs to be done I'm going to just quickly just I'm just going to quickly uh how say copy this in and then we will be good to go okay so let's go to the next part so we'll get the next will check here form address so if there's no form address we're going to just push an error so we again get the two the CC's the B bcc's and the reply two right and then we're going to then insert the threads okay so let's actually insert the threads boom boom boom nothing interesting here so we're just going to do await db. threat. upsert so we going to find the email. threat ID we'll update the information right with the participants and then we'll create it if there isn't a thread that already exists and then find like lastly we need to upsert the email we got this giant piece of code here that's needed um so after we upsert the threat we need to upsert the email like really I'm just like putting stuff like you see subject equals to email. subject like this is boring kind of data mapping stuff the same for the create you just got to do this you know so I already got the boring stuff done for you guys you just have to copy it so then after this we're going to get back to threat emails right then we're going to classify the threat based on the emails inside so if any of the emails is in inbox we want we know that the threat the whole threat is Inbox but if the threat only contains emails that in the sense field we know that the threat is also incent right and then finally after updating everything we can then upsert the attachments right and let's actually get the function to upser the attachment here so it just takes the email attachment where we can import it from our types and we're just upserting it into our database yeah so that's the whole file like I this so use very big but 239 lines right but now the magic Park is we can just call this function pass in the emails from oringo pass in the account ID and this should then start writing into our database and let's actually see in action okay so I'm going to just I'm going to just uncomment this I'm going to update the account I'm going to no longer comment out console the emails right but I'm going to see this in action so if I come back here to slash hopefully if I go through the flow I should now see like data in my database I'm going to just restart my server just in case something needs restarting I'm going to go connect this funds go to orle so this going to go to our web hook then it's going to call Initial sync DB so this will immediately put us into mail but the thing is it's going to start syncing my emails in the background which is like super cool so now if I go back here you can start seeing things happening so uh blah blah by the way there's a there's an error that I noted just now which is when you're upserting email address if there's an existing email address you should just return the the found email address you shouldn't try to update it okay because we have like some rise conditions happening so let's try again so if you go to slash and now let's actually Link account let's link this let's go to the pro process so remember it's going to hit our web hook the web Hook is going to get all the messages going to hit our initial sync to DB and now hopefully we see in our console things should be happening blah blah okay one more issue I found with is that it's actually not very kind of good to do a promise to all and upserting the emails like at batches at the same time because we're going to face a lot of race conditions for example when you're like even if you're like sending in like five emails at once at a as a batch right the batch may have like a lot of different addresses that's being sent in right like from address to address and so it's going to cause a lot of race Condition it's going to break like the Prisma at functionality so what I'm going to do is I'm going to remove the promise all in fact I'm just going to just await uh await and insert them sequentially okay so now let's actually go through the process again so I'll go back to slash I'm going press Link account I'll connect this email address Advance go to orle let's continue right and hopefully will bring us back to/ mail and it's going to sequentially kind of read through our email address and update them so hopefully I should able to start seeing stuff happening so you can see updating upserting email address uh yeah in this case you can see it's like zero but it's fine so in this case you can see that syn has completed and we got triggered so I think this we can also get the index right um you know it's okay it's okay let's just leave it like that but now that we can see we upseted how many addresses we can we upser 21 email so go back to Prisma now if you see we can see we have 21 emails in our database we have 21 sorry 11 email addresses which are literally just like the names and the the addresses and I can see the list of emails that has been sent from here right and then we got the threads so the threats will contain the account and the list of emails that actually is in the threat cool stuff so we have successfully done it we successfully kind of uh read in the orle API to get the emails and sync up our emails from Gmail into our own database and we have done the database engineering and now we can actually do some cool stuff and like make some U eyes but before that we actually need to do one last step which is to configure the web hooks so that we can continuously monitor for changes in the Gmail inbox and whereever there's a change in the Gmail inbox oringo should be able to send us a web hook in order for us to continue keep our to keep our database in sync so let's do that in the next section Okay cool so now that we have kind of our database setup we have our threats we have our emails let's actually start doing some fun stuff and actually display it on the UI so let's actually start building the UI first okay so we're going to be basically building this kind of inbox form right and so okay let's begin by by going back to our vs code okay and let's go down to let's just clear out everything first let's go back to our mail page so instead of exporting a mail dashboard I'm going to just create a new component component called um mail um m. TSX okay so inside this mail. TSX I'm going to just export uh just do RFC I'm going to export a function called mail right and so going to do is I want to basically build this kind of a sliding layout so you see this resizable panel so um what we're going to do is going to build that resizer p panel so we're going to import it from shn itself so we going to import from at/ components slui resizable and let's actually get from resizable handle resizable panel and resizable panel group okay so the whole thing is it's going to be wrapped in a kind of tool tip provider first uh so we need the two tip provider because uh down the components we need this provider to be able to use the to tip but other than that right other than that we can actually put the resizable panel group here okay and we'll have the direction of horizontal okay and then we have a on layout right so we're going to just get the sizes from here boom like this so every time the layout changes like we drag it around right we're going to get a callback to get the sizes so I'm going to just console lck out the sizes for now and then within here I'm going to give you a class name of items that's stretch hit full and mean height of screen cool and then inside here we'll have our first resizable panel right we have a default size default size of let's say set a variable up here so let's actually let's get some props type props equals to default uh default layout right it's going to be a number array or it could be undefined so let's actually export this out and so by default if they didn't give us any uh default layout I'm just going to have 20 32 and 48 so what this means is the first the on the left side is going to be 20% of the screen the second layout is going to be 32% on the screen and last is going to be 48% so by default I'm going to take the first item here and then uh I'm going to have a collapse size so we also need kind of whenever the thing has been collapsed we also need uh because this is on the left side right these are the nav bar so we have a nav collapsed size which is a number right so by default let's actually let's actually require it to have the number so kind of we'll just do collapse size to be nav collapsed size um and then we have to make it collapsible right so you can actually drag it all the way to actually collapse it like this cool and then let us let us do Min size equal to 15% and Max size let's do 40% okay uh on resize we also need to kind of do some logic here but we'll do that later on and then lastly we have a class name right so let's just have import the CN module for now we just have a mean width of 50 pixels right transition all duration of 300 is in out so this is for the animation when we kind of collapse it okay and then within this resizable panel div with the class name of flex Flex call height of full Flex of one right and then within this div let's have another div so this div is for the account suure here right so let's have a account sorry account suture right so for this we'll have a flex height of 52 pixels right and then we'll have items of Center justify center right and I just leave it here for now and actually we also need to track whether the the sidebar has been collapsed so let's also have an another kind of variable called is collapse right so by default by default is going to be a false right we can also pass in another prop here called default collapse which is a Boolean and we can pass this in like this right cool and so actually with that uh set collapse we can actually also do some stuff here so let's see let's see collapsible true right and then we have on collapse on collapse we're going to just set it collapse to True okay and then on resize we're going to set on collapse set is collapse to false okay and then here with the class name here we can also track some stuff for example if it's collapse uh actually you know what this is only applicable this whole class name is actually only applicable if the whole thing is collapse so we can just do something like this so yeah only if it's a collapse then we'll apply this classes right and then inside here we have this Flex right so let's also make this a class name component here and then we'll have a if is collapse right we're going to have a height of 52 pixels right if not we're going to have a padding X of two cool and this is where we're going to put our account switcher so for now I'll just put account switcher as a placeholder so let's actually go back to logo 3000 let's see how it's going so let's actually import and use this component inside our mail page so let's just return mail like this right and then we need to pass in some default props right so let's actually pass in um default l out it's like this default collapse equal to false and when it's collapse let's put it to four okay and you still only work in client component so let us make this a use client component and boom now we have this right we still can't see the the the resizable panel just because we have not finished kind of the component Okay cool so now that we have that let's actually continue coding out so under the account switcher div right outside of this div we're going to have a separator coming from our component and then we have a this is where we're going to put our sidebar so let's just put a mental note here and then we have a flex one to actually expand all the way out and this is where we'll put our AI so ask AI so this is the part where we'll put this thing here this ask AI part here right so we have the account Swit we got the Naf sidebar and then we got the this huge spacing here and then we have the AI section cool and then let's actually go back so let's see because right now we have no other stuff right that's why it's expanding all the way cool and then let's see what I'm going to do here is move down here let's actually continue coding out the other resizable panel so under this this let's have a resizable resizable handle like this with hand so if we go back here we can now see okay it still expanded all the way oh but now we have this here right so actually let's see the error no yeah it's actually fine we just need another resizable panel right resizable panel okay and this resal panel we have the default size of layout default size of the default layout index one the minan size will be 30 right and then we're going to have a bunch of tabs so you're going to see here right we're going to have this tabs here we have the Inbox and we have got the inbox Tab and the done tab okay so for now I'm just going to just yeah wrap this into this tabs okay and then we have a default value of default value of inbox okay and then inside here we're going to just have a div this div will have a class name of flex items D Center padding X of four padding y of two okay and then we have a H1 that will have that say inbox so we have a class name of text XL font dashboard so now we can see now it's starting to look correct right we got the inbox here cool and then I'm going to do here is after the inbox I'm going to have the tab list I'm going to import it from components right we have a class name of margin oh class name of margin L of Auto and then we have a bunch of T trigger inside here we have a value of inbox and the class name of text zinc 600 and when it's dark mod I'm going have text zinc of 200 okay inside this T trigger I'll have it inbox so now this looks more like it right so D value of inbox cool TS list TS trigger Y and then we'll have another TS trigger down here that's exactly the same as this except that the value is going to be done and the value here is done cool so now you can see this things are starting to happen okay awesome awesome awesome so we have the value here blah blah TS list inside the TS trigger and we importing everything from okay so the tabs trigger has to be from at/ component okay so now it looks right I'm going switch in between like this cool and now let's exit the div right let's exit the div um so outside the so above the tabs let's have a separator okay and then this is where we're going to put our search bar bar so let's just put search bar for now so this is where we're going to put our search bar um actually no this uh actually why is it outside no actually it's correct yeah you see outside the separator below the separator is our search bar right and then below the search bar we're going to actually put the content the text content which is this section here okay so let's go back here tabs content we have the value of inbox and then we have another Tex content for the done page so we're going to say here we're going to be inbox done so now you can see here I can switch between I get to see my search bar and my done and my inbox cool so now let's build out kind of the final panel here on the right side okay so below the resizable panel right we have another handle and then the last resizable panel right right we didn't know the default size is going to be the second index the main size of 30 and let's actually put the thread display so this is where we'll display the threads right here cool so we have buil out the skeleton right and now we're going to go in step by step starting from the account switcher all the way to the sidebar and we're going to start displaying the threads cool good job guys let's continue cool so now that we have this done let's actually create the left side first so let's actually work on the con switcher but before that I want to kind of talk you through the data fatting strategy that we're going to be using which is through trpc so in at the beginning of the video I've mentioned that trpc is an alternative to the rest protocol meaning that that we will have end to endend type safety when we are fetching data from the front end to the back end and trpc is built on react query which means that it will give us the power of uh kind of refresing caching and everything out of the box right so when we did create T3 app in the beginning to breed out this project it has given us the boiler plate code for the trpc uh functions so now what we have to do is actually use them so I'm going to give you an example so let's go into trpc um so this is the rout Handler for trpc by the way so if you go into Epi router. TS right this is the post router meaning that you are able to hit hello so this is kind of a root imagine this is like a rest end point this a rest endpoint so we're going to say that this hello is a public endpoint right and then this public endpoint will take in uh an input of an object and the object has this shape of text which is a string right and then we're going to query this and we can able to access the input and return a type save input of input. text right make sense so we're going to be using this kind of methodology to start fetching data so the first thing we're going to do is under our routers folder let us create a account. TS uh yeah so for this account. TS I'm going to export cons account router equals to create trpc router right so when we do create create TR sorry when we do create trpc router we're just basically grp grouping end points together into like a certain uh router that's even how it works in Express right so I'm going to just get my kind of rep reference for getting the accounts okay so the first thing we're going to do is uh we're going to do get accounts so this is going to enable us to kind of just uh get all the accounts back for a given user right so in order to that we need to kind of protect this route so to protect this route we're going to go into our trpc dots so this under server API trpcs right you can ignore most of this stuff but come down here to the bottom right because we're going to create a protected middleware okay so we're going to just create a function here called is off right this is going to be a t do middleware so this middleware is going to take in a few it's going to we can destructure the next as well as a context right and then this we're going to check if not CTX if not context dot off but you see we have no authentication kind of thing here and so where does this come from so this all comes from if you scroll up here right we can see we are going to create a trpc context so the context is being created through this function so this context is what sits in the middle of our request and our route Handler okay so in order to get the authentication shape what we're going to do is let's actually see this where is this okay cool I see it here so we can just get cons user equals a weight off so this off is a function that we have been using from clerk uh from Clerk and then what I'm going to do is other than the database I'm going to also return the off to be the user object right and now that I Expos it in our context if you scroll all the way back down here and I can do command control space I now see our off uh data in the context so I'm going to do if CTX do if not CTX off. user ID that means they're not authenticated I'm going to throw a new error saying that unauthorized if not I'm going to just call the next function because it's a middleware right so I'm going to call the the function after the middleware right I'm going to pass in the context to be uh whatever context I have from here but because I know the user is 100% going to be here I'm going to pass this off to be CTX do right as required type of CTX right so what this does is saying that because we have already checked that they are authenticated when we return we are saying that we telling typescript that whatever value of O is they're going to definitely be like there not going to be n or undefined so we're going to do this required so this is from typescript so now that we have this is all function we can export a other than the public endpoint we can do a private procedure so procedure is just a Endo so you can Define t. procedure we're going to use the EO middleware so this Endo that uses the isos middleware so whatever procedure that hits here we're going to make sure that they're authenticated already okay so now if you go back to kind of account. TS under the routers right we can do um get account equal to protected procedure procedure right and we can import this from okay private procedure yeah we call it private procedure right and then we can do do query because we're actually querying this endpoint Let's do an aing let's actually unwrap the context in here all right we're going to return await cx. DB so the database the Prisma database lives in the context variable as well account. find many where the user ID is equals to the context. off. user ID so you can see we're matching it here based on the off that we pass into context and quing the list of accounts in our database right and for this I'm just going to select I only want the ID field I want the email address field and I want the name field okay so now how do we actually query this endpoint right let's go into let's actually go back to our mail page so let's actually work on the account switch right so for this account Swit it's going to be a select right and then we're going to put in the list of accounts and display in a drop down cool so if you go into our search account switcher right we have this account switcher let's actually create create a new component uh let's create a new component right here called account that's switcher dots X cool and with the account switcher what I'm going to do is I'll just have a uh R fce sorry TS RFC account switcher right so for the props I need to know whether the sidebar is being collapsed because if it's collapsed I need to just kind of hide it away make sense so I need to have that access to the props is collapsed and then I'm going to do is I'm going to get back list of data from API so you see API there's two apis one from react and one from server so I'm going to get the one from trpc react because we're calling this from the client side so to make this we have to also put use client on top here so if I do API dot we can see a list of stuff but I cannot see my kind of uh account router so to add account router I'm going to have to go to roots. TS under my server API right I'm going to go to account I'm going to just put account account router in here so just ignore everything out here the only thing you need to know is whenever you create a new router right you need to add it into the the basically app router so that when you save this you can now have access to the account here and if I do space I can see the get accounts function that we have written here so this is the private proced procedure do get accounts and going do do use Query so this is the react query part of this and from this I can get back data variable and you can see this data is the exact data that's being returned from the function that we have written here get account so if press GD right which s for go to definition right I get to see uh I get to directly jump into my back end right so Isn't that cool cool so now that we have this data I can actually just just I'm just to display for now I'm going to just do um data map account right I'm going to just return return a diff that has the account. email right and we have also a key and then I'm going import this account switcher here passing in the is props and so now if you go back here we can now see we got a list of emails but they are sight to S side but we actually want them in a drop down okay so to put them in a drop down I'm going to come down here so the first step is we're just going to first check right um if we don't have data that means this component is still loading because this data will not be initially available right because we're still fetching it by the moment it fetches it's going to come back with the list of accounts so we're going to check if there's no data we're just going to return now first to kind of not render anything and then instead of rendering this we're going to render a select right and then within the select we have we're going to have the account ID so let's have account ID right and how I'm going to set this account ID is I'm going to instead of using react. state right which is uh temporal temporal meaning that once I refresh the page the state is gone right I'm going to store this state in local storage so there's a very handy hook here that we can use so if we just do sudo but install um let me see use hooks DTS so this use hooks DTS Library gives us a lot of nice utility hooks for ex for example one of the hooks that it gives us is use local storage so this use local storage is a direct replacement of use state meaning that I can store I can store the key here as which logo storage key I want to store it as and then I can give you an initial value of of stuff so now whenever I change the state here it's going to be stored in my logo storage so whenever I refresh the page or I close the tab and I come back the local storage value is going to always be persistent in that tab so the reason why I have this account ID is because if I change it to let's say another account and I refresh the page it's going to remain at that account even though after I switched it because everything is St in local storage and so it is just persistent in the browser okay so for the value we'll just have a default default value of account ID account ID and then on value change we're going to set the account ID so this is the exact same format of of react use state so if you understand use State you can literally just replace use state with use local storage and you immediately get the benefit of persistency of the Box Okay cool so now in here let's actually have a select uh trigger select trigger from components and then we have a class name of of CN right we're going to check um some actually just copy this because it's a long class name like this okay so just copy this from the giab repository we're just doing some styling with the internal span and svgs right and then down here let's actually have a select value in here and the placeholder is Select placeholder select an account okay and within here we're going to have a span and the class name is going to be right if we're going to basically hide this is hidden soorry hidden if it's not is collapsed so we are saying that if it's not collapsed we're going to hide this and then we'll have another special value for when it's not hidden okay so then we're going to do account sorry data find so this data is basically the list of accounts find account account. ID equals to account ID and we're going to get the email address right and we're going to get the first character of the email address so this is what happens you can see when I Collapse it I can see this o which is the first letter of this uh this email address makees sense cool so below this span we have another spin but this is the opposite right we're going to have a class name of it's going to be hidden if it's collapse so you can see it's the opposite of this where it's hidden when it's not collapsed here it's hidden when it's collapsed right and let's also give you a margin level of two okay true and then we'll have the same but this time we show the actual email address instead of just the first letter then outside of the select a kind of trigger we'll have a select content from components okay and inside the select content let's actually do data. map and then uh for each account we want to return a select item from component s select let's give you a key let's actually make this a curly brace sorry a normal brace key goes to account ID uh what's this data map all right we need to wrap this in this so for each account we give you a value of account uh do ID and then within here uh we can just have a account. email address cool and so if I save this and we come back here we can now see I got a lot of accounts right because because I pressed I try to link it up a lot of times right so uh I'm going to just clear out in a bit uh actually I can clear out now so I'm going to go in here so let's look at the account so this is account 68589 meaning that I need to clear out anything that's not 68589 so let's just clear out everything previous here let's delete the records cool so now if you refresh I can see there's only one account and I can select it and if I refresh it you can see it's persisted local storage right cool and so let's continue so other than here I also want the PE I want people to be able to press add an account here right so let's actually add that so uh just above the select content we have a div right and then we have a plus icon here so we have the plus we actually have to get it from so install Lucid D react so it's just a icon library right so let's actually import plus from Lucid react cool so this plus icon will have a class name of size of four margin rate of one and here we can just below it we'll say add account right so you can see now we have this add account by look super ugly so we're just going to have for this diff let's have a class name of flex relative hover BG gray 50 with a full cursor pointer item Center rounded s and then padding y of 1.5 padding left of two padding right of eight text SM outlined non Focus BG accent right pardon me it's a super long one and I think it's fine yeah so I'm going to just save this if I come back here now this looks the same as the both cool so what happens when we want to press add account we're going to basically use the same function from the link Account button right so what I'm going to do is I'm going to do unclick right let's have this function so remember we have this uh we have this initial get orle off URL let's try that as well so we have this uh link Account button right we can actually just copy whatever is in here and just put it in here cool and let's make this Asing and let's import this so now when they press add account it's going to lead them to that Google authorization page all right awesome so now we have this uh kind of Account button uh done and we successfully saved it to account ID and the beauty of this is we can access this account ID from anywhere in the app and let me show you why I mean so let's actually just create the sidebar component first so let's go down to uh uh here let's actually create sidebar. TSX use client tsfc cool sidebar right so let's actually import this sidebar so let's go back to our m page whever this sidebar is let's actually replace it with the actual sidebar from our component right so we got a sidebar here and let me show you we can actually access the account ID from use local storage and as long as we're using the same key which is called account ID if we get to use the same key I can actually access account ID from this component as well see 68589 and if I change change it here it's going to automatically react here so it's like having a global kind of atomic State Management just through this uh through this to the local storage yeah so let's actually work on that sidebar first right so for the sidebar I also need to know whether it's collapsed right to actually conditionally render it collapsed okay and then what I'm going to do is let us have a nav so we need this nav component so to create that let's go down here nav TSX so this na. TSX um let's see let's see let's see I'm going to copy in here so just copy it from kind of the just copy it from the the the repository but basically what what we're doing is we getting whether is collapsed and then we're just looping through a list of links right so the list of links is like inbox draft and sense right and then for each link we're just having a tool tip right and then we're going to show the link the icon so the icon is what is being shown beside here right and then yeah that's pretty much it so let's save this na. CSX right and then return here so we'll just export this nav component and then in here we'll pass in the is collapse variable and for the links let's have three things so the first thing is a inbox right the label let's just put one example the icon will be the inbox icon from Lucy react and the variance let's put as default so now we should be able to see the first item here and this is the label so by by right we should be able to have access to kind of the number of the threads within this mailbox but let's do that later let's do the same for draft so we have title is draft right and then the the label is also let's say put for for for placeholder the icon will be file from Lucy react the variance is put as ghost and and then lastly we have send right let's just move this and then we'll just put a send icon from Luccia react and then we have the label of six cool so now we got the three things on the side here amazing work guys and now that we are here let's actually also kind of um set the tab right so we're going to have another local storage variable called uh tab to be lose local storage we're going to put it as normal human Dash tab so you can name it whatever you want as long as it's the same name across all your components right because this is the key of the local storage right because underneath the hood what it's doing is doing local storage. set item and putting in normal human. Tab and changing this value every time the state changes okay and we can give you a default value of inbox so depending on the tab we have inbox draft and set right cool so this will be inbox draft or set and so we get a type safety here inbox right and then because we have this nav this this value now we can actually uh conditionally render the the variant so if tab equals to inbox we're going to put as default and for this if the tab equals to dve we can put at default and the same here okay and so now we have this local storage and T there's one more caveat that needs to be to be careful when you're using this uh local storage which is that we need to actually dynamically import this right so if you go down to um nav TSX sorry we go back to mail. TSX right we can see this component is being used here right sorry there component is used in the m page we actually need to dynamically import this so cons Mill equals to Dynamic from next so we're going to do is return import do/ Mill and we're going to set Sur side rendering to Bea so because by default all the components even if you mark it explicitly as uh as used client nextjs will still serers side render it and try to hydrate it and stuff like that so to make sure server site really don't touch this component we're just going to make this purely client site by turning the server site rendering to be false okay so now that we return to the page right we can see that our kind of tabs work again right and is being persisted across render so I can go to draft and if I refresh wait sorry if I go to draft and I refresh the page we can see that it's still UND draft right make sense cool and so now let's actually continue on coding soah so one more thing is go back to your kind of mail page so you need to add your kind of e collapse pass it into the sidebar component okay so now let's actually kind of build out let's actually make this data real because we do have the data right so depends on how many tracks we have per box right so let's actually go into our let's actually create a new uh let's actually go into account our account router so account router right so we have this get account right but let's also have another function called get num threats right so this get num threats will we pass in whether we're looking for inbox whether we're looking for draft or whether we're looking for sent and you will return us with how many threats are in that inbox okay so let's actually do that uh ma let's see get threats Okay cool so get num threats so as always this is going to be a protect a private procedure right we're going to that we're going to have a input right so the input we're going to use zot so Z is like this a schema validation Library where we can Define the input to be an object z. object we're going to need the account ID being passed in right cuz we need to know which account you're looking at right now we also need the tab right so the tab can be a string but obviously it should be inbox send or draft and then we're going to do the query we're going to get the async we going to take out the input and this context in here so we can just this query is going to pass in the context as well as the input and you can see this input is has the same type as the the zot object that we have passed in so that's kind of the magic of trpc and typescript okay and then here we're going to first authorize the account access because remember CTX do off. user ID gives us back the user ID but what if the user ID is trying to access information on account that doesn't belong to him right that'll be that'll be critical so let's have some kind of authorization process so let's go to the top here and let's export a cons authorized account access right this will take in the account ID that we are trying to access and the user ID who's trying to access this right so the first thing we're going to do is we're going to get account to be await DB do db. account. find uh first where idid equals to account ID and the user ID equals to the user ID and then we're just going to select the ID to be true we're going to just take out email address as well we need the name and we need the access token cool and then we're going to check if there's no account meaning that there is no such valid user ID account pair that means that this user is trying to access an account which he doesn't own so therefore we're going to just show an unauthorize error right we're going to just say account not found and then if not we just return the account so now we have this authorized account access we can call it before here so let's do U cons account equals to await authorize account access pass in the input account ID so this input account ID comes from this input here and then the user ID comes from CTX off. user ID so if this passes we will get back a valid account from our database right and now we're going to check right so uh we're just going to try to filter the inbox so what we really want is to return await CTX db. threat. count cuz we're trying to count how many threats there are in the inbox or the draft or the scent right so we're going to do the count and we're going to pass in where right and so how how we determine what is so account ID will always be the account ID that we're filtering by and then we're going to check uh inbox status equals to either true or false true or false right so if we're looking for the T the inbox tab we're going to filter out by uh whether the inbox stus is true okay and then we'll do the same for draft and for set okay that's cool that's cool and then and then let's see let's see if this will work yeah actually we can extract this out as well right because we can just have a cons uh we can have a filter right this filter will come from Prisma dot uh this is the the type of this is thread where input right and this going to be initially empty so if input. tab equals to inbox right we're going to set the future. inbox status to be true if not going to set draft status not going to set St status and then here we just put we're going to spread out the filter right so this is going to conditionally filter out the inbox draw status and send status based on the input that we're going to give it in so now let's actually use this by going back to the account switcher okay and so what we're going to do is let us get kind of um the list of we're going to get the number right so let me see let me see account sorry not account sidebar right cuz this is where we are kind of calling this function so for the first thing we're going to do is we're going to get data and we're going to rename it to inbox TRS equals to API so API imported from at TF PC react API do uh account. getet num threats and going to you query it by using doing get use Query and then this query I can get back account ID to be the account ID up here and then I need a tab right the tab can be inbox right so this inbox threads will give me the number of current early thre current threads in the inbox tab okay and then we we'll do the same we'll do the same but for draft and let's rename this to draft threads and we'll do the same for send so let's rename this cool and now that we have that can replace it with inbox threats draft threats and send threads so with this we can see that the data comes from the the use Query hook so if we kind of come back here we can now see that this is accurate information right so inbox has 13 trats in there my send only has four and my draft has zero which is accurate awesome so now that we have built out the kind of left hand s right now let's actually move on to building uh the the list of threats okay all right so now let's move on to actually displaying the list of threads so we're going to do this middle section here the one that's kind of under below the search bar right here this stuff here okay and so what we're going to do is let us come back to the code so the first we're going to write the function to actually uh get the list of threats by the tabs right so we're going to go back to our account. TS router and it's going to be a very similar filtering output from here right so let's actually come down here and let's do get threads right it's going to be a private procedure where the input is going to obviously we're going to have the account ID right so which account are you looking at we're going to have the a tab the c. string right okay and then we're going to have the done filter whether we have completed right so it's going to be a bullan whether it's true or false right so we have two things to filter we have this filter by the tabs and we also have filter by the Done Right therefore we need this two kind of filter variables here okay and let's do a query let's actually get back a this the context and the input cool and now that we have the context and the input right so the context always contains the user ID and the database and the input contains the input as passed on into this function so the first thing is let's actually authorize the account access using the function we've written here and let's have the filter which is also very similar right we can actually copy it directly here that filter okay and then what we're going to do is we're going to do filter DOD equals to uh equals to this equals if input equals to input done yeah so we're going to do an additional future on the done column right whether it is equals to the done field that's pass into the input okay and then finally we can then uh get return await uh CTX db. threat so we're going to quy the threat threats. find manyu all right so where would be the filter right right cuz the filter is is exactly the the for the threat where input and then we're going to include the emails cuz each threat can have emails right so we're going to do an internal joint right within Prisma we're going to order the the emails by the send at field ascending order okay and then we're going to select only a fi field few fields from the emails each email we need a the the email from the body right we need the body snippet right we also need the email label we need the subject uh subject we need the system labels and we need the ID and we need to know when it was send that okay cool and then we're going to only take the latest 15 threats right wait actually here yeah we're going to take the latest 15 threats and then we're going to order the threats by the last message date in the descending order okay and now that we have this we return it directly okay so now let's actually uh use the data written by this get threats so what I like to do is to actually put because we need we're going to be using this threats data a lot in our kind of app I'm going to create a custom hook right to get to actually allow us to access the list of threats that we are looking at so let's go into to hooks let's create a new hook called use- threats. TS right like this so use TR is going to basically call the function that we just wrote The backend JPC procedure and it's going to save it in a variable cool so for use strats let's actually just do our fce use strats and then so we're going to need the kind of we could just fetch all the data that we whatever need in our app so the first thing is let's actually just fetch the accounts right cuz we need this somewhere else so we'll do API from react so make sure that this very common mistake make sure you're importing API from react instead of server if not it's not going to work okay so import api. account. getet accounts. usequery right and so we rename it to accounts and then we're going to have the account ID right which is going to be use local storage and the key is account ID initial value is empty screen and then we need a tab right is use local storage normal human tab so this is one we Define in the sidebar right so this is the tab and this is the done right and then um we're also have a done field which is going to use local storage and we'll have a normal human done so this is done and this is tab as I mentioned just now okay and finally let us get a uh we need the query key right yeah yeah so no actually let's just do this first so I'm going to get the data I'm going to rename it to threats and this is from the api. account. getet threats function that we just wrote so what do we need to do use Query we need to pass in a few input right we need the account ID we need a tab and we need to know whether it's done okay and then I'm going to only enable enable this this function to be called when I know that there's account ID and I know that there's a tab right because if there's no account ID or there's no tab it doesn't make sense for me to call this endpoint right okay and then I'm going to give you a placeholder data right and I'm just going to return the same data here so this is to basically maintain the the old query data whenever we have a let's say we change tabs right the placeholder data is going to allow the data to be rendered statically without kind of flashing in and out of the screen and let's allow it to refret every kind of 5,000 milliseconds every 5 seconds it's going to automatically refresh in the background okay and lastly I can just return the the list of threads that I have and I can also get the is fetching variable cuz I need to know whether This Thread is f the function is fetching so when it is fetching I can get a bullant value out of it right and then let's actually so let's also export the is fetching variable right and then we can have a refres we can pull out refret from here as well so I can get this function out so that anywhere in the app we can just call this refresh and you will automatically refresh this data and let us also just export out the account ID and let's get account which is the accounts. fine so this is the list of accounts we're going to find the one has active ID okay so we export everything out here so we can find work on our list of threads right so if you go to the mail page right we can see that this inbox thing here is the we want it to be a list of threads okay so let me just come down here inside this mail let's create a threat that's list. TSX component we'll use use client we have a rafc we have a thread list okay so this thread list we don't need any props so let's save this first and let's actually just replace this with threat list import this and for the D done tab we can also just show the tread list here right because the the difference with this inbox and done is that the state is being changed but the the component is the same because we're going to be pulling the the done tab from the local storage anyways right so we're going to go in here we can see the tread list being shown up here so let's actually do this we're going to return a diff and the first diff alter diff here will have a class name of Max WID of full overflow y of scroll Max height of we got to do a calculation of 100 view height minus 120 pixels okay and then we have a another div this div is going to have Flex Flex Das column Gap -2 padding D4 padding top of zero okay and then inside here is where we're going to kind of get the list of threads so before that I'm going to just get the threads from use threads so now you can see I'm importing the use threats function the hook that we wrote just now and we're able to destructure the threads and you can see I can get the account the account ID that is fetching refret and the threats right so with this threats right I can actually then render it in a list but I also want to render by the date so you can see I'm grouping it by the date so September 25th September 24th September 23rd so what I'm going to do is I'm going to group them by the dates first before rendering out the groups individually right so let's have a group grouped threats right so this is going to be threats right do reduce right so I'm going to take the accumulation and the current threat that I'm looking at right and I'm going to look at the current date which is the which is I'm just going to format it right so format I can actually install it from uh date- FNS so I need to do studo bun install um let me see is it from format where is this format from yeah so date- FNS sorry date functions basically so it provides a lot of utility functions for us to operate on dates so in this case we will need the format variables from date FNS we need to format the threat. emails at index z. send at right if not we just have a new date by default and we're going to format it in this format year year year month month day day so it's going to show me this so I'm going to do send add like this um right so I think it's going to be fine fine so it's basically a format the so when we do index zero we're getting the latest email in the threat and we're formatting it by the year year month month day day format okay and then we're going to check if we don't have this date in the group yet we're going to then set the date to be an empty array and then we're going to just do accumulation the date do push pushing the threat so we're just basically grouping the threats by blocks of the dates and then let's actually just return the accumulation and we're going to start with a object right and this object finally is going to be a mapping from the dates to a list of threads and how do we get the list of threats right we can just do type of type of threats so this threats come from here so if I do type of threats I ultim get the invert type of array nice so now that we have this group threats we can actually display them right so let's continue here so within this Flex call I'm going to do object. entries right group threats or none right and I'm going to do map and so I get back in the first array the date and then the list of threats so this date is like 2024 September 25th and the threats is actually the list of threats that were in this date okay okay so now that we have this let's see we can then map it like this let's see what's wrong with here can find date uh we have to wrap it in another here so that we can destructure this yeah and then let's actually just return return a fragment right we'll just do react. fragment with the key being the uh let me see I get this right the map yeah the key being the date okay and let's save this okay let's see what is wrong here why am I getting an error so object. entries right map I get back here oh I see the error so let's just remove this here I should just reset from here so object do entries I'm going to do map so from this map I know that the first variable right is going to be array and I can destructure it to get back the date and the list of strats and then let's be careful then we can return a react. fragment with the key of dates okay now it works and for now I am going to just have a div here with the class name of text- XS font medium so this is going to be the date string text muted foreground and then margin top of four and the first one I'm going to have the margin to zero and this is the actual date so now if we refresh and we should be able to see okay silly me it was not showing up because um we were in the draft Tab and once we look in the inbox tab we can now see the list of threats cool so now we know that things are working right we can see the list of kind of um dates that they are showing up uh that's weird why is it showing like this format that's not right your your m m what did I put oh it should be capital M okay so now we get back the correct format cool cool cool cool so you can see that we are IND getting back the last three days of emails from what we did from the initial sync all right so now let's actually map through the list of threats below it okay and so below this diff I'm going to have get the traps I'm going to map over it for each threat what I'm going to do is I'm going to return return a button with the key being the threat. ID and it will have a class name of flex actually just let's do the CN thing we need that later so flx Flex D call uh items start g-2 rounded Das large border padding of three text left text DSM transition all and hover okay uh yeah wait so not have a relative okay and then within this div sorry within this button we're going to actually show the threat so I'm going to have the div the class name is going to be Flex Flex that's call with of full gap of one and then within this we'll have a another div with a class of flex items Das Center and then within this oh we'll have another diff it's a lot of diff guys we have Flex items that Senter G of two and then finally we can have the final diff I promise guys font semi and in here we can have the item sorry uh we can do sorry uh threats we can do threat right do emails do add negative 1 so this add is a method on the array and when we do negative one we're basically getting the last element of this array right Dot from dot name cool so now we should be able to see this is uh just a list of my name from the the the from of the the threads okay and then and then let's come down here so this is the first diff that you come out of and then the second diff right right and then under the second div that you come off we have another div okay here and then in here we'll have a this div have a class name of um let's see let's see let's see let's see uh we need a class name of let's do the CN thing and we'll have margin left of auto text of XS okay and then in this div we'll have the format distance to Now function from d f date functions we need the thread. emails. at1 so we're trying to get how long was it before you can see that we have this about 19 hours ago right so we can get that by doing this uh. sense at new if not is new date and then we'll just do uh at suffix equals to true so if save this we can see this about 3 hours ago oh right hopefully this makes sense we can zoom in a little bit for you guys all right cool so we get this stuff and then uh underneath here so we exit out by one diff and then exit out by another diff then under the second diff we have a another div here with a class name of text XS for of medium right and this is where we'll put the threat do subject so let's save this we can now see we got the subject underneath here cool and then let us come down here um cool so after the subject div you come down by one and come down by you exit again so now we're at the button again so this time this is where we're going to put the body snippet so we have the div but this class name will have text XS line - clamp of two text muted foreground and this time we're going to put the actual body s HTML inside here and so to do that you need to have the dangerous leet inner HTML and underscore uncore HTML to be uh let me see I threat. emails we're going to get the last emails body snipper if not going to be empty string so if we save that we can now see the empty string uh from we can now see the kind of the body snipper in here and because this is dangerous to set the inner HTML you kind of exposing yourself to vulnerabilities we will have another library to help us kind of clean up the HTML before we insert so this library is called um let me see Dom purify so we install Dom purify okay and so we need this one function I think we also need like add types so don't purify yeah we also need to install the typescript types for this okay let's import Dom purify from Dom purify and where wherever we were setting the HTML before we actually set it down we're going to just do Dom purify do sanitize do sanitize here okay and then we're going to have a used profiles for HTML to be true so just this just allows us to be a little safer but nothing really still changes about this okay cool stuff and then we're going to exit this div where we're putting HTML and now we're going to put the system labels so we're going to do item sorry threat. emails the first email we're going to doist labels do length so if there are system labels right we're going to just show a div with a class name of flex items D center- Gap 2 and then for the item the threat. emails. system label we're going to look through each label and we're going to return a badge so this badge comes from components d i all right and then uh we'll have not this but we want the variant the variant we get B uh variant from label passing the label and where do we get this function right get variant so we get this function wait get variant that's actually a really good question where is this function coming from um okay yeah it's a function that we have to write ourself below here so function get bch variant from label right so we take in the label as a string okay and then we'll just kind of return comp component props from react and what do we want to return we want to return the type of batch and we want to index the variant of it so finally we going to che check if work do includes label. to lower case we're going to then return default right else we going to return secondary cool so this is just basically gives us like a few different colors for the batch if they are kind of different system label po cool so now we get this nice UI right and then I think I think that's pretty much the bu of it yeah we did it cool so now let's actually uh allow people to select the thread they are on so when they click on it they can select right and to do that we need to keep track of the thread ID right and so to keep track of the thread ID here is what I am going to do um I need a global state to keep track of the threat and to keep track of of a global stage like that uh we should install jotai so ji is this um how do I explain joai um J Tha is a flexible State Management Library so it allows us to have just put little information into packets and we are able to use the state anywhere in our react app without going through that react context Redux 2 Kit type of thing so how it works is you literally just Define an atom so an atom is just like a thing to store information and now that you have this count atom you can use this atom anywhere in your app to this use atom hook and now you can access the value from anywhere in the app right so it's like a super simple library and so what I need to do is let's go down to use threats right let's have a let's just import Atom from joai and then we'll export a con thre uh Shad ID atom right equals to atom string on now so we want we want to keep track of the active Trad ID so if I click on this Trad the Trad ID should be set to this thread right and then I'm going to just have thread ID and set thread ID equals to use atom and I can pass in the Trad ID atom make sense so now I have this state that I can use anywhere within my app so I'm GNA pass it down here thread ID and set thread ID cool cool stuff so now with this thread ID I can come back to my thread list right right here and so I'm going to add some additional styles to my CN here so from the top button right if this button is selected means this Dr is selected I want to kind of uh show a different style okay so if I have let me see um actually no it's not here it's not here we want to show it somewhere here um let me see let me see yeah know what it's fine it's fine so if the thread. ID equals to the current active thread ID thread ID I want to give it a different background in this case background accent so now if I actually put this on click so on click I'm going to just set the thread ID to be the thread. ID and I'm going to just destructure this as well so hopefully you can now see when I'm selecting one the thread ID changes which causes my uh background to change as well because we're conditionally rendering the background based on whether the thread ID goals to the selected Trad ID yeah so hopefully this makes sense and now that we have uh and now that we have this threat ID uh actually no yeah I think it's fine I think it's fine now that we have this threat stuff uh we can then take this and we're going to we can then display what's on the the display here on the right side okay but before that I'm going to add the dark mode in here so remember in our app we have dark mode so it's actually super simple to do so let's go down to the documentation and search for dark mode okay select nextjs so the first step is to install this thing called next team so Stan install next- themes okay and then we need to create a theme provider so let's create that so go down to my components and let's do theme- provider. TSX right let us copy this in here so we're just exporting a provider and then we need to wrap this wrap our app with a team provider so let's copy this so let's go to our layout layout here so this wraps everything let's see okay so they're putting it in the body cool so let's do it here team provider okay let's see and let's wait for it to export okay so okay let's just wait for it to okay maybe my vs code is dying let's just load the window window then I'm going to import this team provider right I'm going to save it all right so now that I have this team provider I can work on my my my toggle button okay let's see why is this not being imported okay no it's fine this should be fine okay so I'm going to save it and I'm going to come down my components I'm going to create a team- too. TSX component okay so for this team toggle right I'm just going to make it a client component uh let us do rafc team toggle right so I'm going to get the theme and the set theme from the use theme Hook from next SL themes and I will have a function called toggle theme that just sets a theme uh to the opposite of light and dark and finally I can just return a button here right the button do have a variant of outline and then the size of an icon right and un click we want the toggle the team and then in here right we're going to just copy in these two icons here the moon icon and the sun icon cool and so this is literally just saying that look on my dark mode I'm going to scale it to zero which means I'm going to hide it and on the light mode here here uh I'm going to scale to zero on my moon icon so depending on which mode it is on it's going to hide automatically so if I go back here I'm going to add this tee tole to my main page here and so I'm going to just wrap this in a uh okay how's going to do I'm going to wrap this in a fragment I'm going to have a absolute uh bottom four left four and I have a team toggle like this and then hopefully I can see this and if I press on it you can see boom our UI is changing from dark mode to light mode and everything looks nice awesome awesome okay so now that we have done that let's actually go on to work on the threat display okay so now let's finally move on to the thread display on the right side right so when you click on the thread now we are able to to select and keep track of the active thread ID but now let's actually use that thread ID to uh to show some interesting stuff here okay so now let's actually go back to our cursor I mean vs code and then let's actually clear everything out let's go back to the main mail page so while we see the threat display let's actually create a new component right let's call it threat Dash display. TSX use client uh RFC threat display okay so Trad display let's actually save this and let's go back here to show threat display right and then we should able to see threat display everything is still showing up here but now if we uh let's actually get the thread ID from use threads here I'm going to get back the thread ID and I'm going to show the thread ID that's being selected okay so if I come here you can see you see as I am pressing around the thread ID changes right so I'm going to use this thread ID to find the selected thread from the list of threads and then we're going to just display it so remember we also exported out the list of threats so now do you see why we actually did this Hook is because we are able to access the list of threads anywhere in our app without needing to kind of rewriting rewrite the refetch hook right hope this makes sense and so now let's actually find the uh threat so we just do we just look through the array and find the one that has the active threat ID right and then since we have that we can also show uh we can finally show the threat do subject to begin with right so now can see if I click around I can see some um yeah we can see some threads that are being shown up here cool so now let's build out the UI for this so we're going to have a uh we're going to have this stuff up here on the top right uh but I'm going to do here is let's just do something like this so instead of returning the subject so let's return the D first so the this the diff we have a class name of flex flex-all uh height D full within that we'll have another div with a class name of flex items that Center gap of two all right and then inside here we have basically uh button right and the button will have a variance of ghost the size of and sorry the size of an icon right and then we're going to disable it if there are no thread there's no selected thread okay and then there is archive button in here right and the archive button has a size of four so if we save this now we can see something that is happening here cool and I think we also need some padding here right so I'm going to find kind of where we're putting the padding padding uh try display interesting interesting do we not have padding here H let's see how do I get P okay you know what it's fine uh we'll just we'll just see what happens oh sorry this is not gap of two my bad this is padding of two ah now it looks right okay cool so the second this have a padding of two okay and then so now let's actually copy the button down a few times and so for this is not archive anymore this is the uh archive X icon right and let's copy this down again and then we have a um it's not archive X anymore we'll just have a uh trash to Icon okay this makes sense um Flex item Center there's one more here right oh I know what happened okay so you see these buttons this buttons need to be wrapped in other Flex items that Center there's Gap two so that we can give it some spacing between here so just know that there's like three l of div down here right because we have kind of the first uh first div here sorry uh that's three because we have kind of this section here and also the other section here and within here we have another div so this first div is for the three initial buttons here right I'm going switch back to light mode okay awesome then we'll have a separator kind of after this button separator let's see um let me think let me think does this make sense this makees sense separator cool from component UI and then the orientation is vertical right and then you can see it's like this and then we'll have just a another button but this is for the snooze icon so the snooze icon will have a clock icon yeah makes sense so I think this two stuff has to be in the div as well so that we have equal equal spacing and then let's just give it a class name of height of four you know what I'm going to move it back out here and let's just give it a margin level of two and then this button let's have a class name of margin Lev of two all right awesome and then we're going to have another a diff out here right so we have a diff here that's also the same Center uh like this sorry it should be in here okay and then we'll have a margin left of Auto right and then in here is where we can just uh put a kind of drop down so let's just for the drop down component from Shen here right so let us just copy the import and let's copy the utilization cool and then we'll have a obviously you just have a button here but the button trigger would actually be a more vertical icon more vertical and in here we can just uh put the content as a line to the end and then we can have Mark as un Red Star Thread at label and mute thread okay okay this looks weird I think it's because uh Flex okay it should all be in this div right cool so now we got this Mark as and red awesome and then now if you come out of this div so let's just collapse this row so this is the buttons row and then we have a separator so this separates here like this right that's cool and then here is where we put the actual displays of the thread so you can see we got the subject here we got a reply to got the date Etc right so this is where we're going to put our stuff okay and so I'm going to come down here and I'm going to just see if there's a thread right so if there's a thread I'm going to display something if not I'm going to display that hey you know what there's no message selected so we have a class name of padding of eight text of Center text muted foreground and say no message selected cool so if I refresh this right it says no message selected cool but if I have a threat what do I want to show okay so if I have a threat I'm going to have a div pardon me like this so the the diff will have a class name of flex Flex of column Flex call Flex of one overflow scroll okay within the div I'm going to have a flex item Center padding of four okay and within this we can have a uh class name of flex items that Center gap of four text XM okay and then finally within this thing is where we put our Avatar so component Avatar we have Avatar fallback right so the fallback we have a well sorry we have the image first uh sorry the image will be a self coding tag okay and then the out will be kind of Avatar and then obviously we have the Avatar fallback and inside this fallback is where we can actually put the initials so we'll just take the active threat we'll get the emails the first email we'll get the who is it from we'll put a name and then we'll split it by the Whit space right and then we'll just like kind of just get the first so basically getting the first character of every uh word in the in the name all right and then we'll just map it ch ch the we get the first character of the first word and then we'll join it together do join using this this empty space just put some optional training and now we got show up something cool so now you can see my name Chong we're getting the first initials of the whole of the of the two words so EC that's why you are getting the EC here make sense all right so then let going to exit the Avatar right and then we have we're stilling the the third div right and then we have another div here with a class name of grid gap of one right and then inside this div we'll have a class name of fonts semi bolt and then we'll have thread. emails the first email from. name right and then we have another diff with text XS uh line clamp one inside here we're going to put the the subject right subject and then we'll have another div with the same class name text XS line clam one and then here we can have a span. font D medium inside this font medium we can have reply-2 right and then down here we're going to have thread. emails the first email dot from. address uh yeah from do address so if we save this now we can see things are starting to happen right so now let's come down so uh come out of the reply to right so you see the first div that you come out of the reply to and then the second diff of the line clamp and then the third diff like this right so it should be let me see if it's out of the grid Yeah so basically just collapse the div with the grid and you come out of it right and then come out of it one more time so we are kind of out of that so now you have it should be two divs out from the bottom and then let us do thread. emails do send at so if there is a scent at I'm going to put a div uh div like this with a class name of margin left of auto text of XS text mut foreground and then I'm going to format format from date FNS so I'm going to import this wait is that it date FNS we'll get new date we'll get the threat. emails at index zero. send at right and we're going to format it in the PPP format so this becomes like this kind of long format September 25th uh 225 PM okay cool all right so then we come out of that uh we come out of the diff uh right here and then I think let me see come out this and then we can come out one more so now you just one layer out you can have a separator right separator sorry separator okay so it separates this and now finally under the SE operator let us have a diff the class name of Max height and you have to calculate 100 view height minus 500 pixels right overflow of scroll Flex flex-all okay and then inside here we have a diff that says class name of padding of six Flex Flex call Gap of four okay and then we can map through the the email so now finally we can map through the emails and display each individual email card so TR the emails. map for each email what do we want to do we want to return a let's just put a div with the subject of the email with the key of email. ID right so now we can see this is the subject like this okay and we're going to put the actual email card later on but then what I'm going to do is uh finally I'll will come down to the last diff here below here uh I'm going to have a flex one and then I have a separator again with the class name of margin tab of Auto and then this is where we're going to put our reply box so this is the email box here this is the list of emails we have a big Flex one to space it out and then this is where we put our reply box okay so for now I'm just going to put a stand like a placeholder reply box item here okay cool so if I save this cool so we got reply box in the separator okay so now let's actually work on displaying the list of emails okay so let us create a new component called email email- display. TSX whoever use client we will do our uh t srf c e email display so we need to be able to get the email that we're trying to display right now so what is the type of email okay the type of email is going to be router outputs so this router output is going to allow us to get the type definitions of the account do get get threats right the number so we're going to get like the first element of the get threats we're going to then index into the emails and we get the first element of that so then this will finally resolve to the actual type here so this is just a really nice utility feature from trpc where we get the uh the type output of the output of the procedures so then we can then return actually return a email display here passing in the email and the email ID as a key you can see you see this matches up exactly and there's no type errors because this router output is always correct okay so now let's save this so I'm going to just unst structure the email from here okay and what I am going to do what I'm going to do is I'm going to just look through it okay and let me show you what I'm going to do here uh okay so okay you see the issue is if I send to let me see if that's a good example add it okay you see this a great example I love this so um if it's coming from me I want to have a this different UI and if it's coming from someone else I want to have another UI so I need to differentiate between the emails that are coming from me and the emails that's coming from someone else so we're going to have a variable called is me right so now it's going to be true right but how do we determine as a if an email is from me or not so to do that we need to know um who sends the email and if the sender is me okay so to get my identity I can just get it out from use threats so remember we got the account right so if you go into use threats the account is basically the list of accounts and which account I'm looking at right now so in this case the current account is this right so uh I can show you I can show account. email address right so this will show me so this is my email address that I'm looking at right now so then I'm going to look at this email right so it me is if the account email address equals to the email. from. address right does it make sense so if I'm the one who sent out the email and the account is me like then this variable is going to be true okay so now based on this variable we can then display things differently so I'm going to return the div the div will have a class name right of we have a CN then I have a border has to be around rounded medium padding of four cursor pointer trans actually no not cursor pointer transition Das all hover translate X2 and then okay and then if it's me I'm going to have a border left of of gray 900 and Border left of four only if it's me okay so if it's me I'm going to display this thick border on the left side and then inside this div I'm going to do is have a div with a class name of flex items Das Center justify Das between g-2 and then in here we have a narrative with a class name of flex items at Center justify between gap of two cool and then finally we're going to check so uh let me just okay you know what for now I'm going to just put the uh email. from. name so who is it from okay so you see Google this from Google right Mak sense here so both are from my name basically cuz it's two different email accounts by the same name so I'm going to have this Avatar icon right so how do I get this Avatar ion uh I'm using this library right so this library is called install react D avatar so this will give me uh a component where I can just pass in the name and you will tell me give me this nice Avatar icon okay so let's remove this pen so if it's not from me if it's not from me I'm going to Showcase Avatar so this Avatar comes from uh from react Avatar cool and let's actually do this let us have a name so the name be email. from. name right uh or email. from. address and then the email will be email from the address and we'll have a size of 35 to make it bigger our text size ratio should be two and let's make it round round equals to True okay so if I save this now we can see I got this icon here looks pretty right okay and let's see yeah I think this makes sense cool and then so if this will only show up if the email is not from me right but then if it's from me is me I'm going to show is a span with just a normal class form medium right that says um let me see let me see actually no this span is outside and then I'm going to check so if it's me I'm going to show me if not I'm going to show the email. from address cool so now I show the name beside the Avatar cool stuff and then underneath the div so I'm going to have a pag with a class name of text XS text- muted foreground okay we have a format distance to sorry to now right and then we have the email. send at or new date and then we have a add suffix equals to true so we get to see when it was sent all right then finally under the div outside we have a height of four just a spacing of four and then this is where we show the actual email so we are going to use another library to showcase the HTML email right so come down here and install install react dlet okay so I'm going to just import letter from from react letter okay and so I can just do I can pass in letter and then I want to pass in the HTML to be the email body right and then I can have a class name of BG white round medium and text black Okay cool so now I should be able to see the email this HTML email being formatted cool awesome so now we have built kind of the basis of an email client we're able to display emails right okay awesome so before we continue I'm going to start actually teaching you how to build the command K bar so you see this command K when I'm pressing command K I get to bring up and I get to quickly switch to my either my sense folder my inbox folder I can switch my dark and light teams right I can press TT right which you can see is the the kind of command to switch teams I can go to my send view by doing GS GS and GI right you can see like super clean right and then I can also switch my accounts right to the different accounts that has been connected to this user so I'm going to teach you how to make this command bar item right and so let's actually go back here to our code and so the first thing that you got to do is to install a new library called Kar so I'm actually using this Library called Kar right which is just a allows us to make it super easy for us to actually make Command K applications work right so let's actually do that I'm going to walk you through step by step on how to do that so first thing let us install slow but install K Bar okay and so let's come down to a component components here I'm going to create a new folder called Kar and I'm going to have index. TSX in here so this going to be our default export from Kar right and so we're going to just export default function Kar right it's going to take children which comes from react. react node and so uh how we're going to do this is the first thing is the first thing is we're going to have another component called cons actual component so because this K bar is actually going to be a wrapper around this actual component right because we're going to have a keybard provider right so let's import that import keybo Provider from kbar um let me see if that it what's my keyar provider um let's see K Bar provider nice yep just a misspelling so we're going to wrap it here and we're going to wrap the actual component like this right uh and then the actual component will actually then wrap the children right and so this actual component we're going to take in the children right like this right is going to return a kind of fragment and inside the fragment we're going to just have a Kar portal so then within portal we have a Kar positioner within the positioner we have a k bar animator and then finally we can have a kind of uh K Bar search and then we have the we can then put the children right below the K portal okay so this good and what I'm going to do is I'm going to just wrap my app in this Kar provider Kar provider so let's come down to our um let's see where should I put this in uh let me see let me see um K Bar yeah let's put in in our layout so let's come down to our layout and then um let's actually come down to our all the way into our children here inside the crpc react provider and we'll wrap it in K bar so let's wait for it to load Kar let's restart our vs code buggy uh Works only in client component of course so let's go into our K Bar and let us add a use client on top here so you see if I press command K you see this input is starting to show up here so now let's actually start this right so we're going to start it by doing this so for the kar positioner with have a class name of fixed insert zero background black divide by 40 when it's dark mode we want to have background black divide by 60 backdrop backdrop Das blur DSM scroll bar Das height uh important to have no padding and let's just do have a a 999 Index right and then with a keyboard animator with a Max width of 600 pixels we have a margin top of 64 right with full BG white right when is Dark mode I want to have a BG gray of 800 Tex for ground when it's dark I want to have a Tex gray of 200 okay and then we have a shadow large border when is Dark mode mode I want to have a border gr of 700 right I want to have a large roundedness overflow hidden relative and I'm going to negative translate y12 right it's a super long class name sorry guys but yeah and then above the K Bar search I'm going to rra it in another a div here with a class name of BG white when this dark mode I'm going to have a BG G of 800 right and I'm going to wrap the keybo search in another Z and this one will have a class name of Border X of zero border bottom of two when is dark I want have a border gray of 700 cool so now if I save this I come back here we can now see we have a nice kind of animation and the inputs right so now that startall the input so we have a padding y of four padding of six text large with a full background of white right when it's dark we want to have background of 800 outline none water none Focus outline of none right Focus ring zero Focus ring of set of zero okay and now we have this nice command bar awesome okay so now let's actually kind of Define some actions so the basis of this is we're going to give this a list of actions that it allows us to take and then you will render it automatically for us okay so uh here I'm going to come up here in inside the kar component I'm going just Define a bunch of actions to be this action that we def that we take from Kar right so every action every action in the actions array needs an ID so right so let's say we have a inbox action so we want to have an action that goes to the inbox automatically okay and then we'll give it a name so we have a inbox here right and then we can give it a shortcut if you want right so we can have shortcut of G and I so if you press G and I on the keyboard consecutively it's going to execute this action we can give you a section of navigation right and a subtitle of view your inbox and then the most important part is the function that will be called whenever this action is selected in this case let's just conso the log inbox right and so to pass this action we just pass it into this K Bar provider uh prompt here right and then if you come down here and press command K right obviously we can't see anything CU we don't have that uh we we're not rendering the results yet okay so now let's actually render the results so come down here I'm going to create another component called render results. TSX right so this render results will have a kind of R fce render results and we'll we'll import this render results just right underneath the search right yeah so after the div we just do render result we'll save this here render results come on oh here yeah that works as well render results and then for the render results um kind of component what we're going to do is um we're going to get the list of results from um hook called use matches from Kar right and then we also need the roots action ID and then we're going to basically export out a wrapper on Kar results right and then we have the items to be the results array okay and then we can render it have a custom render right which we can structure the item and whether if is active right and then we can basically ask if the type of item equals to string we can just render a diff with a class name of padding X of four padding y of two text xsm uppercase opacity of 50 text grade of 600 when it's dark I'm text grade of 400 right but if not what I'm going to do is if it's not string I'm going to render a result item right so let's actually create another results Das item component right so this result item will have R fce result item right so this result item is special right because I need to do a forward ref so what I'm going to do is just delete this Con result item and you know I'm just going to copy this in because it's like super long right uh but the point is that uh we need to finally add one more thing result item. display name equals result item so this is literally just a component that just controls some State have some framer motion elements to it right so let's also install framer Das motion cool let's save this let's go back here and then for the ROM results let us actually import the random item right and then the action will be the actual item here the active will be set to whether is active and the current root action ID will be set to the root action ID or or empty string cool so now if you go back to the app we can finally see our action right here and so if I press if I go into the console and I select this I'm going to see console lock inbox because that is the action that we're performing in our Index right does it make sense here performing here okay so how do I actually switch the the tabs right I can remember I can I can access the tab from the local storage right that's why we're using use local storage so uh we'll get the the tab and the set tab from use loo storage and we're going to get the normal human Dash tab index and by default we just have a inbox okay and then what we're going to do is just going to set the tab to inbox okay and so if we do this if I'm going send and I press inbox look what happens I switch back to inbox right I'm going to Ty inbox it switch just mean back so perfect it works so now let's actually just do the same for the others so I'm just going to copy in the others it's going to be the same we're doing for the drafts and for the send right and let's just delete this priority so if you do this now refresh right we can now see there's a inbox and drafts and if as as I type along drafts I get to see and it automatically switches me over and if I press GS right you can see it automat switches me GS right GI does it make sense drops right cool cool cool cool so just put drops make sense all right so now we're almost we're almost there so let's go to our inbox first TRS all right and what we're going to do is we're going to do the same but this time we're going to do it for the done so uh we're going to switch this as well inbox and done right so let's do that we'll have a done is normal human done and then we'll just do the same right for the action and the pending action uh let's see here cool so when is when we have pending action here we can just set the value here to be done and it'll automatically be switched over yeah hope this makes sense so now we can see you got this done and pending right cool and so now let's actually also allow you I'm going to show you how you can add custom actions like theme switching so you can see I have a toggle light theme here right or I have a dark team so I'm going to show you how to do this so to do that I'm going to have a custom hook called use theme use theme switching. TSX right so for this use theme switching right I'm basically going to just export out a a hook called use theme switching okay and I'm going to import the theme and set theme from uh from use theme uh from use theme uh let me see uh where's this let's import it from next SL teams okay and then we have a function called toggo teams like this that just switches it based on it like this okay cool and so what do you need to actually do to register some actions within the command bar so we're going to import uh something from K Bar Key Bar called uh use register X actions okay so this hook will basically allow us to actually register some actions into this thing so what going to do is we'll have the theme actions so this is going to be a array of action right so I'm just going to just copy paste this thing over here so it's just the same that we've shown just now right but we have the ID the name of it the shortcut to it right the section and the actual function that we want to toggle it to right so for the set like see we're just like just calling the function and then finally I'm going to just call use register actions I'm going passing the theme actions and for the dependency array I'm going to change it whenever the theme changes okay so now I can just come back to my K Bar and I can just call use theme switching uh use theme switching I can import it from my local directory and so if I come back here now I can see I got my toggle light team called toggle dark team right and I press Double T to just toggle my team so that's how you kind of customize and add actions in here okay and I'm going to do the same but for the accounts so you can see I can switch my account here by like let's say if I have three accounts I can just immediately switch it from here so let's do that so we're going to do something very similar right we're going to have an a hook a custom hook called use account switching. TSX right and then it's just going to be uh I'm going to copy this in for you to understand so let's just do let's get the API oh my God typescript is did that's why okay so this custom hook what it's going to do is I'm going to fetch in all the accounts from my API my uh my trpc API and then I have this main action right so for me to switch account and I'm going to access my account ID from use local storage right and so I'm just going to register actions and what I'm going to do is I'm just going to concatenate the main action with the list of act with the list of accounts and with that I'm able to just show the account as part of my command so if I come down here and I'm going to do use account switching here and I kind of refresh I should be able to see this switch account and because I only have one connected account it only shows me this but obviously if you connect more accounts you able to get more uh options to show up here cool so that basically concludes how you can add a command K uh function and you can obviously add anything here you can add like Market as done you can com you can like navigate it anywhere you want as long as you define the action right and you define the function that you want to be called whenever the action is selected you can do virtually anything right so yep all right cool so now let's actually continue uh building out the reply box down here okay so here's going to be the structure right so actually let's go back into the code first right and for the reply box let's create a new component reply desbox TSX okay let we use client component RFC reply box okay and we'll just import the reply box component here first reply box cool so now everything still looks normal right reply box okay and then what I'm going to do in this reply box I'm going to have another component called um email- editor. TSX so I'm going to explain to you how the structure is going to be like here uh I'm going make TS RFC cuz we need props for this component okay and for the reply box right we're going to make use of the email editor for now okay so the reason why why we have this email editor component is because we're going to be using this component in both so we're going to be using this email editor component in both uh this part here as well as the compos section so this email editor will be have will be a reusable component right for this composing and also for this reply box so therefore in the reply box we're going to make use of this editor and then in the composing we're Al going to make use of this editor so let's just build out this email editor okay and so to do to do that obviously we're going to use this a library called tip tab so tip tab is a kind of very customizable R text editor that we can use to format HTML for emailing Etc right and it's super easy to use and integrate tightly with react and nextjs so let's just get the starting thing first so to actually install tip tab we have to do sudo B install at tipt SL react right we also need at tip t/d kit and at tipt / extension text okay and after you install it right let's come back down to the email editor and the first thing is let's actually in initialize the editor so we have cons editor equals to use editor and we can actually import this use editor from at tip Tac react so this editor we can actually configure a few things the first thing is autofocus we want to set it to false by default right and then we have we'll give it a few extensions so if you see in this tip TP editor that we have here we have all this menu bar items right so we can we can like type stuff and can bold it so everything here is part of the starter kit extensions that we can add to the editor so to actually get the starter kit we can just import starter kit from the starter kit from at tip t/ starter kit and we can just put it as an item in the array cool the next thing we also want is to have a custom custom editor because because in the feature we will have this command J when you press command J it will be able to autoc complete the message so to hook in the auto complete we can have our own custom extension right in this case I'm going to name it uh custom text and we can extend the text so what do we get this text from we get this tab text from at tip tab / extension text and so what I can just do is textt to extend so I'm extending a current feature right and I can to add keyboard shortcuts right so this gives me the ability to add a custom keyboard shortcut to trigger some action in this case I'm going to return I want to trigger this when I press meta- J which means command J on Mac or control J on windows so whenever we press meta J right this function is going to be called and for now I'm just going to console. loock meta J and I'm going to press I'm just going to return true for now cool so now I can just add this extension to my uh extensions list and lastly I also need to have a state right so I'm going to have a state for value and set value right and it's just going to be a react use state of a string so it's just going to be a string that holds the HTML value of whatever editor State we are having and so on update so whenever I type stuff on the on inside the editor right I'm just going to let me show you I'm just going to set the value to to be the HTML HTML version of the editor state right so now that we have this we can actually start displaying the the editor information the editor value so the first thing here is I'm going to return outer div and within this outer diff I'm going to return a uh another inner diff and this inner diff will house our content and here we have a class name of Pros with a full padding XEL four and in here I can put in the editor cont content from tip t/ react passing in the editor as well as the value right like this okay and if I come back to my component now you can see I have the beginning of what seems like to be an editor and I can I can select all I can press command B to bold it so I have some rich text editing kind of built in okay and one more thing you need to add is you need to go go down to your Global or css and add these two classes into your code that will'll be using later on so this tip T Pros mirror is just to remove the outline of our editor right if I remove this you can see I get this ugly outline again so if I add this back in the outline is gone right and we know we need this is active class later on right because we are trying to make this menu bar and we need to have this this background whenever is talk on or off so this this editor will be made out of a few sections so the first section is this top menu bar where we get to control the state of the editor and bold it I tell size Etc we have a second part where you can see that when we when we expand this we got a section to enter the tool the ccs and the subject right and then we got the kind of draft right with the email chat B here which we will show later and then we have the editor which we have done and then we have the last section here which is the sending of of the email okay so now let's actually add the menu bar on top of this component first so to do that come down to your email editor and let's actually create a new component I'm just going to call it editor- menu bar. CSX okay so for this is going to be uh very redund like there's going to be a lot of uh features that have the same so we're just going to do TSR fce right and let's rename it to editor menu bar okay and so it's going to take in only one prop which is the editor component and this editor will come from the type of editor from tip t/ react right so we're going to be passing in the editor instance from our email editor right we going pass it down into our editor menu bar right so let's actually just uh editor menu bar let me see why I can't why we can't so let's save this maybe typescript is dat again so let's reload the window editor menu bar okay now we can exit we can import this but the thing is editor might be now right because it might have not been initialized so I'm going just go is if there's no editor right now I'm going to return now I'm going to wait for the editor instance to load before kind of displaying this stuff okay so now that we have this menu bar starting to show up right here let's actually start it okay so the first thing I'm going to do is I'll just return the outer div right outer diff of a class name of flex Flex Des W gap of two right and then in here I'm going to display the kind of this buttons up here and so the first button is going to be a normal vanilla button and so I'm going to put in the Bol icon here both from Lucid D react let's give it a class name of size four and tex secondary forr okay and the button we want to do a few things right so when we click on the button we want to toggle the B uh feature of the selection so what we're going to do is when we click on it we'll do let's actually destructure the editor from props and let's do editor dochain do focus. toggle bolt. run okay so when we click on this we're going to basically call the toggle Bo function on the editor right and then we'll disable this button right in cases where the editor cannot be B so not editor. can. chain. focus. toggle Bol um toggle Bol oh toggle go bat. run so I'll disable this button when in certain cases the editor can't allow us to bat the mark and finally we have a class name right where editor do is active Bol so if it's if the current selection is bolded right I'm going to just showcase the is active class if not I'm going to show MC string so this ex active class I've show you just now is from the global or css that just adds a background color to it so now if I have this and I come back here we can now see we have this Bol icon and now if we can if we type some stuff and we come in and press Bol right it's going to Bol the stuff and my command B also works and you can see that the class name is updating as we B it right cool and so now I'm just going to copy in the rest of the code because it's going to be just the same except that we are just copying from for eism strike TRS and all the code is the same logic right you can see that we're just call the toggle code uh function on the editor right so now if we go back here we got all the stuff here okay cool so now that we got this uh thing working let's also give it some uh let's go back to the editor right and let's continue styling this this editor okay so for this let's go to to the editor okay and so in this editor uh this menu bar I'm going to wrap it in a diff right and this div is going to have a class name of flex padding of four padding y of two and the botom of bottom right so now we can see that it is we have some separators up here okay and for this editor content we'll just leave it as is and at the bottom here I'm going to add a separator from component SL separator and then below here I'll have a another diff and so this diff here we're now building the bottom section which is the command J stuff okay so here I'm going to have a class name of padding y of three padding X of four Flex items Das Center justy that's between okay and then in here we have a span of a class name of text SM and we'll say tip press here so press what what do we want to press we want to press a keyboard component so this keyboard will have a class name of padding X of two padding y of 1.5 text XS font semi Bol text Gray of 800 right BG gray of 100 border border Das gr of 200 and rounded Das large and in here we just put the words command plus J right and then we'll just have a space after here for eii or2 complete and then outside of the span we'll have a button here import from button ui/ component button that just say send okay and now if I come back here I have this nice component here that says press command J for auto complete and at the bottom right I have a send button cool things are starting to look good right but now let's actually build the kind of this draft text here and when I click on it I want to show another element that uh is conditionally rendom whether it's expanded or not so to do that let's actually also hold some state so let's have a cons expanded and set expanded to be a of of Boolean okay and so I'm going to come uh to my come to my editor content and above this div right I'm going to have a div here that has a class name sorry that has a class name of padding of four padding bottom of zero space- y of two right and in here I'm going to just conditionally render so if it's expanded I'm going to show just like this where I'm going to show my CC inputs okay so now if I come down here to my uh below the the expanded stuff here I'm going to have another diff here so this diff is going to have a class name of flex items Das Center gap of two so this is where the draft text here this this will be so I'm going to here I'm going to put another diff in here that has a class name of cursor Das pointer right and when I click on this I want to set expanded to I want to togg go to expanded State and in here I have a sp that has a class name of text green of 600 and font medium okay and in here I'm going to put draft right and below the span I'm going to have another span and for now I'm just going to put here like draft to Elliot okay so now we got this drop to Elliot you can see when I click on it the CC inputs kind of show up here because we're conditionally rendering rendering this okay so now let's work on this CC input stuff okay so you can see here I'm going to have this multi select with a list of all the email addresses that is linked to this account right so let's actually build this base component I'm going to call it teex select so this teex select allows us to select multiple values to be placed into the select and so to do that let's actually create a new component called uh Tech input. TSX so let's do TS RFC and we name it to Tech input okay and so this tag input is going to be built on top of a library called react select so let's install that react D select so this react select just give us a foundation for us to build a multi select uh input on top of it cool so the first thing let's do is actually just import uh select from react Das select and in here I'm going to return a div here and this div will have a class name of of Border rounder of medium flex and items of Center okay and in here I'm going to um I'm going to take in a few props here right so for this select I need uh the first thing I need is the default values which is going to be a array of label string with the lab the lab with the value and this will be an array right so basically each item in the select will have a shape of a label and the value and then I will have a placeholder and I want to have a label as well right so imagine here the label is like two and for this the label is CCS and this is a default value right you can see when we click around right we have a default populated a value of who want to send it to by default which is basically the person who sent us the email right course we're replying to them so therefore we need these default values to be populated and then we'll we'll accept a call back of unchange right and so this will have just this shape basically we're going to whenever they select a new stuff or whenever the input changes right we're going to give you a call back and by default the value is just going to be this array of labels and values and under the unchange let's also accept the value right so this is for the control input good stuff cool so now that we have this let's continue coding it out so inside this diff I'm going to have a span that just shows the label right let's just destructure all the props first so this span will have a class name of margin level of three text SM and text Gren of 500 text gr of 500 cool and then underneath here let's actually have this actual select component from react select and I'll just give you a few props by default let's give it the value to be value right unchange to be unchange right and the and the kind of placeholder to placeholder and we have a each multiple Fu so that we tell react select that this fu is going to be a multi select so let's just import this Tech input in our email editor first to see how it looks like so Tech input here and we'll give it the props that you need so default values let just give you empty array for label let's do two for unchange let's just console the lck out first and for placeholder let's put like uh add email sorry at recipients and lastly the value will just be an empty area for now so if you go back to the code now we can see when we expand here right we can now see that we have this react select but it looks super ugly now and there's no options here right so now let's actually start working on that recipients and the Tex select so to do this is let's actually just come down here let's just add a TS ignore cuz it like some typescript error but it's actually not issue because I know that this is correct right because it's just they have a more complex type here okay so for this select uh I'm going to just put the default value to the default values R and then let me see will have a bunch of options so how do we actually get this list of options so you see when we click on this we got this list of options so where is this coming from this is coming from all the email addresses that's linked to this account and so let's actually create another trpc endpoint to kind of fetch the list of email addresses to be displayed in this dropdown so come down to your account. TS router right so let's actually create a new uh endpoint for get uh suggestions right and this will be a private procedure right and for the input we will need obviously the account ID that you are trying to get the suggestions for right and we'll do the same thing so query let's get the context and the input right and obviously first we're going to just authorize the account access to the to the account that the user is trying to access and then what I can what we can do then is just return await cx. db. email address so we're going to find all the email address where the account ID equals to the account the ID that we access here and for now we actually just need the address View and the name VI cool so this is going to be our value sorry this going to be the label and the name for the options so now let's come down to the tech input let's actually fetch this down so cons data let's rename it to suggestions equals to API from react do account. getet suggestions. usequery right and once you get the account ID that we are currently viewing so to do that we can actually destructure it from use threats so this reusable hook that we' have written and let's get back the account ID here so now let's actually pass account ID so you can see types script is telling us we need account ID right and so now that we have a list of suggestions we can actually pass them down to the suggestions sorry the options uh menu in the select here what I'm going to do is I'm just going to show the list of suggestions here um let me see so I'm going to put suggestions I'm going to map it right so because they want it in a certain format so I'll just put suggestion they want us to return an array of object you can see we need a label in the value so for the label I just put suggestion do address and for the value I also put suggestion. address so if I save this and I come back here we can now see we now get a list of all the email addresses within our account cool and one more thing I want to add here is I want to kind of have this cute Avatar icon beside the address so how do we actually make add this Avatar beside this ugly select to do that I'm going to have a new I'm going to just move these options out so I'm going to declare new variable options and it's just going to be wers in here I'm going to copy this out into it own variable I can put this options here but here instead of of this label being a text field I can actually return a uh GSX component right so this GSX component is where we're going to put our Avatar so this label is going to be a span right with a class name of flex items Das Center G of two right and in here I will have the avatar from react D avatar which we have used just now the name is going to be suggestion. email sorry address the size is going to be 25 the text size ratio is going to be two and rounded round is going to be true and underneath here I'm just going to put the suggestion. address and now if we save this right we can now see I got this cute Avatar beside the address field okay and now let's actually also give it some styling to make it not so ugly all right so let's go back here let's see so for the select let's actually um give you a class name here class name of width of four Flex of one so it expends it all the way out here and let's get rid of this ugly border so to do that write we have a class names array sorry object and here we can actually customize every little stuff within the react select so in this case we can customize the control which is the out the outer diff here the outer select here and to do that I'm just going to copy in here class names so for the control we just want to remove for the borders and the outlines to reset it then for the multiv value we just applying some dark mode themes right and for the labels as well and so you can just copy this in from the repository but what you then get is this nice array a nice select where we can see we have the Avatar and the recipient we have this list of stuff here we can now go back into our email editor and use this once more so what I'm going to do is uh this is actually one more thing I need to do so if you see in the the the behavior if I start typing Elliot and see that nothing pops up I want to be able to select the start the Emil that I manually chose here right and so to do that you can see that right now if you start typing and there is no kind of email corresponding to it I can select it here so therefore what I want to do is actually keep a state of whatever I'm typing so that I can show it as the last option here to do that I need to maintain another state con input value right and this has to be a state and so what I can do here is I can do uh on input change so whenever I start typing I can just set the input value to be this and so for this options here right if I have some input that means I I have typed in some value what I want to do is I to add this input to the end of the options array here so I'm going to check so if there's an input right I'm going to do options do concatenate so I'm adding it to the end of the of the options the last option right I'm going toate the label to be the input and the value to be the input as well right so let's see oh it should be input value right so input value so right now if I save this and I come back here if I start typing stuff you can see this start showing up right and obviously it's not going to save because the value is not bounded right because it is taking out from uh external props right so let's just save this Let's ignore this cuz we know that it's going to be correct cool so now let's go back to our email editor component and I'm going to just copy this down I'm going use this for the CC's so the label is CC right and we'll save this and now we got this nice component where we can add the ccs and the tools right make sense cool and so now we our email editor is looking great and one more thing is I want to add a subject input here right so let's come down here below the TCH inputs let's have a input here from component SL input we'll have a ID of subject and a placeholder of subject and we need the value to be subject but how do we get this subject value so we need to pass all of this stuff in right so you see this these values here and this default values and the un Change and the subject we need to be able to receive it as a prop to this email editor so to do that let's actually craft out the props that will pass in so obviously we need the subject which is a string we need to have set subject oh what's this set subject to be a string here uh let's see let's reload the window here cuz it's lagging so set subject here is going to be a it's going to take in a value and just output void oh void cool and now we need the the two values right so is the the array of values that we are going to pass in here to the TCH input so two values is going to be label string is going to be array of this and set two values is going to be the same thing and for we do the same thing for the the CC values right and then we also need uh the the people that were actually sending it two right so the two is going to be a array of strings and then we're going to have a handle send function that we're going to pass in as a callback so value void okay and then we have a sending as a Boolean so that we can disable the button when it's sending and we also want a default two bar expand to be a Boolean right because you can see that by default here by default here it's not expanded right but when I'm in the compos button by default the two bar is expended so I just want to pass that in into our email editor props okay and so now I can just destructure everything from my props and I can pass this into my stuff here so for my values I can just put my two values then on change I can set my two values I can have my uh default values here as well here let me see does this make sense the default values to be the two values um you know what we actually don't need the default values to be passed in here right because the default values will pass in as the two values right so let's just remove this default values let's remove it from here cool and we'll do the same for the CC values and on CC change we just set the CC values and then now we can pass in the subject and the on change to be the subject on the set subject here cool so now that we have this super kind of reusable component right let me show you we got this super reusable component right stuff is going to like kind of start showing up here so let's see all right cool so now we got this C and stuff like that and then let's come down here so right now we got this tool handle send and a sending that we're not using so let's use this tool right we can replace this placeholder I'm just going to do to. jooin by comma so imagine you have five emails I'm just going to join them by a comma separated value then whenever we set send on click right what I'm going to do is I'm going to call the handle send function passing the editor value right and I'm going to disable this button when sending is set to True right and one more thing here is whenever they click Send I'm going to just await their function right but I'm so I'm also going to call editor do commands do clear content so after they click Send I'm just going to clear out like reset the input and then send it out so hopefully this makes sense and so now we we have used all the props one more is default value two bar expanded so let's just put this as default value two bar expanded Okay cool so now we got this props working right so now we can see that obviously the stuff is failing because we did not pass in all the props into our email editor right therefore it's failing this error so let's actually do this so the for the reply box we need to pass in like we need to pass in all the props needed right as we have defined in here so who is a subject who are we sending it to who is a cc so you can see that we will need a function so that whenever we we we are on the threat we need to know who we're trying to reply to who is involved in the CC's what's the subject of the threat so let's actually create another trpc endpoint to receive the Trad ID information and return us with all this reply details right so let's come down to our account router again and let's have a function called get reply details and this is a private procedure right that takes in the account ID as well as a threat ID that I'm trying to look for the information of right and so this kind of get reply details right the first thing is we authorize the account access and then we have to find the thread in which we're trying to get the details from right so we'll get where ID equals to input. tread ID and what do we want to do we want to include the list of emails inside of it I want to order the emails by the send at to be a sending order and I'm going to only select a few field from it I'm going to select the from the to CC the send at the subject right also the BCC and lastly I need the Internet message ID so this internet message ID is needed because when we hit the oringo Epi to say hey send a message in reply to this email I need to pass in the internet message ID in which I am replying to so that oringo can properly handle the threats for us right hopefully this makes sense and now that we have this threat we're going to check so if there's no threat or the threat. emails. length equals to zero that means we have not found a threat so let's just show an error right if not we're going to find the con's last external email to be threat. emails. reverse right find I'm going to find the email. from. address does not equals to account. email address so I'm basically finding the last email that does not belong to me right so it's like the the person I'm replying to okay and then obviously if I have no last external email I'll just thr an error cool and now finally I'm just going to return a list of objects so I'm going to return return the subject to be the last external email the subject so this is the subject I'm replying to we need to return uh to who I'm sending replying to right so it's going to be last external email. from right then I'm going to spread out the last external email. to I'm going to filter the tool by whether the tool do address does not equal to my email address cuz obviously I don't want to reply to a list of emails that include my own email and I'm going to basically do the same for the CC's right right I'm going to do last external email. cc. filter out my email address and who is it sending it from so I'm sending it from my account obviously right so therefore I need to say my name is my account. name and the email sorry the address will be my account. email address and the subject will obviously be the external email. subject uh actually we have that as well already and lastly the the ID will be the internet message ID from that last external email that I'm trying to reply to so with this endpoint set up now I can finally find out who I'm trying to reply to so I'm going to get data to be the reply I'm going to rename it to reply details I'm going get I'm going to get this from my API do.get reply details do usequery and so I can get the current thread ID from use threat right and I can also get my account ID account ID so I'm going to pass these two things in right the account ID and the Trad ID so this track ID might be now so for now I'm going to put empty string in replacement of it okay and so if you come back to the reply box here so now what I'm going to do is I'm actually going to have another component called component right and so for now here I'm this is the actual component that's going to return the email editor because this reply detail might be now at first and so if there's no reply details I'm going to return now and only after we have populated that means after we have fetched down the reply details then I'm going to render this component I'm going to pass in the reply details here so obviously this component is going to get the reply details that's not going to be now of course so to get the type of the return details I can use my router outputs here I can index to account and I can index into my endpoint so I want to get the return type of this endpoint right and now you can see the type check Matrix okay and in here I'll also just copy down the track ID and account ID so I can access them okay and now finally finally I can start crafting my props that I need to pass in here right so I need my CC values my set CC values my subject my two values and everything else so to do that I'm going to construct some props subject and and set subject right it's going to be US state so this is going to be obviously the subject of the details but the thing is I to add the re colon in front of it so it should signify that I'm replying to an email so I'm just going to check first so reply details do subject so if this starts with re that means it already has a reply I don't to add another reply in front of it so if it really begins a reply I'm just going to put the subject inide if it does not start with a reply I'm going to manually add a reply in front of the subject here right and then for the two values right it's going to be basically a list of label and values where I'm just returning this right and obviously the the name might be the address the label might be kind of the label will be the address and the value will be the address as well I'm just mapping through the list of tools that I'm trying to send to right and then I'll do the same for cc values right and now I can finally pass this in here so the sub will be the subject State here right set subject and I can have the two values to be the two values and then set two values and I can have the CC values and the set CC values right and then for the two I can just map over the two and I just want to reply with a list of addresses okay and finally you know when I want to kind of I'm going to have a use effect here that basically updates my state whenever the threat ID and the reply details change so you can see in my app right you can see that we have this subject and the tool but the thing is when I'm switching my threat so I go to another threat you can see that it automatically updates the state here right even though is the same component right you can see that the the subject changes and the threat and the two values changes because I'm basically uh resetting the values whenever the threat ID changes so let's actually do that so I'm just going to check if not threat ID or not reply details I'm just going to return now I'm just do a early return okay if not I'm going to set the subject so it's going to be the same logic if reply details starts with re right I'm just going to going to uh addit here subject reply details oh my God TPT is did again right so if it's let's see reply details where is this reply reply details do subject do starts with reply wait where is my types um reply details let's see what what's the issue here uh oh sorry it should be if not try ID all not reply details so if it starts with this right else I'm just going to set the subject here directly and I'm going to set my two values to basically what we initialize here but now we're just basically resetting it every time the reply details and the Trad ID changes and so finally the email editor requires one more thing I mean two more thing is the handle send and sending so for now we just have a empty function right handle send right we're going to put in the functionality later but for now it's going to retrieve the HT HTML value from the input box and I'm just going to conso to Lo out the value for now okay and then is sending is going to be always true for now so always false for now and hook we'll hook this up to the function that is going to be sending the emails later okay so now that our email editor is set up right if you go back here to our app you can now see that I can see who is sending it to and I can see the the subject properly being uh populated right so if I go to Gmail I can see that I'm replying to this subject with the two values being set to the Gmail account cool awesome stuff so now we can also reuse this email editor later on in the compost button here okay awesome stuff awesome stuff so now what I want to work on is I want to show you how I can add the AI features here right where I can type in like uh draft and email right so I click generate I'm going to show you how to kind of add the email functionality into the auto complete box okay so let's do that in the next section okay so before we continue on building the AI features you need to have an open a API key so come down to platform. open.com sign up if you don't have an account and sign in if you already have an account and so what you need to do is just come down to the project that you're working on right you can create a project if you don't have yet and come down to the API key section and just create a new secret key in this case I'll just create a YouTube API key right and then I'm going to press create secret key so now you have this opening I key just make sure you copy it right now you can close the tab I want you to come down to your EnV folder and just put in um open aore API key it's crucial that you enter it exactly open a API key if not this will not work properly right because the libraries they'll be using to interact with open API key assumes that you have this EnV variable sets cool now that you have this you can come down to close the EnV file and so to continue we want to build this AI button on this right side here right so to do that let's actually put this where create a new component and let's create let's call it ai- compost Dash button. TSX right let's do use client TS RFC right AI compose button right and so it's going to it's going to expect two props right the first one is right whether is what I want to know whether it's composing so whether are we in the composing mode or are we justl reping people right and we we need to use this information because when we are replying people right and this kind of B will have access to the whole email thread as context but when you're composing there's no email threat in context so you don't have to pass in any context into the chatboard then I also need to have a callback function on generate right this going to take in a string a token to be a string right and void so whenever we're going to start streaming back response from opening and every time we receive a new token we're going to call this on generate callback function that we pass into this AI compost button so then we're going to take this token individual token and then we're going to append it into the editor right cool so now we can finally start making this so we need to have like this model popup thing so come down to ui. shen.com and search for dialogue and so we're just going to copy this right copy this component let's copy the import statements right and let's copy the usage right let's come down here so let's also import this AI compos button right we'll put it right beside the right under the D for cursor pointer AI compose button I will pass in the E composing to be the default two part expanded and on generate cool and let's see oh what's this error button cool so now we got this component if you come down to our app we can see that this dialogue that says open right so we want to replace this with the the chatbot button so come down to your AI compos button let's just replace this with the with a button right inside here we'll have a bot icon with a class name of size five right and this button will have a class name of size uh sorry a class name sorry not class name but size of Icon and a variance of outline cool and then we're just going to have a state that checks whether the tracks whether the dialogue is open so when you click on this button obviously I'm going to set open to be true right so now this is how it looks like and then for the dialogue content let's just replace it AI smart compost AI will help you compose your email underneath the description let's give a spacing of two and let's give it a text area let's put in a text area let's keep track of the input so what what prom we are trying to set and we'll just control it by passing the promt into the value and then we're going to have is going to have a placeholder that says enter a prompt below that let's have a spacing of two again and then we can have another button right the say generate and obviously when you press on this button right I'm going to just close the model and set the just clear out the prompt and here is where we need to kind of generate uh call the generate function Okay cool so to actually call the generate function let's create a server action right by creating action. TS file so This Server action will will will be Mark with you server So This Server action is going to be run on the server it's going to hit the opening air API and going to slowly stream in the tokens into our front end so to actually write this server action we need to install a few libraries the first thing we need to install is at ai- SDK /op aai and the next thing is we need the AI function from sorry AI library from versel so just quickly install this to package right and then we need to import a few things from here so we need to import stream text from AI we need to import the open AI uh module from s AI SDK openi and we need to import port create uh streamable value from AI react server components right so this will be create streamable value and now we can export an async function called generate email which takes in the context which is a string and a prom which is a string so this context will be a list will be a concatenation of all the emails in the Trad right that will be passed into the context of the chatbot and for now I'm going to have a a stream object first right I'm going to call create streamable value and then now I'm going to do an immediate invocation of an Asing function right I'm going to call the await stream text function from uh from the kind of AI Library I'm going to get back a text stream here inside this dream text thing I'm going to pass in the model which is open AI I'm going to pass in GPT for Turbo right you can EX around with the different models and then the prompt right The Prompt this is what I'm going to do the prompt I'm going to just copy this and you can you can play around with it uh but let's do this okay so the prom is just you're an AI email assistant inside and better in the email client app your purpose is to help the user compose emails right and then you can pass in the time you can tell you start the context block the prom is here and some additional system information cool so after we get this uh text stream object here we can then for await cons Delta of text Stream So for each individual token so each Delta is like basically one token for each token of the text stream I'm going to do stream do update Delta so basically I'm looking at this text stream and then I'm passing I'm basically for each token account I'm going to pipe it into this stream object so I hope this Mak sense let's look at what is there's an error here it seems uh are you missing a semicolon so I think there's something wrong with the syntax here oh to okay yeah I just need to add this I need to add this semic colon at the back here to make this work okay so we're going to basically get the individual tokens from the TCH stream and then we're going to update this stream value here and finally after this whole thing is is done right under this kind of for loop we're going to just call stream DOD to tell it that the stream is completed and finally outside of this async we can just return an output which is the stream. value so this will create a a kind of a text stream that we can consume from the front end right and then to do this let me show you so we can now come back to our AI compost button right and then when you call this I'm going to call I'm going to make a function called AI generate right which is going to be async that takes in uh that just basically calls the server action so let's just call AI generate right so what this is going to do is we're going to just um for now we'll just get the output equals to await generate email so we can get this gener email from the action the server action that we we wrote just now right I need to pass in the context right but for now we have no context but the promt is something we can pass in here the prompt and then for I'm going to do for a wait sorry for a weight cons token of re streamable value from AI Rea component from the output so basically I'm going to consume the stream so remember this is the output stream right the streamable value I'm going to pass this into the read streble value function from RSC and returns an async iterator right meaning that we can just get the token one by one out of the stream and what do we want to do with the token we can just call the props to on generate passing in the token right and we'll do this only if there's a token like this cool so now let's actually try this out and see if we can see happening so I'm just to console to lock out the token for now and we can see it happening in real time okay so now if I press like uh write me a poem hopefully I should see the token start streaming into my console and yeah I see it happening so you can see blah blah so let your hearts be light and free embrace the beauty all around blah blah cool so now I'm going to take this token and obviously I'm passing it into the uh props on generate right so I'm need to go into the the my email editor where I'm using this AI compos button and this UNG generate function where is conso loging on the token I'm going to basically enter it into my uh editor so there is this function right so if I go down to my editor uh let me show you so this is editor. commands I can just insert the content insert the token into the text area the content field so now hopefully I should see right if I say write write me a poem you can see generate as you see you see my poem showing up in the editor cool so Bic what it's doing is taking the token one by one and inserting it whenever this on generate gets called in this uh token function right hope this makes sense right this is super cool awesome and then let me see set open to false right let's just put this at the end okay so now we got something working right I say hi it should be able to put in the token here right nice and I'm going to I'm going pass in the open and the open State here so that whenever I kind of uh reply I should close the model as it generates out okay cool this is starting to look very nice and so the last thing here that we need for this AIA button is we need to pass in the context of the email that we're looking at right so right now we don't know uh what this email is talking about so it cannot adequately reply for us so to do that right okay so let me show you the issue that we're having right now so if you go into our studio and we go down to one of the emails uh let's actually restart our Prisma Studio here so Prisma studio right we can see the email it has a body let me show you the body where's the body is this huge HTML document and there's no way we can pass this into open Ai and expect the results to be good because it's so OB obfuscated right there's like html text there like input tag there like images links it's just so long so to do to help us basically trim it down we need another library that is able to help us take HTML code and convert it into a normal markdown which means we extract the important text information out of the HTML document and just pass that stream down version into opening a for for the email context so to do do that we have this Library called turn down right which is a HTML to markdown converter right which allows you let me let's look at the demo so you can better understand so it takes this whole mark this whole HTML and strips away all the HTML text to leave the only the important text that we can then put into opening EG so let's see let's install this library right so let's come back here we sud sudo B install turn down right and we so also need to install at types SLT turn down so we can some get some types script types right then let's come down to our lip folder and let's create a new turn down. TS file right this is where we're going to put in some configurations for the turn down okay so I'm just going to copy this in let me show you so what we're going to do is we're going to import the turn down service from turn down we're going to export this turn down so this turn down API is what we're going to be using to kind of strip the HTML out into markdown right and so if you look at how turn down is being use you basically require the turn down service you initialize a new object from it and then you can just call the turn down service on the HTML and here you can pass in more options you can add rules to kind of filter out text that you want to replace it with right so if you come down here we can see that I just basically put in some uh styles that like you know like we add a rule we remove all anchor text we remove all style text remove all scrip uh script text and we remove all image text so this just to make it so that when we pass the HTML the output will be even cleaner so now we got this turn down service right let's go back into our uh let's go back to AI compos button so here is where we need to get the context right so you see right now we're passing in empty context here so let's just initialize the context to be a string right so we'll passing the context here so how do we get the context remember we can get the list we can get the threats from use threats so we're trying to get the list of emails from the current threat right so we also need the current threat ID so we can get the threat to be threats. fine where the thread ID goes the currently selected thread ID okay and in here we want to check right if props dot is composing so obviously if I'm I'm writing the email there is no kind of there isn't necessarily any context I need to pass in here therefore I can just directly skip it but the thing is if I'm not composing that means I'm trying to reply to an threat here is where I need to compose my threat so I'm going to do my context right uh what I'm going to do is um for conss email of thread. emails right so I got this individual email I'm going to get the content sorry this will be called context and the content will be um let me show you let me show you I'm going to just concatenate this stuff so I'm going to pass in the subject to be email. subject the who is it from who is it to right I can just pass it I can join um email. to doesn't exist it's fine we just have the email from and then we can do we have the email sent at let me show let me see okay we have to send that so we can also have the send at new date do local string and then here is where we can pass in the body right so the body we can we can import the turn down service that we just has we just found so let's import that import turn down from / turn down and we can just pass in the body right if not we'll pass in the email. body snippet right if not we're just passing the mty string so basically this will strip off the HTML of the body and and put it in line here right and then finally finally we can then concatenate this context equals to plus equals to content so we're just looking through all the emails and com putting it into this context and then finally we can then uh put this context into here and one more thing is I need to tell the open who I am so I'm going to say cons uh I get my account back so I can say finally I can say contact plus equals to I I my name is account name and my email is account do address like this so they just basically appens a small the small information at the end of the context so opening ey knows that I'm emailing from Elliot so hopefully now if I go back here and let me actually show you the content so I'm going to just console to lock out the context before we actually send out the email so let's go back to our code here so for example let's look at this so this is a HTML email right so hopefully if I come down here and I'm going to make this smaller I'm going to sa reply and press generate you can see this is the context and you can see that we successfully got the context being written out here as well as have the of I have my account information being put in here so I can see this is the the context we got the subject we got the body and you can see the body now is part into the nice markdown format to be inputed into open Ai and then at the end I can say my name is Elliot and my email is this and that's how open AI gets context of the entire email threat and is able to give very accurate information when it comes to replying the email yeah so I hope you understand the reason why we're doing this right cool so now we got this kind of AI chatbot going so now let's actually finish this by writing the command J function because it's extremely similar to the AI complete feature okay so let's come back down to our email editor so you remember here we're doing meta J right so let's actually check that when I press command J I can see a it being logged out so if I press command J you can see that meta J is being logged out that means my shortcut is working cool so we will actually also come back to our action. TS this our server action right it's going to be a very similar function in fact it's actually the same but with a slight Swig so I'm going to copy this in because it's the same function I'm going to come down here right I just wrote a new function that also does create streamable value and we're also going to hit the gbt 40 sorry this gbt 40 and I'm going to say that you are a helpful AI embeded in here that is that is used to auto complete sentence blah blah blah and then we're going to stream in the tokens in the update and pass back the output so we're going to call this generate function whenever we press command J so let's come back to our email editor and then whenever we press command J let me show you here I'm going to call AI generate uh sorry I'm going to call a generate and then this function con a generate will be an asnc function and so what I'm going to do is I'm going to get the output from a weight generate so this generate from our server action and we'll pass in the the value the value which is the current uh thing that I'm typing in the the editor right because you can see the editor uh what's the editor the value is what I'm typing in here so I'm passing in the value into the context here and then what I can do is for await cons token of read streamable value so it's the exact same thing we did just now we are basically looping through each token that start streaming back in and we're just going to insert it into the command and so if we do this now if I press deer and I press command J you can see hopefully uh yeah I generate let's see why is not calling ah a generate um so let's just try console the lock out um AI generate let's see if you can see the value here first so say here oh it's still meta J let me see why uh oh maybe we need to refresh the page to update the editor so let's try again I say here Google press command J and okay I see AI generally being conso locked out but this doesn't seem to be a coming back okay so I think there's a issue with putting the editor command in this Asing function right so I know I'm getting back the tokens it's just that I need to have an external cons token set token to be a string and I'm going to do is I'm going to just set the token to be token and then outside of this function I I can just have a use effect that reacts to each token change and then I can just call this here right and I can bring this down here I'm going to reinitialize this every time the editor changes so let me walk you through what is happening okay let's just see if it works first if it doesn't work there's no point right so let's try dear Google and Okay cool so now I can see that it's working if we press command J things are Auto completing so what's happening is every time I press command J here we're going to call AI generate we're going to pass in the the the text content of the editor and then once we're in this AI generate we calling the generate server action then We're looping through each token that is streaming back and we are setting the token up here in the state and then because this use effect has the token as the dependency array it's going to detect that the token has been changed and it's going to call the editor command insert content in which it will then insert the token in here cool so so this makes sense this makes sense so now that this works and we now got the command J Auto Complete working as well as the chatboard working right so these are super cool features that will help basically you write your emails faster give you like a base template when you're actually drafting your emails yeah so cool so now that actually completed with this reply box we can then now go on to making the compost button and just putting this email in here and once we have that we can start sending out the emails okay so now let's actually make the compos email button which is this cool sliding sheet that comes from the bottom right so to do that let's just come back here to our theme toggle so we're going to put it right beside our team tole right so let's come back to our code let's come back to our page mail page right and where we put this team toggle you know what we can also put user button here so this user button comes directly from clerk so they really give us a nice user button that we can add beside it right let's actually also have a nice kind of center. Gap to going on here so you can align them side by side cool and so we're going to put our we're going to put our kind of sliding sheet up here for the Emil compost so come down to sh cnii come down to search for sheet right so you can see that this sheet that slides in from the left and from the right here right and so what I'm going to do here is I'm going to copy this uh soorry this not a sheet actually it's a drawer yeah so this drawer is what you can see you can like slide up from the bottom and from the and you can drag it down like this with the with kind of the handle so let's copy the actually let's create a new component first let's just C this compos Das button. DSX use Cent TS RFC uh actually just RFC is fine compose button so we're going to import the drawer from Shen and let's copy the definition and now let's just paste it in here to start with let's import button and let's come back here and let's just import the compos button oh not this compose button so let's save this two files so if we come back down here we should hopefully see this compost button right that we can like slide up and down cool so let's come back to our compos button right so this button let me see yeah so this button let's actually put a button that says compos here right and let's actually add a pencil icon beside it so the pencil icon will obviously come from the from Lucid react so pencil and let's give you a class name of let's see size of four and margin rate of one cool so now that we have this um what we're going to do is let's see let's see come to the draw title let's just rename it to compose email and then the description let's just remove the description and we can remove the the footer as well so we don't need a footer so under the drawer here under the drawer kind of hater we can actually directly put the email editor so remember the email editor that we have crafted we can finally put it in here and we need to pass in all these props in order for this to work right so the first thing we should pass in is that's construct cons two values right it's going to be just an empty array of labels and values and we'll have the same for cc values right and then let's have the subject and the body we can ignore but the subject we can have that so then let's pass in the two values in here and then everything else on set CC values on set two values yeah I think it's fine so just passing in on the props here and then we also need a few more things we need handle send so let's just create a fake function here for handle send first handle send uh let's see let's see yeah this a Asing function that just console locks out the value that we get back here and then we can pass in the handles s and is sending let's just set it to false for now we're going to build out the sending feature in the next section okay and then I think that's pretty much it right what else do we need we need the two so the two is just going to be the two values do map right two value it's going to be a string and then we're going to have a default two bar expanded to be true right so that we can by default when we click on this you can see that this thing is expanded right so cool now we got everything working we got the tools and the CC's and we can drop the email here right and the contact still works the smart compos still works awesome so let's actually continue writing this out um you know that's actually that's actually all that we need to have the compos button work okay that was quick because you see we we basically have a reusable component here that we can use in both the replies and also in this compost so with that I think we can start moving on to the next chapter of sending emails awesome stuff so now let's actually start sending out emails right so from to begin let's actually clear out everything I'm going to go back to our orink file right so this is where we export out all our server actions and everything related to oringo so now let's come back down here and let's actually export a function to help us send emails okay so yeah sorry we're not going to come down to orink we're going to come to account. TS so this is remember where all we can put our methods for operating on accounts so because we're going to be sending emails from accounts I think it will be suitable to put it here okay so to do this let's come down to the account and then let's actually export a new function let's create a new function to send email so we'll just Define an asyn Asing function called send email and so this email is going to take in a lot of things it's going to firstly it's going to take in a from which is going to be an email email address from our types right and then it's going to need a subject which is a string it's going to need a body which is also a string we need to see who we are replying to right we need to have we need to know the references which is just going to be a a string as well we need the tool which is a list of email addresses we need to know if there are CC's which is also a list of email addresses and bccs and also a reply to right who are we replying to cool and so now that we have this let's destructure everything here and so this function is going to basically put be in a try catch so we're going to try cont response equals to await xus do Post right we're going to do uh https api. oringo doio want/ email/ messages right and we going to pass in everything we have here so pass in the from subject body is in reply to so this will be in reply to in reply to the references the thread ID oh I think one more thing we need to do is the thread ID as well so thread ID which is a string right and it could be optional ID when you pass in the threat ID all right the tools the CC's the bcc's and the reply tool right is going to be an array but the thing is we just want one person in there so we're going to just put in the array with a single item okay and then obviously we'll just have the authorization barer token so this basically is the remember the authorization token for the individual account okay and then and then let's actually also have some params that says return IDs to be true because we want to get back the email IDs and the tread IDs after we send this request and finally we just console.log email send and we'll lock out the response of data and return it and obviously we just have some error handling if it's EXC error we'll just log out the error. response. data cool cool cool cool so now that we have this um we got this send email function let's create a let's go down to our account. TS router so this is our router and now let's create a trpc endpoint to actually call that function right so let's come down here let show let me refer to this so we need to send email let me see um okay so let's put this what issue here yeah so we need to send email route endpoint so it's going to be a private procedure and as always we're going to have the um we're going to have the account ID and we're not going to have this email stuff here because we're just going to receive everything here that we need we need the body which is a z do string we need the subject which is also a string we need a from which is the Z uh sorry yeah the from which is the email address schema right that we imported from types so remember initially we have this types uh file I did defined a z schema for you which you have which you should have really copy in here so it's going to be literally just uh email and address right and then we need to know um who are cing so it's an array of email schema we need to know who are BC seeing um we need to know who is it to right and the CC and BCC can be optional but we need to know who are sending it to right so this can be optional and then after that we can have a reply to which is also a z email address schema right we need to tell when we send out email the other person should know who they're trying to reply who they need to rep reply to and then in reply to which is a z. string can be optional and lastly most importantly we need a threat ID which is a zero string but it's also optional okay so now we can just uh let's see let's authenticate the users account goes to await await await authorize account access passing in the account and the user ID and then what we're going to to do what we're going to do is we're just going to construct a new account so cons account equals new account so remember this is the class that we constructed passing in the access token import this and on this we can call account send email right right we can do await and here we can then pass in all the emails so we can put the body the subject the from the in reply to so I think in reply to this is should be let's see what's this issue in reply to um I think here we can Define in reply to as optional here cool and then let's see we need what else we need we need references and references actually no references we don't need yeah so I think references alongside in reply to can be optional therefore have to pass this in okay and so finally we can just call this function so remember that this is a mutation so mutation is basically a function you can call to perform an action on the server right so now let's come down to our reply let's come down to our reply box so remember we kind of have a stop like a fake function for Cle send now let's actually send the email so the first thing I need to do is actually get the endpoint right so send email equals to API the account send email to use mutation so now I can call this this send email function here on the front end to actually send out the email right so the first thing I'm going to check if there's no reply details I'm go return and then finally I can do send email. mutate I can pass in the account ID I can pass in the tread ID right so the Trad ID can be tread ID or it can be undefined right and then I need the body to be the value so this value is the HTML input in the editor and then we got a subject we got a from which is reply details. from we got the tool which is reply details. to. map for each tool we need to reply a specific schema because we need the address and we also need the name right and then we'll do the same for CC's so this address might be the the name might be now therefore you put empty string in replacement and reply to will be kind of who I'm sending it from so it's reply details of from and in reply to it'll just be uh reply details. ID so this ID is the internet message ID right that I need to send in so let's come down here so this is the internet message ID that I'm replying to yeah and so if you come down here let's double check so send email in reply to so now this will be piped into this fun this procedure in reply to and then we're passing it in reply to in here and then they send email in the account will then hit the orle API to send out the emails like this cool so I think basically that is all and so if you come down to the reply box after we s after we successfully kind of send the email out on success we just want to con toast uh toast. success saying that email sent right and if there's any error we just want to conso lock out the error okay and we need this tools and in order to for the pop-up tools to show up so let me show you what tools we're talking about we're talking about this serer tools on the here so they can pop in alert so to do that we need go back to our layout we need to add a toaster here that we can import from soner so that we can display the tools properly oh my God why is it that every time I come back this layout it crashes my entire vs code that is so weird that is so weird anyways okay I'll just wait for this to finish and just reload my window okay so after adding the toaster in the layout hopefully now if I try to send an email by replying let's just reply to Elliot using here I'm going to say dear Elliot this is a test message okay and by the way here this sending is sending we can also replace it to send email. is pending so this just shows that whether the email process is happening so let's try this out I'm going to press send and hopefully we should see a success message okay cool email sent that means that the email has went through oro's API and oringo has helped us send an email on the Gmail's behalf and so if I come back to my production here and you can see if I come back to my normal email client right so this is the email I sent in reply to right I can now see that I received an email from uh my other email account here right with this with the email that I just sent to this text message right so now cool we are able to send out email messages so now let's just hook this up to the composing here so we can also do this as well so come back to your compose button right let's actually hook that up as well so we just need to import the cons send email to be API do do account do send email use mutation and this handle send we already know what we need to do so obviously if we do have account uh let's come down here we need to get the account from use threat if not account we going to return if not we're going to do uh send email. mutate and then we going passing the account ID to be account. ID right we need the threat ID so the threat ID is going to be undefined because we're not replying in respond to a threat we're just starting a new thread so theat ID can be undefined the body will be the value that we pass in from this variable here and then from we've did done this before the name will be from me account. name so I'm I'm the account holder right or me right and the address hopefully will be account do email address right or me example.com so we pretty much know that it's going to always be here so it's fine the two is going to be two values. map two we need to give back in name to. value and address to be two. value cool and then we need to do the same for CC's and then we also need to do reply to so I need to reply to to the sender which is me right and fin we need to pass in the subject and we are basically not replying to any email right because we're starting a new email threat so in reply to can be undefined and finally we can do on success and on error all right and let's just import tost cool and then now we can replace the button here uh is sending to be send email. is pending cool so now let's try this out so we come down to compost and I'm going to compost an email to Elliots using q.com let's try test email composing I'm going to send a b message dear Eliot and I'm going to send ital size hello from YouTube so I'm going to send this out and hopefully I should see a to email sent and if we come back to my normal email client I can see that my test email composing has been received on the other email but it got put to spam unfortunately but the point is that we can now send emails receive emails and kind of reply to emails cool and so now we can now get the emails but the thing is now I want to sync my emails right like you can see that I replied to this email thread but I'm not seeing my the the email that I replied to so now we're going to do that in the next section which is now setting up the syncing so remember in our excal draw that we drew just uh at the beginning of the video in the explanation so if we come back here and let's look at excal draw remember this part here right so now we have received a new email in our external inbox but the thing is it doesn't exist in our our database so what we need to now do is we need to set up a uh we need to set up a web hook within Gmail and oringo such that uh we can sync our emails to here and so what I'm going to show you is let me show you this so remember we have this datta token that we kept all along so so you come down to account right you see we have this next Delta token I can actually use this next Delta token to call my get updated emails endpoint remember this first endpoint that we were talking about here/ email/ as updated if I pass in my data token I should be able to get back the most updated emails from my account so let's try that so I'm going to go down to my cursor I'm going to come down to my playground I'm delete this I'm going to import um uh let me show you what do I mean by this okay I'm going to just do account equals new account here and I'm going to pass in the token so I'm going to copy in my access token here so this just for demonstration just to show you how it looks like right so I'm pass my token here and this account I can then call get updated emails right and I can pass in my data token to be my next Delta token here and let's see the results of this a wait so let's see the result of this if I run this so by right what I should do what I should see is my new emails being in this response so I'm going to do BU run Source playground. TS and hopefully I should see the updated emails so let's wait and boom do you see there hello from YouTube so this is the emails that I need to get right you can see that these are okay there's a big HTML payload but the point is that now you can see that uh when I I pass in the next data token and so this is the new the new token that I need to store in order to update my bookmark so remember this part here after receiving the new data token I should update my bookmark so when I pass in the bookmark again I can I will receive the updated emails so I can I should be able to I should be storing this in my after saving the the list of new emails in my database I need to then I need to then store this new data token right so now let's actually do this synchronization I'm going to write a function in here in this account right I'm going to write a function called async sync emails right so this is going to hit the updated emails I'm going to store it in my database and then update update my next data token so I'm going to come down here give me a second I'm going to look at my reference so get updated email sync emails okay so the first thing I'm going to do is I'm going to find the account that I'm looking at so await uh db. account. find unique where the access token equals to this. token right and if I have no account that means it's invalid that's through a new error right and if I don't have account. next Delta token that means something went wrong as well that means I have no bookmark so I can't sync it without a bookmark right so now what I'm going to do is I'm going to get response equals AIT this. get updated emails which is the function that we call just now in the playground right we get updated emails and we passing this data token so we going to pass the Delta token to be the account the next Delta token and so we're going to get the list of all the emails that we collect right because this Delta token right it might this response let me to you the response might have a next page token so it's basically the same process as the initial sync I need to keep checking the the updated email if they have a next page token so I have to keep going to the next page until I get to the end of the pation so the first thing I'm going to do is I'm going to let the the most updated data token the St data token to be response the next data token if there are any right uh let's see do make uh sorry account the next data token and then I'm going to look at the response so if response the next data token I'm going to set it to the put to be the store data token and I'm going to do a a for a while right W response. next page token so the same process I'm going to set the response to be await this get updated email passing in the P page token of the previous response and I'm going to take the emails to be I'm going to concatenate it with the records so I have like batches like let's say I have five pages I'm going to go through each page I'm going to concatenate the records into one final emails array and if I see any next data token I'm just going to make sure that I store it again and so finally finally what I'm going to do is I'm going to uh try catch I'm going to try to sync my emails to the database so I have this function member super useful I'm going to pass in all emails as well as my account ID right and this if there's anyr I'm console do error and then finally I can do await db. account. update what should I update I should update my next Delta token right because I need to update my bookmark and then I can just return this list if I want cool so now we got this nice utility function where we can just call on an account to make sure that we we synchronize all our current emails so I'm going to try this right so if I come to my playground instead of manually calling this I can just call A account. sync emails right and hopefully hopefully it should write to my database and I should be able to see my reply showing up here because we have successfully synced our emails so let's see what's the issue here okay it's fine so let's try this I'm going to run this I'm going to run this Soo bun run so we can see that attempting to sync four emails to the database and you can see I'm uping the emails and so if I go back here and boom now the emails are in sync and I get to see my updated message right you can see my reply right I can see the threat so everything is working basically perfectly so I can just update my emails so the thing is isn't this very Troublesome every time I want to sync my emails I have to call this function and to like get my uh to to get my emails here and so what I can do is I am I can just call this sync email function every time I fetch my emails so I'm just going to make sure okay I'm going to make sure to copy this so if I come down to my account. ts routers so every time I am fetching my get uh get threats here I'm just going to make sure I synchronize my emails so it always stay up to date so this is a polling polling infrastructure so I'm just going to have account equals to account access token and I'm just going to do account. sync emails and then I can say if there's any errors I'm going to console. error so what is this what this is going to do is every time I fetch so every time I'm on this page I fetch this it's going to attempt to kind of sync my emails you can see that attempting to sync emails to database zero that means I I don't have any new emails so what this does is every time I fetch it every time I fetch this page it's going to run my sync emails asynchronously in the background while it returns me the information and so anytime I have new emails hopefully this I can pull for new emails and then it will update my kind of database accordingly yeah so it's a super kind of convenient function that we can run now to basically make sure that we can always keep our emails updated cool that's awesome stuff so let's fix this hydration error I think I know why this is is because button eval I think it's because of our um what's it called our compos button right so let's go down our m page sorry page mail I'm going to just make this cons compos button I'm going to do the same thing I'm going to render it I'm going to disable server side rendering right so hopefully okay cool now my my hydration error is gone yeah cool so now everything should be in sync I can send emails and hopefully my emails will be up to date okay we're doing absolutely amazing and now finally we can get to the most interesting part which is the full Tex search as well as the AI retrieval augmented generation model all right so now let's move on to the full Tex search plus the AI functionality and to do the full text search we'll be using orama which is a kind of fast dependency free full Tex Vector search engine right and so we can just self Host this and just use the library out of the box right so it's like really simple to use you just like import the create their insert their remove move and the search functionality and you basically create a database with a schema and you just insert documents in here okay and so I walk you through like we're going to experiment with it and play around with it before we are comfortable with actually using it in the code so the first thing is let's go back to our vs code so obviously we need to install a few things we need to install so we need these two packages install orama orama and orama plugin data persistence so the plugin the data plug data persistence is for us to save the the basically index into our database and so that's what we're going to do we're going to come down to schema the Prisma and I've already added orama index column into our account and this is going to be a Json field so after installing this you're going to basically try to run sudo buan Prisma DB push to add the new column into our database so each account will have a list of index the orama index to store basically all the document and so let's actually play around with this by in by by importing a few things from as orama SL orama and we need the create we need to insert we need to search right and we need the type of any orama we just need this type for the use later and so I'm going to Showcase a few example right so the first thing we're going to do is let's just do uh cons DB equals to a weight uh create right and we're passing a schema and the schema have a um subject right which is a string so and then we'll have a body which is also a string we'll save the raw body right we have a from who is it from who's it to which is a array of strings what time it was sented okay and then we have embeddings but we'll save that later so the embeddings is for the embedded the retrieval augmented generation it's going to be a vector of Dimension 536 but let's ignore this for now I explain this later on and we need a tread ID as well okay and so what we're going to do is we're going to just uh let's just rename this to orama okay we need to get let's just try to get the list of emails right equals to await DB from Prisma email. find many right and I'm just going to select a few things we just need the subject we need the body we need the food is it from and and this stuff okay and for now for now I'm just going to Loop through for cons email of email I'm just going to console.log the email do subject okay and I'm going to run sudo buan run sour as playground. TS and I should see a list of kind of email subjects being logged out in my console so what I'm going to do now is for each email I'm going to insert them into this orama database okay and to do that I'm going to do a wait a wa it's insert right orama and then for the document uh is going to be let me see what should I do here um yeah I'm going to put in the email but the email is not going to be so we have to give you some type so let's see how what's the the Shipe like so let's go to the orama documentation okay let's look at some example so what they're doing is they they created a database of schema and they're inserting the DB and this is the actual stuff here so we can actually just put the first thing is we need the subject which is the email. subject we need the body which email. body uh let's see if this makes sense yep uh email let's see what issue here okay yeah just a type error I guess the from who's it from so email. from uh the address who it to email. to do join uh sorry we can just map it from two let's just get the address out and then we'll join it into a with a comma and what time you'll send that you'll be email. send that will convert it to a to local string and then obviously the thread ID will be email. thread ID right email. TR ID cool so let's just ignore the type error for now here ignore as long as you know that it fits is fine let's look at this uh why is this not working so body doesn't exist email. body we have this right email do email do subject and then the body oh because it's a comma colon em body okay yeah so we'll insert each of this document or sorry each of this email into the orama database right and for now you can see that nothing's going to happen right if I run this again uh let's see create error a schema validation because okay two because we're trying to insert an array of strings right so we don't need to join it right we can just directly send this in so let's try again and cool so we locked out each email subject and we've inserted in so now let's just try to query it okay so to query it right what you do is you can just do con result so let's search by orama okay and we'll search by the term of let's say uh class pass or or wait let's see what I have we have we have like fashion okay so if I search fashion I should be able to see uh this email right from Tommy Hill future because this is related to fashion he has some fashion in it so I'm going to then console. logout search result dot yeah I'm going to console search results so what's happening is I created a database I'm fetching all the emails for each email I'm inserting it into the orama the text engine and I'm going to search it by the term of fashion so hopefully I should be able to see a record you see I get one hit and I get the document so let's console the lot of the document so let's get the document uh the hits right let's do this so you see I get back my my document here is a little long but you can see I get back my my ID the score is how similar of it is to the term and I get the document right so what I should do is for consit of search. hits I can conso lock hit. document do subject okay so I'm going to get back the subject of everything that related and now I can see must have fashion pics and if I search for like let's say Elliot Elliot I should be able to see these are the emails I'm sending to myself and and from myself right and let's say if I search for Google if I just search goog let's see if this works and now I can see all everything is from Google so cool we now officially have a very cool uh full text engine that we can use in order to insert in the emails and search for it in real time so I'm going to convert all of this functionality into our own orama class so come down to lip and come down to Let's create orama .ts so this is going to house our our orama manager so orama client so we're just export a client and this is going to have like a few stuff we're going to have a private orama database so this is going to be a type any orama right so let's just import the type here and we'll just ignore the the TS ignore for now because I know like this going to get initialized anyways and we have a private account ID so we need to know which which which account we actually operating on right so let's do Constructor we'll take in an account ID and we'll set the account ID to to this we're going to bind it to this class or sorry to the object and then we're going to run ASN initialize right the first thing we're going to do is going to find the account right await db. account. find unique where is equal to account ID right and if this no account I'm going to show an error right and I'm going to check right uh um what I'm going to do here is let me show this if account do orama index so if I already saved this orama index to my column here if I already have like a save version I'm going to reuse the save version so therefore I'm going to set this to orama equals to await restore so I get this restore function from at orama plugin dat persistence right I'm going to restore it in the Json mode from account orama Index right and this can just be as any but the thing is if I don't have Aroma index column or if there's no value in there I'm going to create a new database so I'm going to call create insert and search I'm going to create a new database and the schema is going to be what we Define here right makes sense okay and then after we create it we're going to immediately save the index this do save sorry this do we're going to call Save index and this save index is another function that we can write here Asing save index so we're going to save the the orama database to our database so to do save index we just have the index to be await persist so this persist also comes from data persistence persist we'll just call persist we'll pass in this orama and we'll just put it in j on mode right and then what we can do is just await db. update we update the account ID and we'll save the index into our orama index Okay cool so after we we have written the function to save to our database and initialize it right we can let's write more utility functions like a search function search so it's going to take in a term which is going to be a string and so this term we're just going to return await search so search is from orama here okay and we're searching let's see we're searching by this orama and the search term is going to be whatever is passed in here cool and lastly we should have a asyn insert function here and so this is going to take in a email um actually no you'll just take in a document of any and we'll just await insert into this. orama passing the document and then we'll just await this. save index so immediately after we insert it we're going to save the index into our database and so let's actually use this orama client in action in The Playground so I can just delete a lot of this stuff here right we're going to have a cons orama equals to new orama client from our class right and we're passing the account ID so let's just take an example here here account ID to be 68589 right here and I'm going to just await await orama initialize right so this will create the index if it doesn't exist if it already exists it's going to restore it from our database so the first time is always going to create a new one and then for each email I'm going to do a wait orama client. insert and I can just insert this email here and then here I can just call orama search with a term and I can log it out so hopefully if I run this I should still be able to see the same result uh or client is not in function let's see why is not a function uh let's see orama do insert let's see a wa insert oh that's my bad so you see orama cent. insert is not a function of course because we need to be calling the insert on the orama instance itself not the class so we're calling on the instance not the actual client itself so let's clear again let's try again so I should get the same result right I get all my Gmail but this time look at this if I go into my database and I refresh I should now see a new where's my kind of uh Json index so I'm going to restart the the Prisma Studio and hopefully we can see orama index is like this big Json block that you can display but the point is that you look look at this internal document ID store so it's this huge Json block containing all our information and we can just load it in and search it up every time we need to use and so now we can actually just we don't have to insert this anymore because we can just uh remove this right so we don't have to insert it anymore we can directly initialize it and if I try to search again let me show you because we have it from safe sorry because we already have it Sav in our database if I run this again I should be able to see my alerts immediately I don't have to insert it anymore so I can search like Elliot right and this should give me the results right so because I'm reading it directly from our database this time without needing to populate it every time I need to run this cool so now we have our own kind of internal search engine happening right and so what we do is one more thing is um go to our sync to DB right so every time we are syncing an email to the database every time we add a new into email into our database we also want to uh put it into our orama Index right so that's what I'm going to do I'm going to do um cons orama equals to new orama client passing in the account ID and then I'm going to a away orama initialize and for each email I'm going to a orama insert right and what we're going to insert we're going to insert this here so let's just do this let's uncommanded out so we insert the subject the body the from the addresses to send that and then we're going to insert into our database so this way every time a new email comes in or we sing our emails right uh we're going to also write it in orama database such that now we can perform the full text sech here cool so now that we have that let's actually create the UI for the search bar all right so now let's actually work on the search bar okay so let's go back to our component where we are doing our search functionality so I'll just clear everything first come back to our mail page sorry our mail. TSX so this search bar let's actually create a new component here search dasb bar. TSX use client we have our fce right we name it to search bar let's use this component instead search bar cool so in this search bar what we're going to have is so everything's still normal uh we're going to just return a div here right let's see let's see how will be work how we do this okay uh search bar yeah okay okay here's what we're going to do we're going to have a div here the class name will be relative we'll have a input right and also have a search icon from lucd react this search icon will have absolute absolute left of two top of 2.5 size of four text muted foreground for the input um for the input we're going to have a placeholder of search dot dot dot let's give it a class name of padding left of eight right and then let's also have a search value okay but the thing is this search value search value we put it a use item so that we can because what we got to do here is let me show you uh boom boom so this search value here is as we are typing here you can see that this is a different component so we also need to have access to this component right here so we need this value here so that we can access it in in here so let's have an atom here with a string and so we have search value here to be used ASM and then in the input right here we can have a value to be search value and on change we'll just set it to e. Target value okay and then let's see I'm going to destructure um use threats right remember we have a we have a use we have a e fetching hook over here right so this e fetching is basically whether the get tras is matching so let's just display a UI here so uh what I'm going to do here is I'm going to have another diff here class name of absolute right of two top of 2.5 Flex items Dash Center gap of two and in here if it's fetching we're going to display a loader to right with a class name of size of four animate of spin text grade of 400 and then in here we have a button here so the button will have an rounded SM hover I want to have a BG gray of let's say 400 okay and then here we have a x icon from Lucy react with a class name of size of four Tex grade 400 right and then for this button when I click on it on click I'm going to clear out the search value so now if you come back here now I got this oh where's my local 3000 here okay so let's go to SL mail and so now you can see I have this thing that's showing up here and I got the the cross button right and then for this button one more thing I need to do here is R let's remove this padding okay so now it looks nicer and for the whole search bar I'm going to give it a padding of four so now it aligns better actually I need to put this padding of four margin of four here cool so I got this margin of four I can search this is updated and I can cross it out here to like clear it out okay cool stuff so now now as I as I'm typing here I want to make sure that I can see right I can see uh my message my search results being show up here so I'm going to have another state here which is the use searching State because I need to know whether I'm on I'm still searching or not so I'm going to have you is searching atom to be a Boolean atom and then here I can just have a I'm going to use atem here and so one smart thing here I'm going to do is on on blur on this input here whenever I click on this input right on Focus I'm going to set the a surging to be true okay and on blur I'm going to have a function here I'm going to call a function that says handle blur right and I'm going to say handle blur but the thing is for this handle blur I'm not necessarily always going to set searching to be false right if I have if such value equals does not equals to empty string that means I am currently searching something and I basically click out of it so imagine I'm like searching something I click out of it I don't necessarily want to disable my searching mode because maybe I just want to refer to another email right so when I focus out of it if there any value inside of it I'm not going to set it to false okay hopefully this makes sense to you it'll make more sense to actually code out the the display okay so now that we have this value here let's go into the to the threat display because this is what is being shown here right no message selected so now come down to our our threat display right the threat display and we're going to just import the is searching value from use atom is searching atom right I think we need to export this yeah let's export out the both items here so that we can go into display item and then we can import it okay and then we can also have a search value here uh actually we don't need a search value we just need to know whether it's searching or not so we're going to come down here and you see this threat thing here so if we are searching if we is searching we're going to show a UI that says searching okay but if we not searching then it makes sense for us to display the thread information here so I'll pull this thing up hope this makes sense uh here make sense so now we can see if I focus in here you can now see that it say searching right and I focus all of it I'm no longer searching but if I press here and I I have some stuff in my search value and I press all of it you can see I'm still always searching make sense only if I clear away so if I clear away I should also prbly set to be false so if I click on here I should probably set searching to be false so if I click on this and I press cross now I reset everything cool so now that we have this we can now have a we can display the information that we need to be searching for okay so come down here let's actually have a search- display. TSX so this search display is for my for this for this stuff over here okay the list of search results so for the search display I'm going to have RF FC sech display okay and I'm going to have my search value to be used atom search value atom right and I'm going to display this instead of this component here I'm going to display the search display component right and over here what I'm going to do is I'm going to just display the search value here so now if I come back here you can see that as I'm typing here on the on the right search display I can show what I'm typing here okay and so I'm going I'm going to delete this I'm going to return a diff here that says padding of four Max height right Max height I only want it to be a calculation of uh 100 VH minus 50 pixels then overflow y of scroll right right inside here I have another diff here with a class name of flex items the center gap of two margin bottom of four and I have a H2 here that have a class name of text Gray 600 text- smm when it's dark mode I want text Gray of 400 if I this H2 I can say your search for search value cut came back with dot dot dot okay so let's just replace this with quotation marks so now I can see here I can see this UI being show out here cool and now I should actually do the actual searching right so let's actually create an endpoint here under my account router let's go all way down here right let's create a search uh search emails and point right this going to be a private procedure right the input I'm need the account ID that you're trying to search for trying to search in and also need the query that you're trying to search for okay and so this is going to be a mutation right so this mutation is going to do we're going to first authorize the account access and then we're going to have a orama be a new orama client here new orama client passing in the accounts. ID and finally we can then do or. search passing in the term to be the input do query and then we can return the results hope this makes sense so we're just basically inting the orama client so what this does is it goes in there it calls oh I should call away orama initialize as well so when I call initialize it's going to find the account related to account ID then I'm going to restore the orama index if I have it in my database it's not it's create a new schema right so once I restore it in here I'm going to call or. search which as we saw just now just search just through all the emails with the query and then I'll return the results right so now if I come back here I'm going to have the search endpoint to be API do account. search emails do use mutation right and then every time my search value changes I'm going to console.log searching for search value so let's just try this first right so every time my changes here you can see that I'm searching for something but you see it doesn't make sense for us to search on every input because it's very kind of uh consuming on the back end to be spamming our endpoint so what I'm going to do is I'm going to debounce it which means that I'm only going to trigger this function every few seconds only after you stop typing for a few seconds so imagine I'm typing like Elliots like very fast after I stop typing for like say 500 milliseconds and then I'll call the search function so to do this I'm going to have another function another thing called D bounc search value and we will call use debounce value which we can import from use hook. TS and we can pass in the search value and this second argument here right you can see that it Returns the BCE version alongside a function so I'm going to only update this every only if I stop typing for 500 milliseconds and so now if I try to do debound value search instead right you can now see as I type you see alot you see nothing happens but only after I stop typing for 500 milliseconds it will display time Elliott if I stop typing for 500 milliseconds it show OT right but if I keep typing like spamming you see it doesn't show up only if I stop typing and then it will show up so now I can ensure that I will hit the API uh in an appropriate amount of um of time so let's do that so I'm going to do if debounce if not debounce search value that means Mt value I'm going to return okay and I'm going to do search. mutate that means I'm going to do search I'm going to get account ID so we already done this before the account ID we can take a from use threat threats right passing in the account ID as well as a query right and then we'll just get the the the sorry account ID out in the kind of dependency array as well so if I do have account ID I'm going to return as well do an El return and lastly let's put in the search into a dependency area so now if I am uh if I am searching right it's going to give me back the results right and I'm able to access the results of the search to the search data so I'm going to come down here okay and and I can do search. data so this data is the result after I mutated right so if search. data do hits uh hits. length equals to zero right I'm going to show I'm going to show an indication that says uh hey no result found mhm like this all right but if I do have results what I'm going to do here is I'm going to say for search. dat. hits for all the hits I'm going to map over it right for each hit I'm going to return a list item with the key being the hit. ID right and then I can have a basically let me show you what I shall do here uh the class name the list will have a class name of border round the medium pading of four when I hover over I have a badging background G of 100 cursor pointer transition that's all right and when is Dark mode and I hover over it I want to have a background of 900 okay and inside this list item I can finally have a hit tree there a class name of text base font medium right and in here can have a hit do docum title right and then have a a paragraph with the class name of text smm text grade 500 and here I can display the sorry hit do document subject and here I can show hit. document dot uh from oh sorry from like this I'll copy this down I can say who is it to so I can say two do join with a comma and finally under the p tag here I can have a class name of texts imagin of Two And this is where I want to display the raw body right so if you go down to let me see s to DB remember we also putting a raw body raw body to be email. body right uh yeah wait let me let me see okay there's one issue here that we need to resolve which is that whenever we insert this em body right uh we need to I okay so uh let's actually change one thing here that we that we're going to be adding to the orama database okay so the first thing here is let me just go back down to my Prisma Studio okay let's see here what's the issue oh yeah let's just delete this first let's go down here so I'm going to count down to my account and I'm just going to clear out the orama index first so I'll just set this to be now and save this okay so let's come back down here so I'm going to reset my orama index because one thing I need to change is okay you see this schema right I'm going to have the body I cannot be inserting the HTML body here doesn't make sense because the HTML body is like with the HTML super messy right I'm going to use the turn down service that we have just now I'm going to have the body to be turned down do turn down passing in the email body or email. body snippet right we can also import from here like this so this will convert the body into the markdown format after we clean up all the text basically and then so down here the body we can just pass in this clean body and then for the raw body we can put in the body body snipper instead right so I hope this makes sense to you so the body is the cleanup version of the HM body whereas the raw body that we're inserting is the body snippet okay and let's go back into the orama in client here to just update our schema a little as well so raw body and body string okay that's fine actually with everything here so now let's actually run playground. TS to insert it inside here and hopefully okay looks good and now let's start up our development server again okay so let's go back to our search display right we're going to be displaying the body the raw body okay so let me show you here uh search display okay for this P we're going to do dangerously set inner HTML the HTML is going to be the Dom purify which we have used before do sanitize hit do document dot raw body so this raw body is the body snippet right and then use profiles right HTML to be true okay so hopefully now we should be able to search for Stuff let's see oh something is stuck in a recursive Loop and I think I know why oh it's because we have this search here so let's remove the search I think this is what is causing the recursive Loop so uh so about death okay let's refresh so I think the reason why is because every time we change it the function was changing this search function was changing and therefore it kept spamming the end point so let's try again hopefully this will this time it will work cool so let's search Google and boom we got it back right so I search Elliot you can see things are happening right if I search YouTube like you see hello from YouTube right so if I search YouTube right it works hello from YouTube cool so now we all got the search results coming back in right so that's all perfect it working is working fine do subject let's see yeah so let's actually see why there is no subject here uh hit. doent subject uh oh it is here but we have one more oh because here the list item here right so what I'm going to do here is just remove this list description wait list none y so I remove the dots on top here cool and I'm going to add some spacing into this I'm going to wrap this in a unordered list here Ur and then I'm going to have a class name of flex flex-all gap of two so it space it out nicer and cool now we have a full full Tex engine working you see it's working perfectly and I'm going to remove this as well course if there isn't I'm going to just clear it away Elliot if I clear it out like this boom so now it's working perfectly if I search for a result like test right normal human awesome so looks to be working perfectly looks to be per working well and one more thing I'm going to update here is uh remember the syn to DB here I'm going to do the same thing for the body here right so I'm going to come down to playground and because I did the body here I'm going to come down to this so I'm going to import turn down and I'm going to replace this body with this and the raw body will be the body snier okay I think with that we have completed our full Tech search engine and now we're going to move on to basically creating our uh ask AI button which is the most exciting part of this awesome so we're not going into the final part of the application which is the uh retrial augmented generation right so you know how you can like so you can ask any question to the AI right and is able to answer with the relevant information and so how it works is here's how it works so on High level overview so imagine you have like let's say you have this bunch of emails right and then you ask a question to the AI like when is my next flight okay so obviously CH GPT doesn't have information about when your next flight is because it doesn't have access to your inbox but in this case we do have access to our inbox but the thing is we don't want to Feit in like our entire inbox and stuff all the bodies into GPT right because we're going to run off contact tokens and that's just like not efficient way of doing it so the high level overview is when you ask this when is my next flight right we're going to pull in the in the relevant information into the prompt the context window of GPT so we're going to put in like let's say this email is like a booking confirmation and then this email is like uh online checkin information right we're going to put in this two emails into our GPT context window so this like GPT right so it will have this booking confirmation email and this online checking email and then partner along with this question then CH GPT will then finally be able to give us a a relevant answer to what was in our email so the high this is a high level of RW so now how do we actually pull in the relevant information so this is where the idea of embeddings SL vectors have to come in so a vector is literally just a a line an arrow in a 2d space so imagine this a cartisian coordinate plane we can have a arrow that's pointing let's say the coordinates is 1 one so one unit to the X Direction and one unit to the Y Direction and we can have another another arrow pointing somewhere like let's say this is like 0.5 and let's say 1.9 so is 0.5 units in the X Direction and 1.9 units in the y direction so if you have two this two of these vectors in 3D space I mean in 2D space we be able to calculate how similar they are by looking at the the angle between them right so the closer the angle the more similar the vector is so imagine if we are able to make this concept but in terms of semantics meaning like text so that's what embedding means right embedding like when you say you I'm going to embed a text what you mean is you're going to take a paragraph or you take a text a sentence or something and you convert it into a vector which is just a converting text into a array of numbers right blah blah blah blah blah you can like have up to however many dimensions there are so in this case this 2D Vector will only have is a dimension of two because there only two dimensions and two numbers right but uh embedding when it comes to text it could be up to in this case 1536 Dimensions if you're using if you're using open AI or open AI Ada embedding Dimension right it goes up to 1,5 136 text embedding space so what this means is when you input a text the output Vector is going to be an arrow in the 1536 Dimension space which humans cannot understand but to computers it makes sense to them and what this embedding contains it contains the meaning of the text so imagine I have a a text that says cat and I convert this into a vector so imagine this Vector is what represents the word cat and then I can have another embedding called let's say animal right so this animal imagine the embedding Vector will represent like this so you can see it's very close together because semantically the meaning is very close together a cat is an animal so you expect the meaning to be very close together when you embedded into a high dimensional space but then if you have another word like let's say computer right a computer would not be some was not be semantically similar to it so maybe a computer's embedding Vector is like this and so imagine you have like different embedding of all different sizes right here right and so then when I ask let let me say like for example this where like uh Mouse which is also a animal right and I have another thing like dog which is also similar to animal but everything like something that's directly opposite is let's say something like pillow or something like uh AI right so it has different kind of embedding so maybe computer and AI is more similar but pillow is completely separate in terms of meaning from everything else right and so when I search for animal Vector I'm able to get back similar vectors around it by looking at the cosine similarity meaning looking at the angle in between the different uh different vectors and so then I'm able to retrieve the similar vectors to my query and then I'm able to get this uh vectors and put it into my LGBT context and so this is exactly what we're doing imagine each Vector here is uh is an email thread right so you have email thread talking about different things maybe one email threat is talking about a booking is is talking about booking confirmation a threat is talking about online checking right so let's just take this example so let's say we have this email and once we embed it it means uh booking confirmation right and we have another email that is for online checkin right right so what we're going to do is whenever we ask a question hey what is my when is my next flight we're going to put this when is my next flight into the a function called get embeddings right so this get embeddings will take in text and it will split out a vector which is an array of numbers right and so we're going to put when is my next L into this function and we'll get back a numbers array which is an embedding and with this embedding we'll put it in the 3D I mean in the in the vector space so let's say the embedding Vector is like this right we're going to then look at all the emails with the most similar vectors uh in terms of degrees like the cosine similarity and this is how we can then pull out the relevant information it's all to this uh uh this kind of retrieval embedding kind of method right so we're just going to basically put this into our code and so every time an email comes in alongside our orama storage so see orama Vector right Vector search so you see being a vector database orama allows you to perform Vector search natively so what you have to do is instead of searching for a term you need to provide a vector object to search right so you can see is exactly what it is so you give it a title and you give it an embedding which is a high dimension representation of the text and so you just have to insert everything in here and then what you get back is a index of factors and so when you ever you want to query for a new question you just embed it and then now you can search through orama for all the relevant a text so I hope this makes sense to you and now let's actually begin coding this out so the first step is first step is let us actually go to lip let's actually create a file called embedding dots right so this embedding dots is going to expose a function right it's going to export a async function called get embeddings right which takes in a text right I'm going to try catch and I'm just going to hit the open a API so the first thing I'm going to do is I'm going to install so B install open ai- H right so this will give me access to the open API I'm going to import open EI API and the configuration object from open a-h right and then I'm going to have a config to be a new configuration passing the API key to be my open a API key which I have in my EnV file right then I can actually initialize the open AI variable with the API and what I'm going to do is I'm going to get the response to be await open AI do embeddings sorry create embedding right I'm going to pass in the model to be text embedding Ada O2 right and the input is going to be the text. replace I'm going to replace all the new line characters with the empty space okay and finally we get back a a number the result to be await response. Json Json and then we can just return results. data index0 do embedding as a list of numbers and if there's any errors I'm just going to call console.log error calling open AI embedding embeding API with error I'm going to just uh throw error cool so with this we not successfully get back this function and I can actually try out so if I can if I run console. loock await get embeddings with hello world right and I can run this function run s/ liip SL embedding hopefully I get back an array of numbers and that's what I get so this is basically the 1,536 AR array I can try this out by doing uh I can just do this and let me just conso the lock out the length of this aray and I should able to see 1536 so this is a vector of 1536 dimensions and this is what we're going to be used to search up the orama index space okay so before this right let's go back to our sudo buan Prisma Studio we're going to just reset our Vector space again so I'm going to go back to account I'm going to remove orama index so I'm going to just remove this set it to now and save it because I'm going to do is going to go back to playground and I'm going to Define one more thing in my schema which is the embeddings right sorry not embeddings here I should be Define it here embeddings so I'm going to store the embeddings here and where is my okay or my client here and embeddings to be a vector of 1536 and that's why just now we have a vector of 1536 Dimensions here and then I'm going to put an embeddings and how do I get this embeddings I can do this by doing get embeddings passing in the filter out body right and I can just put it here cool and so now if I run this again if I run solo Bun Run sour as playground. TS I should be able to insert everything in here with no problem so it's basically going through this one by one wait actually no I need to stop this one sec I need to stop this okay I'm going to say console.log anding length here let's run this again to populated so now I should be able to see my emails starting to populate in one by one right so it's just going to take some time because for each email we need to populate it by getting the embedding and like storing it into our insert and then saving it um let's see and one more thing here actually we need to save it right so after we have embedded everything here we can just do a away orama index to save this properly and one thing here I can do is I can just make this a wait promise the all to make it a to make it run in parallel so that we can speed this up okay so let's just wait for Mr CLA to do his thing cool all right so let's finish this and let's run it again so it's going to hit everything and hopefully it's going to be done so if I search for my Elliot I should be able to see my results after it has saved it to the database cool and you can see after we have entered it inserted it we can see now all the all the results coming back in okay so I'm going to do the same for our syn to DB function right so I'm going to get con embedding equals to A we get embedding with the body I'm going to embed this in here okay so we insert the embeddings in here as well cool and so now let us go into our orama let's create another function called async Vector search that takes in uh a search term right so what we going to do here is oh we're going to embed the search term and then we're going to do the we're going to search it within orama so uh here's what I'm going to do I'm going to do cons embeddings equals to get embeddings by the search so this is like me searching for uh when is my next flight so hopefully the flight the emails that is related to flights will be pulled out right and say cons results equals to await search this orama right I'm going to search by the hybrid mode right so hybrid mode means I'm searching for both the text and also the the vector I can say the term is a prompt sorry the term is the term okay and then the vector Vector the value will be the embeddings and then the property we are searching on is embedding so this name has to match this embeddings schema and then the similarity let's do 0.8 and then the limit let's just limit to 10 results at a time right and then we can finally just return the results cool so hopefully if I come down here and instead of entering in inserting all this here let me comment this out I should be able to search I should be able to do a Factor search by let's say uh Google and I still I should still be able to get back interesting data hopefully yeah so now I should be able get back information about Google right and hope and obviously like because I got some sensitive information I I don't want to show what my real inbox looks like that's why we're going to deal with some junk data right but hopefully if you have a real inbox with real data when you search for when is my next flight it will be able to pull down all the emails within your orama database that is related to flight information yeah so now that we have this search index we can actually build this UI out right so let's do that so let's come down to our um let's come down to our local host and let's finally come down to the ask AI part okay ask AI part so let's come down to our code let's come down to mail ask AI so let's just replace this with a real component called ask ai. TSX is client rce ask AI so let's import ask AI in here save this okay and then in here we're going to what we're going to do is um uh okay here's what I'm going to do I'm going to return the first thing I'm going to return here is a div here uh actually know ask eii will receive a prop of whether it's collapsed right because this is in the Side Bar right so if it's collapsed here I want to just hide this so a class name of padding of four margin bottom of 14 okay so if it's collapse first in the first place let's just return now okay and let's ask AI so now we have created a spacing here so it's floating up here instead and then we want to have this oh uh let's see here so we want to have this Premium plan Banner for our stripe Integrations but we'll work on that later but let's first set work on this AI thing here okay so I am going to have a so okay one more thing you want to notice here is if I say hello you can see there's a smooth animation that's that basically floats up whenever uh whenever kind of we enter the email right so to make that animation work what we need to do is to have frame or motion so we have that so let's import motion from frame or motion cool right and then we'll have a motion. div in here with the class name of um Flex Flex One Flex Flex One Flex Das call right items that's end ping bottom of four rounder of large background gray of 100 Shadow inner when it's dark I'm going to have background gray of 900 cool and then in here I'm going to have another diff here the class name of Max height of 50 view height overflow y of scroll with a full Flex Flex call Gap two ID of message container okay so this is the message container I'm going to have an animate presentence which is from frame or motion and I'm going to have a mode of wait okay and then I'm going to have a list of messages right but for now I'm going to have a con messages uh messages is going to be empty array right so I'm just going to Loop through messages messages map for each message right it's going to be any for now but it's fine uh I'm going to return a motion. div the key is message. ID uh let's see implicitly let's just do any for now the map uh will this work let's just restart this messages. map for each message I'm going to return a motion with a key of message ID okay so we'll get the messages later when we're interacting with the chatbot but for now I'm going to have a layout of position and then in here um let's see we have a class name so we're going to have a CN right so we're going to have a z index of 10 margin of two Max width of 20 50 pixels mhm break that words rounded that's 2 XL BG G of 200 when it's dark mode I have BG grade of 800 right and then I'm going to have a conditional I'm going to align it to the n and have a text grade of 900 right this will be if the message. row equals to user so you can see here oh let's refresh this oh that's weird so you can see if I'm is a user message I'm to push it to the end and if it's a assistant message I'm going to push it to the start and it's going to be blue color so that's what I'm going I'm doing here so if is a AI message I'm going to do self start right I'm going to have BG blue of 500 so the blue background text of white and this is if the message. row equals to assistant cool and then I have a layout ID to be message uh ID okay uh no actually I can have container Dash message. ID like this and then uh I can have a transition object so this transition object will be the type of e out and a duration of 0.2 cool so this just for the animation right and then inside this motion div right this is where we can actually put our message. content with the class name of padding X of three padding y of two text of 15 pixels and leading of 15 pixels cool and so let's see if this works um yeah should be fine so if we come back here we got not this background but the thing is we don't have any messages so actually let's let's link up the message but before that let's have the form input here right so underneath here let me check so motion div that should be fine so outside of this anime presence but inside the motion div let's have a new div here with a class name of with full so this is where we're going to put our text input to submit right so so inside this with full we'll have a form okay the form will have a class of with full and flex okay and then in here we're going to have a normal input with a type of text okay and then we'll have a very long class name of padding y of one relative height of nine Place holder I don't have a text of 13 pixels Flex grow okay rounded full border border gray of 200 background of white padding X of three right we want to have text 15 pixels and then outline of none okay and then will have yeah I think that's okay let's see how it looks like cool that looks fine and then let's see let's see yeah then let's have the button beside it so beside this input we're going to have wait sorry we're going to have a placeholder that says ask Ai and then under the input we have another motion. div and then the the key will be messages. length Okay and then yeah interesting layout layout equals to position and then we have a layout ID to be um so the layout ID has to match exactly what we put here here mage length and actually change here this should be messages. length uh minus one yeah this makes sense messages. length uh okay so once we have this layout ID um let's have a transition to be the same here and then initial initi show so this just for animation guys you don't have to follow this if you just want to skip the animation that index of negative one and the same thing for the animate property okay and exit with oppos of of one and Z index of one cool and finally inside this motion div here we can have a class name of padding X of three padding y of two text of 15 pixels leading of 15 pixels text grade of 900 dark texture of 100 and here's where we put our input right and we'll put we'll have the input variable here later on but then finally outside this motion div let's commment this out first this button with a type of submit class name of margin L of two Flex size of nine items Das Center justify the center rounded full BG G of 200 when is Dark BG G of 800 and in here we have a send button from Lucid react with a class name of size of four text grade of 500 when it's dark text of 300 so let's see how it looks like cool it looks starting to look good right we just need to have the email content on sorry the messages content up there so how do we actually get the messages right the messages come from this library from the the AI library that we installed just now right from AI react and we have a hook called use chat right and so we can actually destructure from use chat so use chat will basically allow us to interact with our our API and handles everything with the streaming handles everything with the input Etc I'll show you what it means but for this use chat I'm going to pass in an API to be/ API chat so later on we need to make this API npoint in order for this library to interact with the open API right and then we have a body when to pass in the account ID which we can destructure right cons account ID equals to us Str because we need to pass in to our our endpoint so that we know which orama database index to be looking for right because if we are on this account we don't want to be searching through emails on other accounts and then on error right error let just console do lock error for now cool and then finally finally initial messages initial messages going to be empt array and here we can then get our input our handle input change our handle submit and the messages array so this is where we're getting our messages we can now finally fill up the information so uh this is where the input lives and here for the form on submit we can do handle submit and for this input we can have the value and on change we have to handle input change and all this being controlled by this Ed chats okay so cool so one more thing I to add here is like this by default messages you got this nice UI here I'm going to just add this real quick so uh come down here sorry to ask AI so come down here inside this uh above this withd full so if messages. length is greater than zero I want to have a div with the class name of height four just a spacer diff right and then come down into the WID F and if I have messages of length of zero that means it's empty I'm going to display that UI okay I'm going to display a div with class name of flex with full okay and then uh sorry not classing of with f so this is margin bottom of four okay and then inside here with another uh let's see what's the issue here oh we have a missing diff here cool so then this inner diff with a classing of flex items that Center get a four okay and then in here we have a sparkles icon from Lucy react with a class name of size of six text Gray of 600 okay inside here we have a div what's in Auto completing that's weird okay anyways we'll have a thing with a P tag text Gray of 6 900 dark text Gray of 100 it says ask AI anything about your emails okay and with a P tag here it's actually the same thing but here uh no actually no it's different text gr of 500 text- XS text when it's dark I want have a text G of 400 I say gets answers about your emails to your questions about your emails cool and underneath this D we have a height of two that's a spacer and then finally we have a div with flex items the center right gap of two Flex of rep and then in here is where we'll put in our badges so the first one is spend and let's just do class name of padding X of two padding y of one BG gr of 800 text gr of 200 round round the medium text Xs and here I'll say what can I ask and then so now I should be able to see this okay and there's obviously a buck here so let's take a look at issue y okay so we have this div so one mistake is you see this div this from height to should be outside here okay now it looks right now it looks right okay cool um and then when I click on this span what do I want to do when I click on this span I'm going to do handle sorry handle input change right I'm going to pass in Target value to be what can I ask like this so when I click on this you can see that it populates it like this nicely cool okay obviously there likebox with the styling but let's fix it now so one more thing here is on this outer div here padding after padding of four we need add padding of four here so it looks more right cool right so let's see why this is expanding unnecessarily um input so let's come down to motion div here right so this motion div that's housing this enimy we need to give it a class name of pointer events none absolute z- 10 Flex height of N9 width of 250 pixels items that sensor right overflow hidden break dasw rounded full background gr of 200 right we have word- break colon break-word and when it's dark mode BG gr of 800 so hopefully this looks more right cool so you can now see you see you see this cute animation B okay but something is falling obviously we don't have the end point to actually handle it that's why it's breaking but now at least we got something working so I'm going to copy this down this B A few more times right and I can see when is my next flight and the last one is like when is my next meeting cool so now we got this nice bat UI that's the same here okay and for the placeholder let's change it here cool so now we got something more similar to our production app okay so now when I enter it when I enter information what it's going to do here you can see that it's going to try to hit an end point it's going to try to hit my/ API chat endpoint as we Define here right so let's actually create the endpoint say apss chat route. TS so let's go down to route. TS under the chat endpoint okay here and so this will correspond to/ API SL chat okay and so it is we need to import a few things really so import configuration from open AI that's it import a few things from the AI module we need the message we need to open AI stream sorry open AI stream streaming text response so don't worry about it being decator it still works it's just that I prefer this style okay okay and then let's actually export a a sync function called post taking a request okay we're going to do a try cat block okay in here so the first thing I'm going to do is I'm going to get out the user ID await off from clerk obviously if I know have no user ID I'm going to just say it's unauthorized if now I'm going to get back the uh kind of messages and account ID from request Json so this messages is something that the use chat hook will always give to us right and then what I'm going to do is I'm going to get the orama to be new or Rama client so I'm going to search for the relevant emails right I'm going to pass in the account ID okay and then I'm going to await orama do initialize cool and I'm going to get the last message so the last message is the message that I'm going to send in to this endpoint right so for now I'm just going to conso the lock last message I'm going to return a new response like this so hopefully I should be able to see this last me message being sent over so now if I enter like high test message and I press submit I should to see post ra my index you see post uh ignore this but the point is I get to see my my post request being sent through here okay and there's only wrong with the kind of indexing so let's fix that orama index doesn't exist let's look at the issue together so it's saying that unknown argument orama index so let's go down to orama to save index is saying that orama index doesn't exist I wonder why that is the case orama index okay I know why maybe you just need to restart our server okay so whenever you need to you add a new column to Prisma sometimes you need to restart the the nextg server to make this work okay so I'm going to refresh this the page and hopefully I I should say hi test I should be able to see my last message right I get to see my role and I get to see the content cool so now we verify that we are getting the last message Here and Now what I'm going to do is I'm going to try to get the contact to be await orama do search Vector search and the term is going to be a last message do content okay and then I'm going to just control. loock context. hits. length hits pH so this will show us how many relevant emails we found okay and then finally I'm going to have a prompt which I'm going to copy in here in here which you can copy from the repository but just long but it's just the point is that it's a assistant embedded in here your purpose is to help the user uh please keep in mind to be respectful blah blah blah and I'm basically just putting in the list of hits into the document into the context prom here okay so after with this prom I can then do cons response equals to await open AI chat sorry create openi dot uh where is openi so I need to do cons openi itals to new new configuration right open ey. chat create chat completions passing the model I'm going to do gbt 3.5 turbo okay let's see uh create open check completions model 3.5 D Tero cool messages so the messages is going to be the prompt that passed in and the list of messages right where the message message. row equals to user so this message is of type message right so basically what I'm saying is I'm going to pass in the whole context I'm going to pass in all my emails where I am the one who sent it so I don't to pass in the assistant email back into itself right I'm going to say stream equal to true so I'm going to stream back the response right and finally I'm going to say con stream equals to open AI stream with the response here okay and then I'm going to say on start actually no it's fine um yeah so the the power is that whenever we start a function when we start a stream I get to say stream stter and on on kind of completion I get to to lock it here as well like this so this just to kind of have a pass in a callback function here cool and with this we can return a new streaming text response with a stream right and so hopefully if this endpoint works I should be able to get back the response from B so I say hello let's see hey look it works so it starts the streaming back and let me show you which should able to see the console happening here so let me exit this out so I'm going to say uh tell me about Google emails so you see 70 hits found so say seven hits found then stream started and the stream completed right tell me go emails so it basically found all my emails related to Google and it I put in the context and responded so I can say tell me about Elliot's emails so I should be to see that nine hits found that means he found nine emails related to Elliot and then you can see that based on provide context OT has received security alerts blah blah blah yeah so now we can see that AI has context about entire email inbox and so I've already tried this on my other emails where I have kind of flight information and meeting information it works flawlessly is able to kind of extract out uh my information and respond to me accurately so I found this to be super helpful and so I I I want to push you to kind of try on your own email inbox to see if you also find it effective on your email inbox but cool so now we basically have built out the entire system we got our search functionality right we got the search functionality working we got our kind of uh AI Rec model working right so I think we're doing really really well and so I think we're done and the last part now is to kind of link up the stripe account so can we start we can start processing payments Etc and so I'm going to teach you everything there is to know about strip I'll see you in the next part cool we're officially basically done with the app like I'm super happy of what we have accomplished so far and now let's actually teach you how to add stripe into this so you can start charging users and make money out of this software as an as a a service application okay so here's the kind of high level architecture of what we want to do with our services so by default we want users to only be able to have so we have a free tier so you have a free tier you can only connect connect One account and you can only send 15 messages to the to the AI to 15 messages AI per day right and so but the thing is if you subscribed to the pro tier right you get to connect to up to three accounts connect three accounts and then you can get up to you have unlimited AI messages per day right so this is kind of the structure we're going for right so we're going to basically add kind of guard rails to help protect this right and so the first uh thing here is let's actually install the stripe API so Sudan install stripe okay so we're going to initialize strip here and we have this super simple way of doing it so let's come down to to our lip folder lip Okay stripe. TS and I'm going to import Stripe from stripe okay I'm going to export a con stripe object equals a new stripe okay pass in process. EnV do stripe Secrets key as string right and we have the API version to be W version you see in here so I think it's going to change for you guys but I'm doing 2024 June 20 June 20th okay and how do we actually get this stripe secret key so I want to come down to your stripe.com here and you're going to try to register for an account or sign in if you already have account so go to Dash Okay so once you have loged into your strap dashboard what we going to do is come down here to your and create a new account right you can name me anything you want name normal human- YouTube choose your country of operation all right and then you're going to basic wait for it to load and now you in test mode so let's go into developers and we should be able to see our API keys and so this is our our secret key so let's save this so what I'm going to do is I'm going to come down here I'm going to put stripe uh secret key so never expose this to to public I'll delete this afterwards okay and then come down to stripe publishable key should be this like this okay so once you have this you should now have your stripe secret key okay so leave the dashboard open because we're going to come back here for the web hooks okay so now that you have this stripe secret what we're going to do here is we're going to then uh start creating some stripe actions to actually trigger the the checkout portal okay um so the first thing here is okay let's come down the dashboard to the STP dashboard come down to your product catalog okay you going to add your product and here we're going to call it normal human Pro right or what you going to name it so here is like the description unlimited access to AI messages okay and you can just charge it whever you want I'm going to charge let's say 15 bucks I'm going to build it monthly right I'm going to press at produ so now that we have this we can now go in here and let's go into the price let's go into here so we're trying to get a price ID so this price ID is super important for us so make sure to note it down here okay come down to create a new file called stripe Das action. TS and put use server so we're going to export a bunch of server actions from this stpe action okay the first thing here is I'm going to export Asing function create check out session okay and so I'm going to get the user ID from o from next J from clerk I mean and if I do have user ID I know you're unauthorized right I'm going to show a new error saying that you're unauthorized and I'm going to create a session which is a wait stripe which is we initialize from our F just now trap. checkout. sessions. Crea we want the payment method sorry payments method types to be array with just card right and then line items we want to have the price right the price which is whatever you whatever you have the price ID there and then the quantity is one so one of this item the mode is subscription and then the success URL is let's define I think we have the next public URL right next public URL do we have that okay we do have it yes so you'll be process. env. nexu URL right and just come back to mail so after they successfully paid we want to redirect them back to the mail page right and the cancel URL let's just go back to mail as well cool and we need a client reference ID so later on how this work is okay let me just show you how this works oh okay like this Cal draw okay so here's how the strap API works so we have this is our app this application our next JS server we have the stripe uh stripe server here okay so what we're going to do here is whenever they click create checkout session we're basically basically hitting strip's endpoint and stripe will return us with a with a URL check out URL so this checkout URL here we're going to then redirect our users to the checkout URL so here this is the create um checkout session function that we're calling here so create checkout session will return us with a checkout URL and then we're going to redirect our user this is imagine this this John John will be redirected to Stripes page which is the checkout URL okay and then after John has finished right he will be redirected here so you see based on this success URL he'll be redirected to our slma URL but the thing is stripe is going to start sending us web hooks right and remember the concept of web hooks right so stripe okay let me just copy down here strip is going to start sending us web Hooks and where we can Define it we can Define our web hook to be like SL API stripe web hook cool like this and so it will send us back the web hook and it will send us different events so we'll be mainly listening to invoice. payment. succeeded which means that uh John has successfully paid and we'll listen for uh subscription. updated so this is whenever he updated update subscription so we're going to listen for these two events and then we're going to basically write a record into our datab pleas noting down that John has successfully paid us and we're going to therefore upgrade his account okay so this is the high level overview right so John creates his checkout sections SE sessions right and then he would deal with the the processing in stripe and then stripe is going to then send us uh event saying that John has completed his transaction okay so what we're going to do here so at the end of this we're going to basically redirect the user to session. URL as string so this redirect we can get it from next SL navigation cool and so we can call this create checkout session so let's actually do the UI for this so let us come back to Local Host let's have the stripe banner up here okay so let's have the stripe button uh St button here let's go back to our code let's create a new component in mail called strip button. TSX use client RFC so the STP button um will Lally just be a button component okay and then uh let's see let's see it has a variant of outline and then a size of large on click let's create checkout session so cons create checkout session equals to await create checkout session sorry uh handle click so we can import this from our stripe actions right so we get back we just basically get back uh it will just automatically redirect us there okay and so if we now come up to our come up to our ask AI here right let's actually create that Banner right you see that premium Banner let's create that premium Banner so let's create a new component called premium Das banner. TSX RFC premium Banner let's import it just right above here and let's have a height of four here to space it out let's save this so I should be able to now see a premium Banner here and this premium Banner is going to have just a few things right so let's just return this um so we're going to render different things whether they subscrib or not so we're going to have a e subscrib variable right by default it's going to be false right but this is thata we're going to pull from the database to check whether they're subscribe but for now let's just go to be false so uh yeah so if not subscribed I'm going to return a different UI so I'm going to return a div that a class name of background of 900 relative padding of four rounded large border overflow Das hidden Flex Flex call medium flex medium flex Das row right gap of four okay and in here I'm going to have a image right so this is going to be the image of the bot oh my God here this bot and I'm just download this bot I'm going to just inspect this okay and I'm just going to can I just open new tab cool and I'm just going to save the image in my downloads and then I'm going to just copy this into my public directory let's name it let's rename it to be bot. webp and here we have the source source equals to/ bot. webp okay and then this image will have a class name of medium will be absolute when is medium screen we have a negative bottom of six in here when is medium with have a negative R of 10 height of 180 pixels and then width of Auto so we should be able to see this B happening here but we do have any text to fill up the height so let's put some text here so we a div here and with have a hit one that says um sorry inside this D we have a um do Flex item SL Center G of two well in here we have a H1 with a class name of text white text XL fonts both that's says upgr that says uh basic plan right and we have a paragraph that says text grade of 400 text- smm right when it's medium I a Max width of I need to calculate C 100% wait so actually no that is just that is just full Max with full yeah and then what we're going to do here what we're going to do here is we're going to put the remaining credits right so imagine we have a remaining con remaining credits of five right and let's say we have let's actually create another for file in Source called constants TTS so this constants dots will export a few things so export cons free credits per day equals to 15 so 15 messages a day and then export con free accounts per user equals to one and finally export cons Pro accounts per user equals to three so this is what we describe right if you're a free user you only get to connect One account and you only get 15 messages right so we're going to have this variables we're going to use throughout so let's come down here we're going to have we're going to display the uh we're going to show that you have remaining credits out of the free credits per day okay cool messages remaining so let's save this and let's see how it looks like okay cool something is starting to happen right like this okay so now let's actually add more styling um let's see let's see let's see let's see and then in here under this paragraph let's have a spacing of four and have a paragraph tag of class name of text Gray 400 text justs medium on to Max width of I need to dra a calculation of 100% minus 150 pixels inside I can see upgrade to Pro to ask as many questions as you want cool and let's see why is it why am I in this uh sorry yeah this should be outside of this div okay now it makes more sense and then let's see here um yeah after this paragraph tag we have a height of four and this is where we put our stripe button okay and this is our stripe button basically cool so let's go into our strip button and let's actually customize this okay strip button let's give it a text of of so if you subscribe where variable cons is subscribed by default it's false but if it's subscribed we're going to allow them to manage the subscription if not we're going to ask them to upgrade so now if I press this button I should be redirected to the stripe checkout page and I do and I get to see my super human normal human Pro to be $15 and I can obviously pay okay and so after I finish paying what strap is going to to send me some web hooks telling me that I have successfully paid so I need to be able to handle that uh um I need to handle this web hook right so before that let's actually go into our schema because we need to keep track of who paid and keep track of their payments right of their subscription so let's actually have a model model called stripe subscription right we have the ID okay where is linked to a user right let's have a Creator ad is linked to a user and we will have a customer ID so this is a stripe customer ID we have a subscription ID we have a price ID and we have a current period end right so this tells us when the subscription ends so that we can check based on this to see if they still subscribe or not and we have just updated ad here so you can see a user right now should be just linked to one so let's just do subscribe subscription so that is singular and to do that right we need to go in here and we need to make this unique like this and save it so now there are one to one relation so each user will only have at most one subscription one stripe subscription in the table so let's do Prisma DB push to actually push this new column this new table up cool so now Al let's also restart our database to make sure that our changes Tak Okay cool so now what I want to do what want you to do is um let's see um come down to our stripe and let us go to developers let's go to web hooks okay and now let's actually test in a local environment okay so the first thing is you got to download the command line interface right so if you're on if you're in different OS you can try different sty but I already have it installed so I just need to run strip login right so let's open my terminal here I'm going run strip login so I need to just open this URL to confirm okay so here is where we decide where we want to do the web right so remember in the drawing if I can find the drawing let's see yeah so it's saying that we can we can Define the web here so this is where we going to Define it so in this this case I'm going to do I want to put it at/ API strip web hook okay so it's going to listen for my web hook here and on to note down this web hook signing secret so I'm come to my EnV come down here I'm going to have strap web hook secret I'm going to paste this in cool so now if I go back to my stripe dashboard it should be listening stuff and here is where I can trigger events right so in this case I'm going to set up a new endpoint so go under your app SL API so create a stripe SL web hook SL roots. TS so this is where we have export uh Asing post function post right and then basically this web PO is where we receive stripe stuff okay and so to do this we have to actually make sure that the the web pool is coming from strap and not from anyone else so we need to verify the stripe web hook signature so the first thing we need to do is we'll do uh request we we'll get the body to be await request. text and we going to get the signature which is request do sorry we going to get the haers from nextjs to get stripe signature right and this is going to be a string and we have a event to be stripe do event okay and then we're going to try catch right so we're going to try to get the event to be stripe. web hooks stripe which import from our own Library the web hooks do construct events right passing in the body passing in the signature and we also need to pass in the process. EnV stripw hook secret that we have here right so this stripw hook secret comes from the C in our testing environment cool and so if there is no event or there's an error we're going to return a new respon saying that there whitebook error because probably they F the signature okay and then we're going to we're going to conso we're going to construct the session to be event. dat object as stripe. checkout. session okay and we can console. loock uh receive stripe event right and we can get the session do events sorry we are able to get the event. type and let's return a new response saying 200 and we can actually try this out if you look into our console hopefully oh one more thing is we need to make sure that the strap web is a public route right so after this if you come back here and I can just manually trigger a let's say I can trigger invoice. payment succeeder if I press I can just trigger this copy this right and I'm going to write track trigger I should be able to see you can see you see this 200 100 request I can say received stripe event invoice. finalize invoice. item. created so I can start receiving this events that strip is going to send me right you can see event payment method attach invoice Creator blah blah blah so this is the power of strip right it will send me events based on the life cycle of the users payments okay so we're going to just look out for a few events right the main three events right so we're going to just look at the main events right if event. type equals to checkout. session. completed that means they have complet a session right we'll do something right else else let's see else if event. type equals to invoice. payment succeeded right we also do something else um let's see and then and then the last thing is wait and the last thing we're checking for is if event. type equals to customer do subscription. updated right we're also going to listen to the event so let's just start with the first event which is check out session completed okay the first thing is we're going to get the subscription equals await stripe do subscription do retrieve they retrieve the subsection the subscription s string okay and we're going to expand Out items. dat. price. product okay and then we're going to check if not session do client reference ID so remember we passed in this client reference ID when we did the strap action we're passing the user ID here so now we can finally see which user ID we are actually receiving the event for okay so if I have no ST session client reference ID that means I don't know who is actually subscribed here I'm just going to return a new response saying that I don't know who it is okay if no I'm going to find the plan which is the subscription do items uh. data index zero right do price here okay and then I'm going to say if there's no plan I'm going to return new response again right if not I'm going to get the product ID equals to plan. product as strip. product I'm going to get the ID of it and then if there's no product ID obviously something filled again and then finally finally we know that he has succeeded so we're going to just await DB do stripe subscription so we're going to then write create a r in our database do create right the first thing we know that is the data is the user ID is the session the client reference ID we know the product ID to be this we can the price ID to be plan. ID so let's see ID product do I not have product ID well I actually don't need it anyways so the price ID with have the customer ID to be the subscription. customer we got the current period n which is new date and we get the subscription. current period and multiply by 1,000 right and then let me see do we need anything else we need the subscription ID to be subscription. ID I think with that we have everything we need cool so now this should create a subscription with us and let's go to the next event so if invoice payment succeeded it'll be super similar right so I'm just going to copy in the code because it's basically the same function right oh after this I need to return a new responsing that received right so if payment succeeded I'm just going to retrieve the subscription exactly what I did above here I'm going I'm getting the plan I'm getting the product ID and I'm going to update the strip subscription where the ID is here and to do this you can see it's got to true and error right because I didn't say that the subscription ID is unique which it should be so I should just do this so Prisma DB push so what I'm saying is that the the subscription ID in the table should be unique and that's why if I do this now the type error will go away because I can then uniquely identify the subscription ID I can just delete the product idid right and then I can can return a new response saying that I've received it and last but not least is the customer subscription updated so if it updated I'm literally going to do the same thing which is just get the subscription right St the subscription to retrieve I'm going to update the session ID uh sorry upgrate the subscription ID with the current period end so this current period end is basically saying where the when the subscription is ending so every time they update or the subscription updates they pay new round the current period end is going to be pushed back by one month so imagine today is September right the the current period end will probably be sometime in October and then when they reach October and they their sub renews then this end point is going to be hit again and now the current period m is going to be November so they're always going to be one month ahead and that's how we're going to check whether their subscription is active so now that we are we are able to receive this web hooks right let us then come down here okay so hopefully what's going to happen is once I press this button to upgrade my plan I'm going to go to my checkout session page right so this exactly the diagram that I'm drawing here so I'm going to go to my session checkout page and after entering the test information 424242 here right and press subscribe it's going to lead me back to a mail page but hopefully my browser as should be able to see here you can see boom do you see this okay there's some errors stripee not found but but the thing is I can see my charge exceeder customer Creator subscription Creator blah blah blah right and so with this if I come down my database right my my Prisma studio right I should be able to see a new subscription being created and I see it so subsp subscription I can see it creat for this user right which is me obviously I can see my customer ID my subscription ID and the price ID okay and I should be able also able to see when it currently ends so you can see that it's ending basically 1 month from now which is October 26 now is September 26 right so the subscription ends in one month and that's what we're going to be checking for to see whether they currently actively subscribed okay and so with this information with this information let's deal with this error with saying that uh because inde dependent a mod that was required but not found record to update was not found so it's okay because what I'm going to do is let's go down to the stripe uh web hook API so over here we're updating right so before that we're going to first check uh first check it so got existing existing subscription right so if there's no existing subscription it's okay we can just say it's fine subscription not found we just return to 100 it's going be okay so with that we can now have a subscription so now we're going to basically allow them to check right because now it's still saying upgrade plan even though I already upgraded my plan so how do I actually check whether I'm subscribed so I'm going to come down to my stripe action here I'm going to export an action to check whether I am subscribed right so I'm going to export a asnc function get subscription status right I'm going to get the user ID from o if I'm not if I'm not authenticated obviously I'm not going to be subscribed I'm going to return false false and I'm going to find the subscription so I'm going to await DB do strap subscription I'm going to find where my user id here is so I'm going to find the corresponding subscription Row in my database and if I have no subscri subscription obviously I am not subscribed but if I am I'm going to check whether the subscription current period end is greater than the new date right so it's saying that uh is the is the current period n bigger than when I am now because if it is that means that the ending is in the future which means they are still subscribed but if this ever becomes false that means they stop came the subscription that means the current period end is no longer in the in the future right that means they're not currently subscribed anymore cool so with this get subscription status I can come back to my premium Banner so you can see where we are seeing subscribe is false right let's make it a uh hook right let's delete this and then whenever this component first mounts what I'm going to do is I'm going to just get the subscription status to be uh let's do this async I'm going to invoke this so I'm going to call subscription St equals to await get subscription status so I'm going to invoke This Server action so this going to return me a Boolean and then I can set the subscribe to be the state of that so then I can check if it's not subscribed I return this and then I can check that if it subscribed I can return it subscribed and so now you can see I am successfully subscrib so that's amazing so now let's actually write the the UI for whether is subscribed so let's see so if it subscribed it's going to be basically the same UI right so I'm going to just copy this down right the only difference is going to say like this is the premium plan right and then here I can say ask as many questions as you want exclamation mod cool and then uh what I can do here is I just delete here because this it should be down here and this can be just gone actually no let's go back here let's just delete this let's see how it looks like on my production so we can see ask as many questions as you want this belongs down here so just remove this ask as many questions oh cool but let's just give it a difference so instead of 150 pixels let's just 70 pixels okay yeah that looks more like it so now this is me being subscribed and obviously if I'm subscribed I don't want them to upgrade a plan what I want them to do is to come down here and I want them to create their I want to lead them to the uh building portal where they can manage our subscriptions right so I'm going to just export another another function where we says create building portal session right here I'm going to get the user ID con user ID equal to off obviously if I'm not no user ID I'm going to return unauthorized but if not I'm going to say I'm going to check do I do they have a subscription right so if they do have subscription say we say it's not found because if no subscription what are you going to try to manage right so then we're going to create a building portal session create right customer passing in the customer ID right and then we'll return them to/ URL to SL mail and then we'll just redirect them to the session. URL okay and so inside the strap button here right I can do the same thing for the premium Banner so let's just copy this right get subscription status so if it if that subscribe I'm going to manage subscription if not right so I'm going to say if it subscribed I'm going to create billing P section but then if they're not subscribed I'm going to create checkout session just like what they did just now so now you can see instead of asking me to subscribe it's asking me to manage my subscription and if I press this button it's something is going to error out but it's a easy fix but it's expected so it's going to error out it's saying that if you're in a test yeah in a test environment you can't create a portal session until you uh save your cut dashboard setting so I'm going to go in here I'm going to press activate test link okay and with that if I go back here again and test it again I should be then able to be brought to the building portal where I can then cancel my subscription Etc cool so now that we have the premium CL and the premium CL settled right let's actually just delete it first so let me just delete the row because I want to show you how we're going to deal with uh the situation where they're not subscribed so if I refresh again now it's going to say upgrade plan okay so how do we actually manage the five out 15 messages right the first thing is let's actually handle the situation where they have no account right and they're trying to add more than one account okay so let's go back so down into our code right account let's see account searching um um let me see let me see let's go into oreno right so oreno dots is where we are help helping them create a new account right so the thing is now we're going to ver check right we're going to get this is subscrib so cons is subscribed it goes to await get subscription status right so if so we're going to find the list of all the accounts it have so if they're trying to get more than one account and they're not subscribed we're going to just show an error so if a wait get uh sorry await CDX DB sorry db. account do count where user ID equals to user ID uh let's see like this right and then I'm going to say if is subscribed right and if the accounts is bigger or equals to the pro sorry if it's bigger than Pro accounts per user that means there are more than three right I'm going to through a new error saying that uh you have reached the maximum limit accounts right like this uh sorry you have reached the you have reached the maximum limits right else so if they're not subscrib if the account is greater than the fre accounts per user we're going to throw an error as well okay like this and so now you can see I'm a free account I'm a free user but I already have one account right so if I press add account is a true and error right saying that you have reached the maximum number of accounts okay and I'm going to handle this error as well right so if you come down to account switcher all right we can see that we try to add the account here so I'm going to put a try catch block try catch block so if there's an error I'm going to check if error. message equals to you have reached the maximum number I'm going to just do toast. error actually you know I can do I can just toast the I can just toast a message right error. message so let's try this if I press this you can see you have reached the maximum number right I'm going to move my face out oh not like that you can see you have reached the maximum number of accounts for your subscription okay and if I upgrade my plan obviously I can then subscribe to it because I am within the account limits okay so now that is handle right we have block off the account switching so now let's actually go into the messages right every time they kind of interact with this chatboard I want to basically minus uh their credits away right you can see five out of 15 messages but this five is obviously fake data so to keep track of that the last thing I need to do is come down to my schema I'm going to create a new uh I'm going to create a new model uh let's see uh chatbot interaction okay I'm going to create a new table called chatbot interaction to keep track of how many times they have message the chatboard so obviously we have ID we're going to keep track of which day they message the chatboard and how many uh messages they have already sent so by default there's only one whenever it gets created there's only one and then we have linked to the user ID so it's link to a user okay and we want to make the unique index the day p with the user ID and let's T it as well like this like this and let's go to the user let's make the user a unique view here so that we can have a oneto one mapping of the chatboard interaction like this cool so now let's P pris TB push this up okay so now what this means is okay let's restart server as well so now each user is linked to a chatboard interaction and now this chatboard interaction keeps track of the day in which we send a message as well as how many messages we have sent that day okay and with that we can come down to our chat route so remember this is the route that handles whenever you press you try to interact with this chat Bo right so the first thing I'm going to do here is I'm going to check whether they subscribe cons is subscrib it goes to await um get subscription status right and then I'm going to check if it's not subscribed what am I going to do I'm going to find the chat boo interaction ital a db. chbo interaction. find unique okay by the way we should we should Define what today is Right cons today equals to a new date dot to date string right and then we can find the unique where the day is today right and the user ID is the user ID here cool and so if there is no chat interaction let's actually create one for them so let's create a CH interaction for them where is today's day there's one user and there's exactly one message because when they hit this Endo that means they message once right so they have at least one chatboard so and then what we're going to do is um let's see let's see so if they are if they are not subscribed we're going to check this and then uh we're going to else if that means they have the chatbot interaction right else if chatbot interaction. count that means they have message more than the free credits per day right we're going to then return a new response saying that you have reached a free limit today and we have the the status of 4 to 9 which means rate limit right so if they are obviously subscribed right this won't run right and obviously if you are subscribed you can have unlimited access we won't check for anything right so with this we have finished checking like this and what I'm going to do is I'm going to come down here and I'm going to try to send a message I'm going to say test right it's going to reply and if I come back down to my Prisma studio and see do I see my chatbot of course not C STP sorry Prisma is just so annoying okay so I should be able to see my uh chatboard interaction I should see one row right I should see today's day the count which means I interacted once and the user ID and if I try again so I say test again right oh I need to inter increment it again so you can to see that obviously it's not going to increment it right so to increment it I'm going to come down to my um on completion so on completion is after it has finished streaming right so which is perfect because then we can hear we can get we can just await dp. chbo interaction. updates where today's day and the user ID and can just increment this by one so now let's try this again if I say test again right and it finish sub finish generating I should now see my count increase to two right so I have two messages cool and with this we can now get like their rate limit uh rate limiting so what I'm going to do is let me see come down here uh let's see let's see so I'm going to come down to my router my account. ts router I'm going to get the kind of chatboard interaction data so get chatboard interaction right so it's a private procedure I'm trying to get the account ID I'm going to authorize them I'm going to get today's dat right I'm going to find where today's dat is and the user ID and lastly I'm going to get the con's remaining credits equals to the free credits per day minus the chatboard count or zero so imagine like the free credits is always going to be 15 right and let's say I have interacted it with it for five times so my remaining credits is going to be 10 right so I'm going to return this remaining credits here remaining credits okay and then in the front end here to get this premium Banner let's go back to the premium Banner to get the data of the remaining to get the data we can do API do do account do get chatboard interaction. use Query passing in the account ID which we can get from use threats right and then we can finally here come down here and remove the heart coding so instead of here the remaining credit we have data do remaining credits left so now I can see if I refresh okay cool you can see I have 13 or 15 messages remaining right so what I'm going to do is I'm just going to once they ask it so C I can remove this hard coding and so if I come down to the chat bot uh chat wait sorry the the what's this ask AI interface right I'm going to come down to the message right here use chats blah blah blah so on finish right I'm going to try to refret the information because after you finish it I want to update to I want to refresh it to see that the number went down so I have a utils equals to API do use utils right so this will give me the ability to do u. account. getet chatboard interaction. refetch so this will fetch the data and hopefully I should see updated so let's try this so if I say hello so hopefully if everything goes right I should be able to see this becomes 12 so see hello and now you can see boom 12 remaining so it's working perfectly and let's actually try it out so let's go to my constants and if I say the free credits is four okay four wait yeah three um let's refresh this so you can see I have zero to zero out of three messages remaining if I try to send a message I'm going to get an error I'm going to get an error saying is 429 right so if I see that console let's try again test you can see error you have reached a free limit for today so I can actually listen for that so I say if error dot um cost dot do I have cost actually no let's see you have the free limit so I'm going to check uh I'm just going to toast do error error. message like this cool so now if I if I exceed the limit as you see hey you have reached a threee limit for today cool cuz I have Z of three remaining so this constant is like super convenient because you can just literally um you can just uh set the limit however you want and people will just kind of uh use the free credits remaining oh my God okay cool we have completed stripe integration and the payment gating Gateway and we can finally deploy this we are almost at the end all right let's do it okay now let's deploy this so it's actually not too much of trouble because we have already deployed this before right like initially when we did the clerk so that's actually really good because now all we have to do is come down to our settings and just fill up all our kind of and environment variables and then it should work hopefully let's pray so I'm going to come down to our EnV okay I'm just going to copy everything here okay let's let's check what is actually missing so we got database URL click okay so we only have the database URL and the click right so we can just copy everything here right we'll copy in here okay and then we should be able to save this and and it should work okay so we have successfully saved it right and now I'm sure there's going to be some other errors but if you just Commit This and push this up it should deploy and everything should now work fine and dandy uh let me think yeah I think that is actually all that it is right okay let's just pray and let's just try out if it works okay so let's wait for it to deploy so you can see has it's it's building now and it we should be back okay so it has deployed so now if you go down to normal human- yt. for.app we you see something happening okay so now let's talk into our uh account and let's pray that things are working and not breaking okay let's let's like pray that it that works okay so we're still on the landing page with there's nothing but if you go to SL mail right we should hopefully see some stuff cool okay so let's select account right let's wait for for it to load I know why it's loading so slow 100% I know why because you need to go into our account go to your settings go to your functions and you need to select a region that closest your database in which case I'm in Singapore so I need to save this and let's just put a function Max duration of 300 and save this as well and let's go back and redeploy this sorry and let's redeploy okay so let me tell you what I just did there so uh here the functions right so the reason why the functions is this is the region on which the serous functions will execute in by default so this is basically where your app is being served out from default and so it should be close to your database because let me show you why so imagine this is the world okay my geography is bad but deal with me okay so I know Singapore is somewhere here okay and I know the US is somewhere here okay so my database right on neon DB is situated in Singapore right but the thing is just now my versel was situated in us east1 so us east1 is somewhere here okay so every time someone makes a request the datab the the the server will have to make a request to my database here fetch the data and then finally return the response and you see this whole big round trip this is what was making the app super slow and so all I had to do was I had come down here I changed the function execution region to Singapore so now now my server is near my database and so now you can see my database and my server will communicate basically almost instantly so that's why it will make the app faster and for this function Max duration cuz I just want to make sure that my servers are not my my functions are not timing out right so um yeah this should be good cool cool cool cool so now let's just wait for it to redeploy deployment okay it's ready now let's check it out again sorry not normal human it should be this right ver. app let's come down here let's go to/ mail okay I should select an account that belongs to me so let's see oh you know what you know why there's no account you know why there's no accounts I know why because I was not loog in with the email I was loog into to the other email so I'm going to sign out actually no this is a perfect way of of of displaying yeah it's fine so I'm going to log into uh my Google account this right sorry sorry Google account okay so I'm going to log into this and you can see that I have no basically no accounts link here okay and so what what I'm going to do is I'm going to test out the the email linking stuff so I'm I'm going to press add account and I should be able to be here I'm going to link this account so it's saying it hasn't verified my app but it's okay continue and so hopefully if I go back into my deployment I see my logs and I see um my live data hopefully I should still I should start seeing like email popping in right let's see normal human let's go down to deployments oh I know why because we don't have next public oh we do oh yeah but you see my next public I set to look go 3,000 okay that's silly of me so this should be really htps uh normal human- yt. verel app let's save this and finally if we redeploy this this should work okay so now it has deployed right so let's try again so I'm going to just add an account right let's try again let's add this account cool and hopefully it will redirect us to normal human-y youtube.app SL mail okay it does it does that's good and I can see I have an account linked and if I go into my lock box I should start seeing emails kind of start streaming in one by one right where I'm writing to the database right so let's see okay see cool we got it attempting to snc 23 emails and we can see that it's starting to upsert emails one by one right cool so let's do one by one let's wait for it and then in no time in no time we're going to see yeah see our emails are starting to come in that means the syncing works right we can see the the emails are working we can see that the the kind of access grun was also showed up in this email right I can also basically say um hello and hopefully you respond to me yay so I opening airworks as well so everything is perfect oh my God first try deploy actually not first try but yeah that's cool let's see if this works reply and yay awesome awesome awesome stuff so we can see our email are able to be sync properly one by one that's cool that's cool that's cool and everything seems to be working perfectly nice nice nice awesome um let's see that is super cool I'm going to say when is my next flight it's going to say that I don't have a flight cool I'm down to 12 tokens now okay that's amazing guys so I think this basically concludes the video okay awesome guys so I am so proud of you to have finished this video with me I have no idea how long it is I've been filming for 2 days I'm guessing it's probably like at least 6 7 hours right but you have basically built out an entire kind of email clone with Shen xgs you have learned t uh the TR T3 stack trpc you have learned a completely new API oringo you have learned how to do proper database management with web hooks you have done full Tech search with the open source library right you have learned how to do Rec you have basically done co-pilot you have learned how to do strip and you have done learned how to deploy this to a production app on ver sale so I'm super proud of you guys for completing this with me and so I hope you have enjoyed the tutorial and if you actually learned something I hope you can give it a thumbs up and subscribe and share this video with anyone you think that will benefit from this video and as always I'm Elliot and peace out\n"