I Automated 1150+ Personalized Cold Emails With AI (N8N Live Build)

Michael Elliott builds a cold email automation system using N8N, Apify, and OpenAI that scrapes LinkedIn data and generates personalized icebreakers. He walks through the entire workflow to send 1150+ customized emails at scale.
I'm excited for this one because today I'm going to be building out a deep personalization cold outreach ice breaking system inside of Nan that allows you to extremely deeply personalize the Icebreaker inside of the cold email flows that you're building. Let's jump into the demo. So this is the flow that we're going to be using today to generate cold icebreaker opens openers for your emails. Now this has a 6.0, 8.5 to an 8.5 open rate. For me personally using the LinkedIn profile scraping technique, I prefer it to most of the others. I have had success with website scraping and other individual techniques Twitter, but I prefer LinkedIn. I find the highest open rates and the best quality leads come from the LinkedIn scraping. So I'll show you the demo very quickly. The tools that you'd use to get this information. Apollo's the goat in terms of lead quality, but everybody's using apify scrapers. I have a full video on this so you don't need to, to worry. But if you never, never seen apify on Apollo before, don't worry because I'm going to link the, the, the mock data that I've got below in the description so that you can use it during the live build section of this video. This is a fantastic way to get leads for cheap Apify actually offer you $5 in free credits so you could keep creating accounts if you wanted to. It would mean you have to update the API keys, but don't scare yourself with that for now. What you want from a perspective of lead generation though is a high quality and high amount of leads. Let's get rid of these for now. I've got a completely separate video on this. These are the outputs that the, that the cold Icebreaker has just generated, but I'm going to delete them. Outside of this icebreaking column which I have added, this is the data that you will receive when you do scrape Apollo with apify. Depending on what obviously criteria you select to pass through as everything from company website information, as obviously as their LinkedIn URL. Otherwise we won't be able to scrape it first name, city, country, all of those things and what you're looking for is interests, mba, all of these things, whatever it is that it can be to personalize it, that it comes up with is, is, is, is not, is not off the table. You know, it's on the table ready to go. So the test data that I've got, let me just close a couple of these. I've hit Execute workflow. And now what we're going to do is we're going to paste in this, this URL and I'm going to put the email address in and I'm going to submit now it's been submitted, this is now going to work. And this is the, is the flow. It's based on a form submission. So you can submit the form at any point to get whatever leads you want as long as you've exported them from apify. You have a get row. So it's getting the information from the Google sheet I've just shown you it has a filter, which is important because that filter actually filters out any, any individual lead or that's been scraped that doesn't have a LinkedIn URL included, which is quite rare. But sometimes they, sometimes they don't. And this looping system loops over the items controlling one by one to ensure that. Well, there's numerous reasons why you keep the batch size quite low, but you don't want to hit the rate limit of anything. You don't want to hit the rate limit specifically of Google Sheets because as a rate limit if you try and pass it too much information at once, it will break the flow. You want to just softly, softly go through. If you have large amounts of data, you might want to add weight node here to allow it to just to be able to recalibrate. But, but the outcome from a perspective of an outcome from the perspective of what we've done at the end there is that it's aggregated the data and it sent a message to Slack deep personalization. At 1206 it opened it in another browser, but there you go, you can see they're both open. It's exactly the same one that I've just got and it's literally sent straight to Slack. So you can see when all of your leads are done and you could go further in the flow and you could extend it to go into instantly. But for now let's stick to this. I'm gonna go into the live build, show you exactly how you can have this with the average open rate of six and a half to eight and a half percent. So let's go. So now we're ready to build. Let's jump inside of the the NAN account, go to hit the plus icon workflow, create impersonal. I have a thing about renaming. So YouTube test icebreaker video. Okay, now we're gonna hit the plus first step and obviously we want a trigger so we want the on form submission as we just discussed, no authentication required. The form title Icebreaker generator and then in the form description. If you were gonna sell this to a client, you'd want to say, please ensure the sheet provided below includes a column titled LinkedInURL, because that's what's included in exports from Apify. You don't have to type that in if you don't want to. Now we need to add form elements. So the form elements, basically just the fields that you've got in inside of the form, the field. So the two that we've got is Google Sheet, Google Sheets URL and the placeholder for the text. Paste your sheet, that'll be absolutely fine. Make it a required field. Add a second form element. Even though we didn't use this in the last flow, probably won't use it here either. But just as an example, because if you're going to sell it, you want to change the node from previously to a Slack, from a Slack message to a. To an email. Very simple. But that's how you can sell it to a client. From an Icebreaker perspective, along with my other video on the flow. So we have as a placeholder here, email recipient of Icebreakers. Make it a required field exit out of there, click command S. Realign it. Now what we're going to do is we're going to execute the workflow and you'll see the form is different to the one that, that we had previously because of its design. I'll show you how we, how we customize that later. Maybe it's not really important, but what we want to do is we want to go grab the sheet URL, paste it in there and the email, you can see, look that what we've just filled in from the form in terms of the placeholder is there the email. Let's just use my email and submit it. The reason why we do this is the data is now here. If we click into it, you can see the. The output. Now when you look at the output, it gives us everything that we need. If you look at the schema, you can see the Google Sheets URL which will be used shortly. But we can see that it's picked up the URL, the email when it was submitted and the. And the form, because it's the test. You are test URL we're using, not production. So we come out of there, we click the node and we click P and that pins it. And that means the data in there now stays. So I never have to submit another form now to get this data and flow working. And it's a Habit you should get into when, when building. Now we need to add a Google Sheets node. So Google Sheets, when it comes down to Google Sheets, we want to get. Get rows in a sheet. If you haven't got your credentials connected, literally just click Create new Credentials and hit Sign in with Google and it will be connected. So you'll have no issues in regards to that. Now the operation that we want is obviously. We obviously want it to get rows. That is what we're trying to do. But we don't want to get the rows from a list. We want to get it by a URL. And the reason why we want to get it by a URL is because we want to hit the Schema tab if it's not already open for you, and we want to select the Google Sheets URL there. Because we want it to be a expression. We want it to be an expression so that every time we change the sheet that it comes up with. With. With that. Now inside of here, select sheet one. If we come out, we exit. And if I execute this node, execute this step, you'll see that the output that it's got is. It's pulled through the individuals that are inside of the sheet. Kirk, it's got Damian Walker. What a name. Love it. I do love it still. Again, I'm going to pin that information and you can see that it's about out five items. I'm only using five items for example purposes. You can obviously use as many as you want, but it's. I just. There's no point in us waiting for the loop to go through for. For too long. Now from there, we need a filter. And this filter is going to again use information from the previous node. So we've pinned it, which is fine, you won't necessarily have to pin it. But if you. This is important to pin data as you build. It's. So it's critical just get into the habit of pinning data because you can always unpin and run. There's nothing, there's no negatives that come from it. So we want to add a new node and we want to search for a filter. This filter simply wants to be that the string exists. And what we're looking for is the LinkedIn URL. And the reason why we want it to be the LinkedIn URL is because later on this is now filtering. You have 50,000 people in a sheet, let's say 100 of them don't have the URL. Well, we don't want our flow to break, do we? So this is filter Stops anybody, any data item from the get rows module before, from the Get Rows node before, from actually coming through and being in a position where, where it gets through to a stage where it's trying to scrape and it fails. So that's why we do this. All we want to make sure is that the, all this does is that the URL, the LinkedIn URL exists. Happy days. So now obviously the next step of the flow is to create the loop that, that we saw. So we just loop over items and split into batches. This batch size is something that you can control. You can do it in batches of 10, 2, 1, 150, a thousand, whatever you want. But the reason why loop over items is a common thing that's used inside of used inside of NAN is because of the fact that you're in a position where you want to be able to control what the output is to each individual tool that you fix to it. So in this case we're using Google Sheets. Well, the rate that you can send data to Google Sheets can cause issues with high volumes. So you want your batch size small, but you then might also want a weight node to make sure that you continue. Just five second wait, ten second wait. You don't need all the icebreakers immediately. You know, obviously you want to allow it time to run and do its job properly, but that's what the batch size is. If you want to run in batches of 10, two, whatever, that's, that's where you would set it. Now we have this lovely loop, sort of overflow of a, of a loop. Let's see what that looks like. Yeah, we have this loop of an overflow. This is the done section. Okay. I'm going to do it in order because we don't want to do that, but that is quite a simple solution to do. Now this needs to be replaced. So we just. If you literally, if you just. Sorry. If you just click it and click the backspace, it will delete it and it'll give you this loop. Now inside of here, the first thing that we want to do is we want to make a HTTP request. Now this wants to be a post request and this I'm going to paste in. I've got this already to hand, but I, I'm going to paste this in for ease of use. But if you all this is, if you go to apify API documentation, I will put this in the description to stop you having to go and find it in case you can't. But if you go to apify documentation or Even in most each individual actor inside of apify does have an API section inside of it. But this all this is is effectively saying that the Apify acts everything after that Axe column here. API maestro, LinkedIn profile detail, run sync, get data set items, all of that. You just. You just replace the replace the the actor name. Every actor has a name and you can replace it. It doesn't matter for this video. Just trying to give you some golden information on on how you would do it for for other for other steps. Now obviously we want to send headers so we we. Well, we want to send headers now the headers that we want to to send. The first one is accept and we want it to accept application JSON so it accepts JSON and then we want to add another parameter and this one is authorization. Now for apify, when you're using apify you don't have to go through official authentication. You can just literally add inside of here Bearer and then your API apify token. So I'm going to copy mine in this will be blurred on your screen, but then I'm going to take you to apify and show you how you would get it. Just make sure that's right. That's what. So my my bearer token is now is now there. Let me show you how you would find that if you go into. If you go into apify because you might have just created this for the same for the same time. If you already know how to do this, bear with me. Doesn't take long as you already know. Settings in the bottom left hand corner API and integrations hit Create a new token, name it so Icebreaker API Token whatever and then click Create and it will literally pop up with your that you can copy literally pop up with your API key. And the only thing that you need to do inside of NAN is put Bearer in front of it if it doesn't have it already. And I just haven't created a new key here. Now then we want to send. We want to send the the. The body right from a body perspective. But we want to do that using JSON and again I've got this here which is the, which is this but to paste in for speed of video usage. But this is just that the username key is the is the loop over items item JSON.LinkedIn URL that is literally all it's doing is pulling the the loop over items item JSON.LinkedIn URL from the. That's ugly from the filtered version into here instead of pulling it from here where it's unfiltered, filtered and we don't know if it exists. So it's just pulling, it's literally just, just pulling that. And the final thing that we want to do at the bottom of this is literally just add, add an option and add redirects and just literally leave it there. Don't worry about anything else. This will work for you in this, in this, in this manner. Now we're nearly done effectively, but what we need to do now is we need to add a Google Sheets node and that Google Sheets node wants to update a row in a sheet. And when we update a row in a sheet, we obviously, because now we obviously want our data list from apify to have an icebreaker. So if you are to use this moving forward for yourself, I'm going to give you this document inside of the description so you, so you can use it. But if you are in a position where you, where you have like this is not here, for example, right? And this is how your data looks. Obviously you want, you want a column that, where you can put the icebreaker. So insert one column to the right. I like to do it like after the name and the, and the. Sorry, the name and the email and so the name and the LinkedIn and stuff, literally call it icebreaker. Your job's done. So we come back into here, we update a row. Now when we update the row, we have not only got to give it the new icebreaker, as you can imagine. Let me just go through some steps. Let me execute this step, execute this step for that reason, please fix them first. There was a problem, right? Okay, let's delete this. Let's execute. Let's execute this step to pull it through. Now if I execute this step, I think that should be enough for what I was going to do, but I'm going to execute the step anyway because it only has one item that's now scraping and it will. If we look at the, if we look at like obviously the, the. If we look at the sort of information that it's scraped from the user. We have all of the individual data that you could possibly need is about us section, the city that he's in. We have the company, you know, the current company that is at a skills URL, his skills in an array that have software project management and design, etc. And I'll show you how we're going to be able to use this information information shortly. But that's why we have the loop over URL items because so it pulls from the item which comes through the loop and pulls, and pulls them one at a time. So we're in, we're in a good spot there. So we're almost, we're almost ready to, to, to go. But what I did a minute ago of adding the sheet is, was actually it's, it was, it's the right thing to do, but it's not in the right order. What we need to do is we need to give it a brain here, right? That actually allows it to be able to. Which actually allows it to be able to change and generate an icebreaker. It now has the information, I'm going to pin it. It now has the information, all of this that we can use, but it doesn't yet have the brain that, that it needs in order to be able to do it. So what we're going to do is I'm going to use open AI, choose whatever you want and we're going to get message, a model which we want the text actions message model. So inside of here you're going to have lots of options and things available to you. But one of the key things just to notice is from the input you have all of this information, like all of that is available for us to use as part of our icebreaker, because this is what's generating the icebreaker for us. This node is generating the icebreaker. So, going to pick a model. Let me choose. Let's just choose 3.5 Turbo. Now I have an already extremely efficient prompt and I'm going to put it in here now. I'll explain it to you and then I'm going to do something in Chat GBT in case you want to mess around and, and check on your own. I mean, you could pause this video and screenshot this, get Chat GPT to write you, or I can just put it in the description. But this basically just gives it a few key and core rules and tells it how to write this. This exact prompt generates me an open rate of between six and a half and eight and a half percent. So I know that this works, which is why I wanted to include it in this video. They're telling it a few specific things. Like you're telling it here. When it comes down to the rules, we're telling it to frame the message as an observation, not a compliment. So show you've done your homework. Don't just like, say how great you are, Damien, even though your name's fantastic. Don't just, don't just say that. You know, frame the message, reference a recent win or milestone, highlight the achievement is highlight that the achievement is rare or difficult. Frame the message as an observation. Reinforce the prospect's identity as a top performer. So you know, we have the, we have the rules, the list of rules of 13 rules. And one of them is that we do not use more than 20 words to keep it short. So maybe we'll show some examples. The final thing in that is all we need to do is just tell it to the response in the following in JSON format Icebreaker, which is the name of the, the. The exact name of the. The. The column that we just created an example Icebreaker just because obviously you're giving this. The system prompter a, A reference to use Oops. But if we, if we copy this, you could literally just go to chat gbt and I'm not going to do it for the sake of the video's time, but you could write out a prompt explaining what you're trying to do. Maybe even send, maybe even send a screenshot of the flow and explain whatever. But you know, if just to put this one in here and just say can you edit this prompt to ensure that. Maximum. Maximum. No, to ensure that the system goes into even greater depths to personalize the cold email icebreaker it's generating. So we just put that. So is a revised version of the prompt with the insights. Let me now see. Whilst that's happening, I'm just going to head back into here, close this down. We've chose, We've added that as a. We've added that now as a prompt. But what we. As a system. Sorry, I've actually left it as a user role. It needs to be a system prompt because we want to add another message and we want this to be a user prompt. And this user prompt is where we have the ability to be able to feed it dynamic information from our input into the node to be able to give it some things that it can go off of what it can base its content off of. Strict rules for follow up, sustained hiring, post funding, team caliber. Yeah, let's try it. Why not? Let's stick this in here. So I just showed you. I mean I did use a baseline to do it, but you can literally just get that. Let's just, let's, let's do this. Let's hope it doesn't break the flow. If it does, I'll use the others, but it won't. So now it comes down to this and a user prompt. And the user prompt is different because what we're trying to do now is give the system prompt is basically the chip that goes into the computer's brain to tell it what it's going to do. And the user prompt is allowing it to be able to reference information as if it was the user. What is it going to be given? Like, what is the. What is the information that is provided? So let's start with full name. So we just literally type full name. We're going to want to make it an expression and we're going to drag Damian's full name over and we're going to stick it next to full name. Then we're going to put in headline and we're going to drag the headline over. That's giving me the ick. Because it was not this. Not, not, not close. Let's give it the city, let's give it, let's give it the about information and we'll drag in the about section and we can keep going. I mean, what else is in here? Company. Should we put company in? You see how easy this is? The pin data, the output of the previous nodes just being used and literally is just allowing us to be able to drag and drop different pieces of information. And the final one, we'll give it as start date. Let's. Let's do this just for the use case. I don't want to do this from a video perspective. Yeah, so we've done that, but we've missed something quite important out of. Out of that, in terms of it. So I am going to add one more, even though I said I wasn't going to. But I like to put the title in. I like to put his title in because it will reference him as the owner. It will do these things. So you've got your prompt, we've chosen the model. We've got the system prompt, which I've copied and pasted now from. Copied and pasted now from, from ChatGPT. And yeah, like I've given it the information in terms of the user input that it's going to have. So we now have to just do one final thing, which is output the content as JSON as we have told it to do in the system prompt and hit execute. And let's see what happens. Right here we can see this is the icebreaker. Notice your quick ramp up to CEO at SASE Solutions in 2022. A rare leap. Few execute smoothly. So there's, there's one icebreaker, right? From. From one. Because we've only, we've only put one item through. The next step of this is for us to add. Well, we want to update the row, because we've got a Google sheet where we want this to go into. Generally speaking, you could just get the icebreakers themselves, but if you're going to use instantly, for example, you can just upload documentation and provide it all in one go. So let's go to sheets again, which you'll already have connected the credentials for. And we just want to update a row in a sheet. Now, there's a couple of critical elements here. We want to update a row, but we want to update a row from. We want to update a row from the URL, because again, that's going to be varied. The URL that we provide, it is going to be varied. So it needs to be an expression. So what we will do is if you go to the schema section, you can look through all the nodes that we've got in the flow. Well, if we open the get get row in Google Sheets, sorry, if we go to the form submission, Google Sheets URL document by URL, select the sheet. And then when it comes down to mapping columns, we do want to map each individual column manually. But importantly, we don't want to do it by id, which is a common way that you would do it if you were doing other flows. We don't want to do that. We want to use the LinkedIn URL. Okay. And we want to use that because the LinkedIn URL is something which is the unique parameter for us from a perspective of the way that we're approaching our whole flow. It's all based on LinkedIn scraping the LinkedIn URL. And what we're going to want to do here is go to the get a row sheet, where the get a row sheet is, and pull the variable for the LinkedIn URL and drag it over. Get rows in Google Sheet Item JSON, LinkedIn URL. So that is how it will use to match. So it will match the URL inside of the sheet, the LinkedIn URL inside of the sheet, like this one for Damien. It will match that with the icebreaker, which we're about to put in in a moment. So we find the icebreaker, we find the icebreaker column, and we go to where the icebreaker comes from, which is inside of the MESSENGER model. We drag that icebreaker in, we hit execute. Well, we hit command S and then we execute the step. And by executing that step, it didn't find the message in the. In the rows. Let me have a look. Sheet manual, LinkedIn URL ID. This should just be JSON icebreaker, JSON message icebreaker of course it can't update the row because I didn't pin this data. Now let's execute it. There you go. You saw the value of pinning data live in real time. Now that's updated a row. If we go to our Google sheet, you will see that the output has been updated and it's been updated matching Damien's. Damien's LinkedIn URL so what I'm going to do is I'm going to delete this because I'm going to run through the whole execution at the end. But we need to go back into here now. We're going to walk our line to try and make it look pretty. Looks awful. Hey, when it does that. But we now want to add. We now want to add. We certainly don't want to. Look how tricky that is. Certainly don't want to delete that. We want to add a done. So once it's done with its loop in, we want to add this section and all we want to do is add. And so, yeah, it's an aggregator, so we want to aggregate and we don't need to worry about mapping anything else here. We literally just want to change it to all, all light, all item data. That's what, that's all we need to do. Come out of there. I'm going to drag it up here because it looks awful. This is the flow it will take when it's done. Now, if you were to send like an email, if you were to use like a Gmail, send. Send a message, you know, you'd go through, send a message, you would have this to. You would map this from the previous node. Like if I go, it's not going to allow me to drag it. But if I, if I let me execute this. So now that it's got that information inside of it, if I drag that back and connect it, this is the value of obviously having connected nodes. You would come into here, you would have, you would go back to the form submission and you would have the email that you put in and you would, you would send it to that email. You would. The subject could be. I don't think I can select from the form the, the information. So the subject would be your. Your icebreaker sheet is complete, let's say, and the message is. Your icebreaker information can be found below. And then let me change it to an expression. And then you would go back to the URL of the sheet here from the form and you would simply paste it in because it'd be the same form that you put in that's there and then you would command S and save it. Send email, rename, add icebreaker. So scrape LinkedIn data. You should get into a habit. I didn't do. I don't do it during presenting as as much, but you should really get into the habit of doing this all even notes. But I, I prefer to rename them and I can generally tell them what they are. So we now have this flow, okay? So I'm going to save it. What I'm going to do is I'm going to execute this, this flow. I'm going to. Let me just delete this, right? And I'm going to. I'm going to execute this flow because it did that because it ran five times, but it didn't. It. It had, it did that because it had it five times but it had PIN data. That's why the same thing. So I'm going to unpin all of the data, okay. And I'm going to execute the workflow from here. I've executed. I've. I've unpinned everything and I hit save. I've unpinned everything. But except for the form submission initially. Execute workflow. It's now starting to work and it's going to start to generate. To generate that. And whilst that's happening, I'm going to load up my Gmail inbox and I'm going to zoom in a bit, probably a little bit too much as this comes. It's generating the icebreakers. As you can see, it's now creating individual icebreakers based off of what the individual user is. It's not doing it on the basis of. It's not doing it on the basis of like it did before where it put them all of the PIN data inside of here. It's actually writing each individual one. Not sure why it skipped. It skipped over over her over that individual one debugging it. I mean, let's have a look at the output in here. Looking @Icebreaker LinkedIn URL Chris, let's look at number four. Icebreaker empty. The scrape must have failed. No big deal. There's no real issue with that in this particular video and use case. But you've seen all of those things and yeah, like I can confirm to you by showing you this that I received an email that came through your Icebreaker information. It can be found below. You click the link, you see the scraped and personalized ice breaking system and you now have a fully function in generating Icebreaker flow. So you can play around with this. We could go inside of the the icebreaker, don't forget, by the way, this was the one that I used from ChatGPT. So let's say we give it 30 words and we want to strip to filler, but be extremely quirky. We want to stand out. Let's just say we tried this now. Okay, back out, command, save, execute, workflow. This is going to run again and it's going to update these, but we're going to see what it does if that prompt changes anything. So you noticing your visionary approach. Launching SAS. Launching a SAS venture solo in 2022 is an audacious move only a few thousands, a few founders dare attempt. Noticing your rare blend. Noticed it's using a lot of notices, which is common in mine. It's part of the reason why it works is relatively common in mind, but you don't normally see that amount in regards to like starting with the same with the same word. But, you know, maybe to show you again, let's see if we can fix that once this is done. Unless. Unless this chuck something out. Yeah, it took something else out, so we won't bother. But I was going to tell it not to use noticed and see how you can do it. But you can see very clearly from this flow how you can manipulate data and how you can actually be in a position where you can generate cold icebreakers based off of an individual user. If you wanted to, you could add more modules in here that scrapes more individual things to provide more information to the. To. To the individual. But I think that that completes the video for. For today. The final thing that I would say is that by linking an automated scraper into this icebreaker and then into apify, which are probably going to be the three steps of this video series that I'm doing, I could then group them all together and have a powerhouse that triggers just off of one individual form submission that goes through. But if you need icebreakers of the highest quality with a six and a half to an eight and a half percent open rate, depending on what industry you're in, I've just built it with you. My name's Michael. Thank you for watching like and subscribe. Catch you next time.










