Ghost has 13 email personalization variables. Its own documentation lists none of them.
So the question keeps landing on the forum in one shape or another:

Can you insert a member's ID into an email that Ghost sends? Yes. You can put it in a link, an image URL, or the middle of a sentence, and you can do it without leaving the editor. Nothing on ghost.org tells you that, and a fair amount of what's written about it elsewhere describes a version of Ghost that stopped existing in February 2026.
Here's the whole thing, checked against Ghost 6.59.
Where you meet it first
Most people run into personalization through the email card, the tool that adds email-only content to a post without it showing up on the web:

Inside an email card, you write a keyword in curly braces:
{property_name}
You can add a fallback for the case where the property is empty, like a member who never filled in a name:
{property_name, "fallback text"}
That's the part everyone knows. It's also the part that makes people think personalization is an email card feature, which it isn't.
Two steps, and only the first one is about cards
There are two separate mechanisms here, and almost every wrong answer about Ghost personalization comes from treating them as one.
The email card wraps. A single function called wrapReplacementStrings turns {email} into %%{email}%% before the post is rendered. The email card is the only card in the editor that calls it.
The renderer substitutes. It takes the finished HTML of the entire email, scans it for anything matching %%{...}%%, and hands what it finds to Mailgun as recipient variables. It has no idea which card the text came from, and it doesn't look.
So the short form only works in an email card. The long form works anywhere in the post: an HTML card, a plain paragraph, a link, an image URL.
Which is why this, in an HTML card, does exactly what it looks like it does:
<img src="https://example.com/px?u=%%{uuid}%%">
Type the %% yourself and you're out of the email card's jurisdiction.
The 13 variables
They're defined in one place, a method called buildReplacementDefinitions:
Personal information
{first_name}- just the first name, extracted from the full name{name}- full name{email}- email address
Member status and subscription
{status}- free, paid, complimentary, or trialing{status_text}- human-readable status with dates, like "Your subscription will renew on August 15, 2025"{created_at}- when they became a member
Account management
{unsubscribe_url}- unsubscribe link{manage_account_url}- link to the account portal{list_unsubscribe}- the one-click unsubscribe URL Ghost puts in theList-Unsubscribeheader. It's added to every email whether you reference it or not.
Identifiers and helpers
{uuid}- the member's UUID{key}- a SHA-256 HMAC of that UUID, signed with your site's members validation key{name_class}- CSS helper, returns "hidden" if no name is set{uniqueid}- a fresh random UUID for every email, so image proxies can't cache one image across every recipient. Behind a private labs flag calledemailUniqueid.
The member ID, and the version you need
%%{uuid}%% works in the email body from Ghost 6.17.0 onwards, released 5 February 2026. Before that, three lines in the renderer stripped it out before anything else ran. If you're on an older Ghost, none of the UUID advice below applies to you.
The commit that deleted those three lines is worth reading, because it explains the reversal:
we weren't previously clear on uuid being 'public' data, although we expose it in various places
That's the honest position. The UUID isn't a secret. Ghost already hands the same value to the comments widget in every reader's browser, a point Cathy made about this post months before Ghost's own code caught up.
The same change fixed the more annoying half of the problem. Ghost rewrites outbound links to run through its own redirect for click tracking, so a personalized link used to be a choice: keep the personalization or keep the stats. Now Ghost carries the placeholder through the rewrite and fills it in at redirect time, from the ?m= parameter on the tracking URL. You get both.
What still doesn't work
Four real limits.
The short form outside an email card
{first_name} in an HTML card stays literal text. Nothing wraps it, so the renderer never sees it. Type the %% yourself.
One exception you probably don't have: with the private emailUniqueid labs flag on, HTML cards run the same wrapping as email cards. That flag only appears once you've turned on developer experiments.
Image cards
An image you upload or paste becomes an image card with a fixed URL, and there's no field to put a replacement string into. Per-member images have to go in an HTML card.
Click tracking on anything but the UUID
Redirect-time substitution only knows how to fill in %%{uuid}%%. A link carrying %%{email}%% or %%{key}%% gets skipped by link tracking entirely and left to Mailgun. The link works, the personalization works, the click never reaches your analytics.
The web version of the post
An HTML card renders on your website as well as in the email, and on the website %%{uuid}%% is just text sitting on the page.
The fix is the card's visibility settings: turn off all three toggles under Web – Public visitors, Free members, Paid members – and the card only goes out by email.
That trap has a sharper version if you ever write about this. The renderer does not exempt code blocks, so every %%{uuid}%% in the examples on this page is a live replacement string as far as it's concerned. Send a post like this one as a newsletter and each subscriber gets their own UUID printed in the samples.
Which identifier to reach for
The UUID
The default choice now. Stable for the life of the member, and Ghost treats it as public.
Your personalized content:
https://example.com/content?member=%%{uuid}%%
The validation key
{key} is an HMAC of the UUID, signed with your site's members validation key. You can't reverse it, which is the point: it identifies a member to your server without the UUID passing through a Mailgun log or a referrer header.
Your personalized content:
https://example.com/content?key=%%{key}%%
The catch is that you have to compute the same HMAC for every member on your end before the key means anything.
The email address
Unique per member and always available, but readable by everything your link passes through, and it changes the moment someone updates their address. The UUID doesn't.
Your personalized content:
https://example.com/content?member=%%{email}%%&status=%%{status}%%&joined=%%{created_at}%%
Thirteen variables, substituted across the whole email, and one formatting rule that decides where they work. That's the entire system. The only genuinely hard part is that you have to read email-renderer.js to find any of it out.

