## TL;DR
Put the exact substitution tag in your template, then map that exact string in `personalizations[0].substitutions`. The keys must match the template placeholders character for character, dashes included. If nothing substitutes, the key and the template tag differ, or you are using a dynamic template, which needs `dynamic_template_data` instead.

```text
sendgrid substitutions
```

## Use this when
- Template emails go out with raw tags like -name- still visible.
- You are migrating legacy templates to the v3 mail send API.
- You need per-recipient values in subject, body, or headers.

## Not for this skill when
- You use dynamic templates with handlebars syntax. Those take `dynamic_template_data`, not `substitutions`.
- Every recipient gets the same value. Use a global substitution or hardcode it.

## Steps
1. Open the template and note the exact tag text, e.g. the string -name- in the subject or body. Verify: you can quote the tag exactly.
2. Build the payload with the key equal to the tag: `personalizations: [{ "to": [{ "email": "[recipient email]" }], "substitutions": { "-name-": "Ruth" } }]`. Verify: the key string equals the template tag exactly.
3. Send a test to one address. Verify: the received email shows Ruth, not -name-.
4. Add global substitutions for values shared by all recipients. Verify: they appear in every recipient's copy.
5. For per-recipient uniqueness, give each personalization its own substitutions object. Verify: two test recipients get different values.

## Variant phrasings
### sendgrid substitution tags not working
Same root cause 9 times out of 10: the key does not match the tag.
### sendgrid personalizations substitutions
Covers the API-path search.
### sendgrid template variables legacy
Legacy templates use substitutions; dynamic templates do not.

Compatibility: SendGrid v3 Mail Send API. Legacy transactional templates only. Dynamic templates (handlebars) ignore substitutions entirely.

## Why it happens
Substitutions are plain string replacement keyed on the exact tag text. SendGrid does no fuzzy matching, no case folding, and no warning when a key matches nothing. So a template containing -Name- with a key of -name- silently passes through, and dynamic templates silently ignore substitutions because their engine reads dynamic_template_data.

## Edge cases / pitfalls
- Substitution keys are case-sensitive. -NAME- and -name- are different keys.
- Very long values can push the payload past size limits. Keep them short.
- Set substitutions inside each personalization. A key on the top-level object does not always inherit into personalizations.
- Never put secrets in substitution values. They travel in the clear.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Y2U2IoOF7UUnvN9Xs-of6w
