Merge Tags Hook
Use this hook to put a value in an email, different for each person.
How it works
You write a tag in an email:
You are a {{ plugins.local_loyalty_tier.tier }} member.
The middle part is your plugin ID, with . and - written as _. So local.loyalty-tier becomes local_loyalty_tier.
When the email is sent, Broadcast calls your function once for each batch of up to 100 people. You return one value for each person.
Full example: loyalty tier
This plugin works out a tier from a custom field named orders.
manifest.json:
{ "id": "local.loyalty-tier", "name": "Loyalty tier", "version": "1.0.0", "requires": { "broadcast": ">=3.0.0 <4.0.0", "pluginApi": "1" }, "permissions": { "subscribers": "read" }, "hooks": { "mergeTags": ["tier"] } }
dist/main.js:
function tierFor(orders) { if (orders >= 10) return "Gold"; if (orders >= 3) return "Silver"; return "Bronze"; } export default definePlugin({ mergeTags: { async tier({ subscribers }) { const values = {}; for (const person of subscribers) { const orders = Number(person.custom_data?.orders ?? 0); values[person.id] = tierFor(orders); } return values; }, }, });
What you get and return
You get this:
{ "name": "tier", "subscribers": [ { "id": 12, "email": "ada@example.com", "first_name": "Ada", "last_name": null, "custom_data": { "orders": 12 } } ] }
Each person always has id and email. first_name, last_name and custom_data are there only if the plugin has a subscribers permission.
You return an object, with the subscriber ID as the key:
{ "12": "Gold" }
A person with no value gets an empty string. A value can be a string or a number, up to 10,000 characters.
HTML values
A value is plain text. Broadcast escapes it, so <b> shows as those characters. To return HTML, you need two changes in the manifest:
"permissions": { "output": "html" }, "hooks": { "mergeTags": [{ "name": "badge", "output": "html" }] }
This is a sensitive permission. Broadcast cleans the HTML before it is sent, and it removes scripts. In a subject line, the tags are removed.
Personal links
A tag whose value is a link that is different for each person, for example a referral link, is declared with tracked: false:
"hooks": { "mergeTags": [{ "name": "referral_link", "tracked": false }] }
Click tracking leaves that link as it is. Without this, a tracked copy of each person’s link is made, one redirect row per person, and the click report shows one line per person.
Common mistakes
You make one call per person. You have 5 seconds for 100 people. ctx.broadcast.subscribers.get in a loop is too slow. Use the data you are given. Fetch one thing and use it for the whole batch.
You expect the send to wait for you. If your function is slow or fails, the value is empty and the email goes out without it. A failure still counts toward the 20 failures that turn a plugin off. Write your text so that it still reads well with an empty value.
You expect it in a test send. Plugin hooks run in broadcasts and sequence emails. They do not run in transactional emails or test sends. To see a real value, send a broadcast to a small segment.
You name a tag actions. plugins.<id>.actions is the place for public action links.