4 min

de la

Automating a Grammar Variable with an AI in n8n

A personalization variable was left empty in our Lemlist campaigns. I built a small n8n sub-workflow that asks an LLM for the right French preposition in front of a company name. Simple on the surface, but the fix went sideways twice.

The problem

Some emails contain sentences like:

“Comment sécurisez-vous les communications {{companyDu}} ?” (“How do you secure the communications {{companyDu}}?”)

{{companyDu}} holds the preposition and the name: “de la MACIF”, “d’Allianz France”, “de CNP Assurances”. Until then, this variable was computed by hand at each lead import. Once lead creation and lead enrichment were automated, nobody filled it in anymore, and new leads arrived with that field empty.

The trigger

It was a campaign preview literally showing {{companyDu}} in the subject line of a test email that revealed the problem.

Don’t trust a single example

My first version relied on three leads already filled in by hand. A method mistake: I then audited the 32 campaigns through the API, searching for every {{company...}} pattern. There were actually two variables:

Variable Example sentence Expected output
{{companyDu}} “les communications {{companyDu}}” “les communications de la MACIF”
{{companyAu}} “voir si ça s’applique {{companyAu}}” “voir si ça s’applique à la MACIF”

companyDu appears in 13 campaigns, companyAu in 3, always alongside companyDu. They aren’t custom fields declared in Lemlist, but lead variables set case by case: only reading the sequences could reveal them.

Why an LLM rather than a rule

A rule on the first vowel isn’t enough. The gender of acronyms comes from usage: in French you say “la MACIF” but “le CIC”. A regex can’t guess it; a general-purpose LLM knows it for almost all targeted organizations (mutual insurers, banks, insurers, local authorities).

I use a lightweight model through OpenRouter, with forced structured output ({companyDu, companyAu}). If companyName is empty, the sub-workflow skips the LLM call.

What broke

A poorly framed prompt. My first prompt asked for the name “after the preposition de”. But the template has no preposition written in: the model could return “la MACIF” alone, and the sentence became “les communications la MACIF”. I rewrote it by quoting the real text:

Dans nos templates, AUCUNE préposition n'est écrite en dur avant la
variable : "les communications {{companyDu}}". TU dois produire la
préposition ET le nom ensemble.

The lesson

To fill a template variable, quote the actual sentence around it, never paraphrase it.

A fix that didn’t apply. Tests went green, I announced “it’s fixed”. The reply: “I don’t see any change.” The node still showed the old prompt.

A JSON path relative to the wrong object

The n8n editing tool takes a JSON path relative to parameters. I had written /parameters/options/systemMessage instead of /options/systemMessage. Result: a new parameters key nested inside the existing parameters, which n8n silently ignores.

{
  "parameters": {
    "options": { "systemMessage": "OLD TEXT" },
    "parameters": {
      "options": { "systemMessage": "NEW TEXT" }
    }
  }
}

No error, “success” on every call. Three updates, including the addition of companyAu, never took effect. The same trap is described in the Apollo/Lemlist enrichment piece.

Fix: replace the whole parameters object, then re-read the actual state of the workflow (draft and published version), which revealed the duplicated key.

Wiring in the fix

In n8n, an “Execute Sub-workflow” node replaces all the item’s data. So I call it in parallel with the main chain, then merge both branches by position before creating the lead.

The native Lemlist lead-creation node accepts no custom variables. companyDu/companyAu are set afterwards with a PATCH /api/leads/{id}/variables, on a side branch: if it fails, the confirmation to the rep isn’t blocked.

For testing, the tool only returned the execution status, not the model’s output. Structured output acts as a safety net (an invalid format makes the execution fail). Tests (MACIF, Allianz France, CNP Assurances, CIC) passed, and the “empty name” case runs in ~10 ms: the LLM is indeed skipped.

Takeaways

The workflow that updates existing contacts stays disabled until it’s validated with real writes. If a campaign introduces a new grammatical construction, it will go into this same sub-workflow. And since then, after every n8n node change, I re-read the actual state: a “success” says what the tool tried to write, not what will be read at execution time.

Read next

LinkedInGenerating LinkedIn Posts with AI for Sales Reps Covering Multiple Industries34The Rules My AI Agent Learned From Its MistakesTeamsDaily Lemlist Task Reminders in 1:1 Teams Chats with n8n