Generate personalized outreach messages from a CSV
You hand a contact file to your assistant, and it hands back the same file with four messages written for each person, ready to import into a campaign.
The usual approach to personalization is to write a template and drop a first name and a company name into it. Everyone does that, and recipients spot it at a glance. The method described here is different: you write the entire message, for each person, and the entire message becomes the variable.
The kit to hand your assistant
This is not a simple prompt but a complete chain: checking the input file, the writing itself, the output format, writing back into the CSV, and building the campaign.
The writing prompt we use, wrapped in everything you need around it to go from a contact file to a campaign ready to launch.
- The check to run on the file before spending anything on generation
- Your offer sheet, written once, used for every contact
- The four-step writing prompt, with its guardrails against invention
- The output format you have to require, and the way out when the assistant has nothing specific to say
- The column-name trap at import, and the two ways around it
- The quality check to run on ten contacts before launching
Why the whole message rather than variables inside the text
A template with a first name inserted is still a template. The sentence was visibly written for someone else with one word swapped, and that is exactly what everyone does, including the people sending fifty thousand emails a day.
Writing the entire message removes that ceiling. The icebreaker talks about what that particular company actually does, the analogy is borrowed from its industry, the order of the arguments changes depending on who you are writing to. None of that fits into a variable.
The trade-off is time and tokens: a few seconds and a few cents per contact. That pays off on two hundred hand-picked decision makers, it does not on ten thousand bought addresses. If your list does not deserve this treatment, it is the list that needs fixing, not the method.
What goes in, what comes out
The starting file needs a first name, a last name, a company and an email address. It performs far better with the job title and the company website, which is by far the most useful column: it is the one that says what the company really sells.
| What you provide | What it changes in the message |
|---|---|
| First name, last name, company | the bare minimum, and the result shows it |
| Job title | the level of the argument: you do not write to an executive the way you write to someone on the ground |
| Company website | the icebreaker stops being vague and talks about what they actually do |
| LinkedIn profile URL | lets you mention a career path or a recent move into a new role |
| Industry, headcount, city | calibrate the examples and the orders of magnitude |
On the way out, eight columns are added to your file: four subject lines and four messages. The contacts the assistant had nothing specific to say about go into a second file with the reason, rather than ending up in the first one with empty cells.
This is the most important rule in the whole kit. An AI missing a piece of information will happily replace it with a plausible invention: a revenue figure, a funding round, a customer. A prospect who reads a false claim about their own company does not reply, and will never reply again. The prompt forbids it explicitly, and the quality check is there to verify that it obeyed.
The trap that breaks everything at import
When Emelia receives a column it does not recognize, it tries to file it on its own: it looks at the name, then at the value, and only creates a custom variable as a last resort. At that point, it flattens the name.
So a message_etape_1 column becomes the messageetape1 variable, and the {{message_etape_1}} you put in your campaign stays hopelessly empty. It is the kind of problem you only notice after sending.
Name the columns without separators : Run-together words survive the import exactly as they are. It is the simplest fix, and the kit applies it by default.
messageEtape1, objetEtape1, messageEtape2, objetEtape2, messageEtape3, objetEtape3, messageEtape4, objetEtape4The second fix, which goes with the first, is to declare the variables before the first import, so they take precedence over automatic detection. The file gives the exact path to follow.
In the campaign, each step contains nothing but the variable
Once the list is imported, there is nothing left to write. The message was written in full, it stands on its own: the step just displays it.
Content of step 1 : And nothing else. No greeting wrapped around it, no signature: the prompt already ruled them out so that the message is usable as is.
{{messageEtape1}}On steps 2, 3 and 4, leave the subject line empty: the follow-up then stays in the thread of the first email, which clearly improves the odds of it being read. Keep the generated subject lines in the file anyway, they will be useful if you later decide to open a new thread.
Finally, plan for the empty cell. A variable with no value leaves a blank, and the email goes out empty. The safest option is not to import contacts without a message.
When not to do this
- On a list you did not choose. Personalization does not rescue bad targeting, it only makes it more expensive.
- On contacts with no usable information. Four messages written from a first name alone are four generic messages, produced at the price of four personalized ones.
- On more than two or three people in the same company. They talk to each other, and receiving emails "written for you" that were obviously stamped out of the same template is the surest way to burn the whole account.
- Instead of deliverability. A perfect message in the spam folder is still an unread message. The domain, the warm-up and authentication come before the writing.
Frequently asked questions
The questions we get asked on this topic, answered briefly.
Which model should I use to write the messages?
The prompt in the kit works with any decent recent model. What makes the difference is not the model but the raw material: an assistant given the website and the job title will write better than a more powerful model given nothing but a first name.
Why is the prompt in English when the emails are not?
That is deliberate. Models follow structured instructions better in English, while writing perfectly well in another language when you ask them to. The style rules that depend on the language of the emails, such as the level of formality and the ban on flattering openers, are written in that language inside the prompt.
How much does it cost?
A few cents per contact depending on the model, and nothing on the Emelia side: credits pay for data and for AI actions run inside Emelia, whereas here it is your own assistant doing the writing. The real cost is your AI provider's.
Can the same thing be done for LinkedIn?
Yes, by adapting the length rules: a connection note cannot go over 200 characters, and a LinkedIn message is read on a phone. The principle stays the same, one column per message and one variable in the step.
Do the messages have to be read one by one?
No, but ten of them do, picked at random. You are looking for three things: an unverifiable claim about the company, a variable left visible between braces, and an invented signature or name. If those ten are clean, the rest usually are too.