Skip to main content

Debugging render-time blanks

An email went out, recipients see 'Hello, ' with a blank where the first name should be. The Send shows Completed. AMPscript Lookup returned NULL without throwing. Five queries against the audience DE and any log DEs that find which of the three usual culprits the bug actually is.

How-to·Last updated 2026-05-19·Drafted by Lira · Edited by German Medina

An email went out. Recipients see Hello, (blank where the first name should be). Or Your tier: (blank where the segment should be). Or a content block that was supposed to render conditionally is just missing. The Send Activity reports Completed. There's no error in any log because Lookup and AttributeValue return NULL without throwing — see gotchas — #2.

This is the most common AMPscript failure shape, and it's almost always one of three things: a column missing from the audience DE that the email expected to read; a Lookup against a different DE the recipient doesn't have a row in; or a Subscriber Attribute confused with a DE column. The five queries below separate the three.

The render-time flow

[ Send Activity fires JobID N against audience DE A ]
        ↓ for each row in A...
[ AMPscript reads %%[Column]%% from row, AttributeValue() from profile, Lookup() from other DEs ]
        ↓ each read either returns a value or NULL silently
[ Email rendered + sent — blanks land in the body where reads returned NULL ]

The diagnostic walks each arrow. Step 1 confirms the row was in the audience. Steps 2-4 check each read mechanism. Step 5 captures the postmortem.

Step 1 — Was the recipient in the audience?

If a specific recipient reports a blank, start by confirming they were actually in the audience DE for that JobID.

-- Replace 'de_send_audience_<send_name>' with the actual sendable DE
-- Replace the SubscriberKey with the affected recipient's key
SELECT
  SubscriberKey,
  EmailAddress,
  FirstName,
  Tier,
  COUNT(*) AS RowsForThisSub
FROM de_send_audience_<send_name>
WHERE SubscriberKey = 'abc-12345'
GROUP BY SubscriberKey, EmailAddress, FirstName, Tier;

Three failure shapes here:

  • No rows returned: the recipient wasn't in the audience for this JobID. They received the email anyway via a different Send (cross-Send fan-out, BU sharing). The AMPscript ran against a different audience row than you think it did.
  • One row, but FirstName is NULL: the audience DE itself doesn't have the value. Continue to step 2.
  • One row, all values populated: the audience DE has the data, but the AMPscript didn't read it. The bug is in how the script references the value — continue to step 4.

Step 2 — Did the audience DE have the column populated?

If the affected row shows NULL in the column the email tried to render, the audience-build SQL Activity is the bug source. Count NULL rates across the whole audience.

SELECT
  COUNT(*) AS TotalRows,
  COUNT(FirstName) AS RowsWithFirstName,
  COUNT(*) - COUNT(FirstName) AS RowsMissingFirstName,
  CAST((COUNT(*) - COUNT(FirstName)) * 100.0 / COUNT(*) AS DECIMAL(5,2)) AS PctMissing
FROM de_send_audience_<send_name>;

If PctMissing is over a few percent, the audience-build SQL is leaving the column NULL for a meaningful slice of recipients. The AMPscript default (IF Empty(@firstName) THEN ... = "Friend" ENDIF) is what should be catching this — if it's not, the AMPscript missed the defaulting rule from the Style Guide.

The fix is in two places:

  1. AMPscript: add the Empty() default so the next send doesn't render blank.
  2. SQL Activity upstream: investigate why the column is NULL for those rows. Usually a LEFT JOIN that should have been INNER JOIN, or a missing default in the source data.

Step 3 — Did the script log a NULL Lookup result?

If the AMPscript follows the Style Guide and logs reads to a de_log_ampscript_renders (or similar) DE, query it for the affected JobID + recipient.

-- This assumes the script wrote rows like:
--   InsertData("de_log_ampscript_renders",
--     "JobID", _jobid, "SubscriberKey", _subscriberKey,
--     "Step", "lookup-segment", "Value", @tier, "Ts", Now())
SELECT
  Step,
  Value,
  Ts
FROM de_log_ampscript_renders
WHERE JobID = 'JOB-2026-05-19-01'
  AND SubscriberKey = 'abc-12345'
ORDER BY Ts;

