Personalization Agent

At send-time, rewrites the subject + preheader + body for the specific recipient based on their traits + recent activity, while staying on brand.

Why send-time

Static templates with {{firstName}} work but feel templated. Send-time personalization writes the email as ifit was hand-written for the recipient — referencing what they actually did this week, what plan they're on, what they probably care about.

How to enable

Set PAD_PERSONALIZE=1as an env var. Off by default in v1 — opt in once you've validated your brand voice produces output you're happy with.

What gets sent

{
  "templateSlug": "renewal-reminder",
  "profile": {
    "externalId": "user_42",
    "traits": { "plan": "pro", "renewsOn": "2026-06-12" }
  },
  "recentActivity": "2026-05-19  workspace.created\n2026-05-18  invite.sent\n…"
}

Caching

Output is cached in Redis keyed by (templateSlug, profileId, traits-hash, recentActivity-hash) for 24 hours. If you re-send the same template to the same profile within a day, no second LLM call.

Limits

  • Skipped for category=transactional sends (predictability matters more).
  • Falls back to the original template if the LLM call fails or times out (10s).
  • Output schema is validated — if the model returns malformed HTML, we send the original.