Skip to main content
IntermediateCampaigns and Sequences

Personalization Variables

Use merge variables, AI-generated personalized lines, custom fields, and intent signals to personalize every NimbusOS email at render time.

10 min read
Updated April 23, 2026
2,150 words

Personalization in NimbusOS goes well past merge fields. A template can render a contact's first name, a researched personalization line, a detected intent signal, a custom field your workspace defined, and a campaign-level token all in the same sentence. The render engine resolves all of them with a single Jinja-style pass. This article covers every variable source, the fallback behavior, conditional blocks, and the hyper personalization engine that produces most of the AI-generated variables.

Variable Syntax

The syntax is double curly braces: {{variable_name}}. Conditional blocks use {{#if variable}}...{{/if}}. An else clause uses {{#if variable}}...{{else}}...{{/if}}.

Names are case sensitive. Standard fields use lowercase with underscores. Custom fields use whatever name the workspace defined, case-sensitive.

Core Contact Variables

These variables resolve from the Contact row for every send. Almost every template uses a subset.

Name fields. {{first_name}}, {{last_name}}, {{full_name}}.

Contact details. {{email}}, {{phone}}, {{mobile}}, {{linkedin_url}}.

Role fields. {{title}}, {{department}}, {{seniority}}.

Location fields. {{city}}, {{state}}, {{country}}, {{time_zone}}.

Lead fields. {{lead_source}}, {{lead_score}}, {{icp_tier}}, {{grade}}.

Missing values render as empty strings by default. Most real-world CSVs have holes, so guard against it with conditional blocks or fallback values.

Core Company Variables

Variables prefixed with {{company_}} resolve from the linked Company row.

Identity. {{company_name}}, {{company_domain}}, {{company_website}}.

Size and industry. {{company_industry}}, {{company_employee_count}}, {{company_revenue}}.

Location. {{company_city}}, {{company_state}}, {{company_country}}.

Tech and tooling. {{company_tech_stack}} (list), {{company_crm_tool}}, {{company_outbound_tool}}.

Public profiles. {{company_linkedin_url}}, {{company_twitter_url}}.

The company row is populated at import time if the company domain was mapped. Enrichment fills in fields that the CSV did not carry.

Personalization Variables

The hyper personalization engine produces a PersonalizationResearch record for each contact it runs against. From that record come the AI variables.

{{personalized_first_line}}. A single opener line generated from the contact's LinkedIn bio, recent posts, company context, and the offer the sender is leading with. This is the highest value variable in a cold outreach template.

{{compliment}}. A specific, not-generic compliment referencing something the prospect has done or said. Always conditional; do not send if the compliment field is empty because a missing compliment next to a generic greeting reads worse than no compliment at all.

{{hook}}. A one-sentence value hook tailored to the prospect. Longer than a first line, shorter than a pitch.

{{industry_insight}}. A data point or observation relevant to the prospect's industry. Usually 10 to 20 words.

{{writing_style_match}}. A reference phrase written in the prospect's observed writing style (formality, tone, emoji frequency). Used rarely, mainly for reply messages.

Each of these is stored on a PersonalizedLine record with a variant number. A contact can have three variants of {{personalized_first_line}}, and the campaign or A/B test picks one per send.

Intent Signal Variables

The intent signal engine detects 25+ signal types and records them as IntentSignal rows. High-confidence signals become variables.

{{recent_hiring_signal}}. "We saw you are hiring three DevRel engineers" kind of line. Fires only if the hiring signal relevance is above 70 and recency is within 30 days.

{{recent_funding_signal}}. Series A, B, C announcements. Conservative; most templates do not use this because funding is a sensitive topic.

{{tech_stack_change_signal}}. When the prospect's company switched a tool your sender would care about. Requires enrichment data, so only fires for prospects with full enrichment.

{{job_post_signal}}. Related to the Apify LinkedIn Jobs Scraper pipeline. Fires if the company has posted a relevant role in the last 14 days.

Intent signals are all conditional. None render if the signal does not exist or the confidence is below the template's threshold.

Sender and Campaign Variables

Variables that resolve from the sender context, not the recipient.

{{sender_name}}. The display name of the inbox actually sending the message. Matches the From header.

{{sender_company}}. The workspace's configured company name, or the white-label client's name for agency sub-client campaigns.

{{sender_title}}. The configured title for the sending inbox.

{{sender_signature}}. Full signature block, which may include calendar link, phone, LinkedIn profile.

{{campaign_name}}. Internal name of the campaign. Rarely used in the body but sometimes in internal test templates.

{{unsubscribe_link}}. Required by CAN-SPAM. The platform auto-appends an unsubscribe link in the footer, but you can place it inline with this variable if you prefer.

Custom Fields

Custom fields defined on the workspace appear as {{custom.field_name}}. For example, if you defined a custom field onboarding_stage, reference it as {{custom.onboarding_stage}}.

Custom fields can be strings, numbers, booleans, or dates. Date fields support formatting: {{custom.trial_end_date | date:'long'}} produces "April 23, 2026".

Conditional Blocks

The {{#if}}...{{/if}} block is a workhorse. Three common patterns.

Guard against missing personalization.

{{#if personalized_first_line}}
{{personalized_first_line}}

{{/if}}

If the personalized line exists, it renders with a blank line after. If it does not exist, nothing renders. The rest of the email continues cleanly.

Branch by tier.

{{#if icp_tier == 'A'}}
I know you are heads-down so I will keep this to two sentences.
{{else}}
Quick note for you.
{{/if}}

Tier A prospects see one opener; everyone else sees a different one.

Use intent signal if present, fall back if not.

{{#if recent_hiring_signal}}
Noticed you are hiring {{recent_hiring_role}}.
{{else if recent_funding_signal}}
Congrats on {{funding_round}}.
{{else}}
I have been tracking {{company_name}}.
{{/if}}

Pick the strongest signal, fall back gracefully.

Fallback Values

You can configure a fallback per variable on the template detail page. A common fallback is {{first_name}} with fallback "there". When the first name is blank, the render produces "Hi there," instead of "Hi ,".

Fallbacks are workspace scoped. Setting "there" as the fallback for {{first_name}} applies to every template in the workspace.

Variable Resolution Order

When the engine renders a template, it resolves variables in this order.

  1. Campaign context (sender, campaign, unsubscribe link).
  2. Contact row (core fields, custom fields).
  3. Company row (core fields, enriched fields).
  4. Hyper personalization record (personalized lines, intent signals).
  5. Fallback values for anything still unresolved.
  6. Empty string for anything with no fallback.

If two sources could resolve the same name, step 2 wins over step 3, and both win over step 4. In practice, naming is disambiguated ({{company_industry}} vs {{industry}}), so collisions are rare.

Preview with Real Contact Data

The template editor has a preview mode that renders against a selected contact. Two benefits.

You catch missing custom fields before sending. A template that references {{custom.onboarding_stage}} but the contact has no value for that field renders as an empty string, which you can see in the preview and handle with a fallback or conditional.

You see what the personalization engine produced. If {{personalized_first_line}} is blank for a test contact, you know the engine has not run on that contact yet, and you can trigger research before enrolling in a campaign.

Protecting Against Rendering Disasters

Three defensive patterns.

Never start an email with "Hi {{first_name}}" without a fallback. If the first name is blank the email reads "Hi ,". Set a fallback or use a conditional.

Never reference an uncommon custom field in the subject line. Blank subject is a spam signal. Guard custom fields with conditional blocks in the subject.

Always preview against a contact with minimal data. Pick the contact with the thinnest profile in your list and preview the template. If it reads well for them, it reads well for everyone.

Automated Variable Fill at Enrollment

When a contact is enrolled in a campaign that uses personalization variables, the platform fires a research task automatically through the hyper personalization engine. The task runs in the background and typically completes within 5 to 15 minutes.

The first send is deferred if the research has not finished yet (unless the contact is tier D, where research is skipped for cost reasons). This is why a campaign's first batch does not fire the instant the campaign activates; the platform is waiting for the personalization to fill.

You can see research status on the contact detail page. Status values: pending, in_progress, completed, failed, skipped.

Frequently Asked Questions

How do I find which variables are available?

The template editor has a dropdown labeled "Insert variable" that lists every available variable for the current template context. Custom fields appear grouped under "Custom".

Can I define a new variable not on the dropdown?

Not without a custom field or a code change. Custom fields cover most cases. For truly dynamic values (like the number of days since a prospect last engaged), the platform offers a set of computed variables, listed in the docs for the scoring and engagement models.

Will the engine hallucinate personalization?

No. {{personalized_first_line}} and siblings resolve from AI output stored in PersonalizedLine records. If no such record exists, the variable resolves to empty, not to a made-up line. The hyper personalization engine is strict about only producing output backed by research data.

Can I run the same template with and without personalization?

Yes, through conditional blocks. The template can include a personalization block guarded by {{#if personalized_first_line}}. Contacts with research see the personalization; contacts without see the generic fallback.

Are there any reserved variable names I cannot use for custom fields?

Yes. Every standard variable name is reserved. You cannot define a custom field called first_name because it would collide. The custom field creator rejects reserved names at save time.

After you have personalization variables working, useful next pages are Email Templates for the template versioning and A/B story, Data Enrichment for the enrichment that fills the company variables, and Reply Intelligence for how personalization performance is measured in the reply data.

Related articles

Still stuck?

Our team answers every support ticket. If the answer is not in the docs, open a ticket and we will write the missing page.