What the rows tell you:

  • A row with Step = 'lookup-segment' and Value empty/NULL confirms the Lookup returned nothing for this recipient. The bug is in the source DE the Lookup queries — that DE doesn't have a row for this SubscriberKey, or the filter doesn't match.
  • No row at all for lookup-segment: the AMPscript block didn't reach the lookup. Earlier in the script there's an IF that branched the wrong way, or a Platform.Load equivalent failure, or a parse error.
  • Rows with non-NULL values for every step: the AMPscript read the values fine but rendered something else. The bug is in the inline form — likely %%@x%% for a local instead of %%=v(@x)=%%.

If the script doesn't have render-time logging, this step returns nothing. The next send needs the instrumentation added; see the Style Guide.

Step 4 — Source confusion: DE column vs Subscriber Attribute

If steps 1-3 show the data is there and the script tried to read it, the bug is likely the gotcha #4 confusionAttributeValue("FirstName") read from the Subscriber Attribute (profile) while the column on the sendable DE was where the value actually lived (or vice versa).

-- Compare the Subscriber Attribute value vs the sendable DE column value
-- for the affected recipient
SELECT
  a.SubscriberKey,
  a.FirstName AS DE_FirstName,
  s.FirstName AS Profile_FirstName
FROM de_send_audience_<send_name> a
LEFT JOIN _Subscribers s
  ON a.SubscriberKey = s.SubscriberKey
WHERE a.SubscriberKey = 'abc-12345';

What the two values tell you:

  • DE_FirstName = "Mariana" and Profile_FirstName = NULL → the audience DE has the data, but the AMPscript read AttributeValue("FirstName") (which goes to the profile, returning NULL). Fix: change to %%[FirstName]%% or the bracket form to read the DE column.
  • DE_FirstName = NULL and Profile_FirstName = "Mariana" → the profile has the data, the DE doesn't. The AMPscript read the DE column (which is NULL). Fix: either populate the DE column upstream, or change the read to AttributeValue("FirstName").
  • Both populated, different values: a sync issue. Decide which source is canonical (per the Style Guide, it's usually the DE column for sendable contexts), and align.

Same shape as the original gotcha — the defense is naming the sources distinctly so this confusion is impossible (DEFirstName vs ProfileFirstName).

Step 5 — Recovery + postmortem

Once the cause is found, capture it in de_log_ampscript_postmortems for future reference (same convention as the SSJS debugging snippets).

INSERT INTO de_log_ampscript_postmortems
SELECT
  GETDATE()                                                                                       AS DiagnosedAt,
  'CP_PreferencesCenter'                                                                          AS AssetName,
  'JOB-2026-05-19-01'                                                                             AS JobID,
  'abc-12345'                                                                                     AS AffectedSubscriberKey,
  'blank FirstName in header greeting'                                                            AS Symptom,
  'audience-build SQL had LEFT JOIN to legacy profile DE; ~3% of rows missing FirstName silently' AS RootCause,
  'added INNER JOIN + AMPscript Empty default; verified next-send render via real test send'     AS Fix;

The Symptom and RootCause columns force clarity. "Blank greeting" is the symptom; "LEFT JOIN to legacy profile DE" is the root cause. Six months later when a similar blank shows up, the postmortem table is the history that tells you "we've seen this exact shape before — it was a JOIN issue, check the audience-build first."

Common causes ranked by frequency

| Cause | How to spot | Fix in | |---|---|---| | Lookup/AttributeValue without Empty() default | Step 3 shows NULL Value; step 1 shows the source DE doesn't have the row | Style Guide; add IF Empty(@x) THEN ... ENDIF | | Audience-build SQL leaves the column NULL for some rows | Step 2's PctMissing is more than a few percent | SQL Style Guide; audit the JOIN that produces the audience DE | | AttributeValue vs DE column confusion | Step 4 shows the value in one source but the script read the other | gotchas — #4; rename sources distinctly | | %%@x%% used for a local variable that didn't resolve | Step 3 shows the value was read fine but the email still rendered blank | gotchas — #1; switch to %%=v(@x)=%% | | Recipient wasn't in the audience (cross-Send fan-out) | Step 1 returns no rows | Audit the Automation; the email reached the recipient via a different Send | | Per-recipient Lookup against a DE missing the recipient's row | Step 3 logs the empty Value but step 1 confirms recipient is in audience | Pre-shape upstream; the joined value belongs in the audience DE | | Conditional content block hidden by an IF branching wrong | Step 3 has no log row for the expected step | Audit the conditional logic in the email body |

Related