Adobe Campaign Classic Developer Expert study materials
Adobe Campaign Classic Developer Expert study resources

Personalization That Works in Proofs and Fails in the Batch

Adobe Campaign Classic Developer Expert · Put your knowledge to work in context.

All case studies

Personalization That Works in Proofs and Fails in the Batch

A telecom sends a monthly newsletter to 900,000 recipients from a recurring delivery built on a custom target mapping over nms:recipient extended with a 1:1 link to cus:contract. The body contains three elements: a shared personalization block 'header' reused by 14 deliveries, a greeting written as <%= targetData.firstName %>, and a tracked link https://t.example.com/o?cid=<%= targetData.contractId %>&src=nl. A JavaScript content block calls an in-house pricing API to select the best tariff. Three symptoms: every recipient in a wave receives the same tariff; 2% of the weekly alert delivery prints 'undefined' as the greeting; and roughly 900 clicks land on the wrong tariff because the URL is truncated. Proofs sent to two internal profiles all look correct.

1. Diagnose the identical tariff and describe the test that would confirm or eliminate each hypothesis.

Read the worked answer

Proofs pass because proof profiles are rendered in a low-volume path where each message is prepared individually; the batch path is where the defect shows.

Hypothesis 1 - wrong variable scope. If the JavaScript block stores the tariff in an application-level or module-level object rather than in per-recipient context, the first computed value is reused for every subsequent recipient. Each personalization script must read from the current target and write only to per-recipient scope.

Hypothesis 2 - shared block compiled once. Content in a shared block that contains no recipient-scoped reference is identical for all messages; if the API call was moved inside such a block, it is evaluated once per delivery, not once per recipient.

Hypothesis 3 - the API ignores the input. Less likely, but cheap to rule out.

Confirming test. Temporarily add <%= targetData.recipient.internalId %> next to the tariff in the block and send proofs to two different real recipients, then inspect the rendered source. Identical IDs mean scope/caching; different IDs with identical tariffs means the API or its parameter.

Design point. An HTTP call per recipient at 900,000 scale is fragile whatever the scope. Precompute the tariff into a field on cus:contract by a nightly workflow, read it with a fallback, and keep the API out of the rendering path.

2. Explain the 'undefined' greeting affecting 2% of recipients and give a robust expression.

Read the worked answer

The greeting resolves to nothing when the attribute is empty or when the path does not exist on the mapping being used.

Two distinct causes.

  1. Missing data. 2% of profiles simply have no first name - imported records, B2B contacts, deliberately blanked records after an erasure request. <%= targetData.firstName %> on an empty string renders as an empty string, but if the field is absent from the returned row it renders as undefined.
  2. Wrong path. With a custom target mapping, targetData is the mapping's schema, not nms:recipient. If the mapping is rooted on an extended schema, attributes reached through the contract link require the link prefix, e.g. targetData.contract.label. Using the recipient-based path inside a mapping whose root is elsewhere yields an unresolved expression.

Robust expression. Test before printing and fall back to a neutral wording that is valid for every recipient: <%= (targetData.firstName && targetData.firstName != '') ? targetData.firstName : 'cher client' %>

Also note the timing: batch content is resolved at preparation, so a value arriving in the database after preparation cannot appear in that send. If the field is fed by a late-arriving interface, the fallback is the only safe guarantee.

3. The tracked URL truncates. Explain the mechanism and give the corrected markup, including what else must be checked for click tracking.

Read the worked answer

Mechanism. The contract identifier is inserted raw into a query string. If it contains &, #, a space, a quote or an accented character, the browser and the tracking redirector both parse the URL at that character: everything after it becomes a new parameter or is dropped. cid=A&F-2231&src=nl arrives as cid=A, F-2231 (valueless) and src=nl, so the landing page attributes the click to the wrong contract. The same happens if the value contains a double quote and the href is written with double quotes.

Corrected markup. Percent-encode every dynamic value and quote the attribute: <a href="https://t.example.com/o?cid=<%= encodeURIComponent(targetData.contractId) %>&src=nl">

Also check.

  • The & separating cid and src must be the HTML entity &amp; if the document is parsed as strict XHTML; otherwise the parser may mangle it before the redirector sees it.
  • Click tracking rewrites the href, so verify the encoded value survives the redirect by inspecting the click log for two or three real recipients.
  • If the identifier is used as a lookup key on the landing page, decode it there before matching - double decoding is a common follow-on bug.