Help & Support
Elevate Your Agents with AgentExchange Solutions
Have Questions?
Marketing Message Test
Overview
Salesforce Marketing Cloud / Engagement 360 message preview and test-send. Use this skill when the user wants to render a personalized or unpersonalized preview of an E360 message (Email, SMS, WhatsApp, Push, In-App, RCS) from a content key, or fire a test send, over the headless API — no app UI. TRIGGER when: user has a contentKey and wants to see how a message renders for a recipient, preview a message headlessly, or send a test message. DO NOT TRIGGER when: the task is authoring message content, building Data Cloud segments/DMOs (use data360-segment / data360-harmonize), or activating a segment (use data360-activate).
marketing-message-test: E360 Message Preview & Test Send
Render a per-channel preview (or fire a test send) for a Marketing Cloud / Engagement 360 message, headlessly, starting from a single contentKey. Covers all six channels: Email, SMS, WhatsApp, Push, In-App, RCS.
Full endpoint tables, request bodies, response-field rules, and the version-floor matrix live in references/api-contract.md. This file is the operating procedure; consult the reference for exact payloads.
When This Skill Owns the Task
Use marketing-message-test when the user wants to:
- render how an authored message looks for a real recipient (personalized preview)
- render content-only with no recipient (unpersonalized preview)
- send a test message to their own address / phone / individual id
Delegate elsewhere when the user is:
- building Data Cloud segments or calculated insights → data360-segment
- building DMOs, mappings, or identity resolution → data360-harmonize
- activating a segment downstream → data360-activate
Required Context to Gather First
contentKey— required, irreducible. The caller must supply it; it is not derivable.- target org alias (for
sf org open/ access token) - channel — Email, SMS, WhatsApp, Push, In-App, or RCS
- mode — personalized preview, unpersonalized preview, or test send
- for a test send only: the user's own destination(s) —
toAddresses(Email),toPhoneNumbers(SMS/WhatsApp/RCS), ortoIndividualIds(Push/In-App). Never derive these.
Transports
| Purpose | Transport | Instance-relative path |
|---|---|---|
| Preview · test send · sender lookups | LWR Apex | lwr/apex/v68.0/… — the exact operation path is on each step's Call line |
| Managed content · data space · DMO · segment · profile | Connect REST | services/data/vXX.0/… (per-resource floors; v62.0+ covers all six channels) |
WhatsApp template fetch (whatsapp-template-fetch) |
Headless dispatch | /headless/invoke/… — a dispatch route, so invoke it through the dispatch tool, not sf api request rest (see the reference doc for details) |
Versions:
- LWR-Apex — not version-gated; pin
v68.0(the path just needs a well-formedvXX.0segment). - Connect REST — real per-resource floors; highest is data-spaces at v62.0, so v62.0+ covers all six channels (others lower — see the reference floor matrix).
Always use sf api request rest — never curl. It attaches the org's bearer token internally, so the credential never enters your context. Never extract a token (e.g. sf org display --json) or put one in an Authorization header — that leaks a live credential and is disallowed. Pass the instance-relative path (no host); the CLI prepends the instance URL and accepts both transports (LWR-Apex paths aren't under services/data/, but any instance-relative path works). One step is the exception — whatsapp-template-fetch is a headless /headless/invoke dispatch route, invoked through the dispatch tool rather than sf api request rest.
# GET (default method); path is instance-relative
sf api request rest 'services/data/v63.0/ssot/data-spaces' --target-org <org>
# POST with a flat JSON body from a file (content-type: application/json is sent by default)
# path = the target operation's **Call** line (e.g. the `preview-sms-unpersonalized` step)
sf api request rest '<lwr/apex Call path>' \
--target-org <org> --method POST --body @body.json
Verb rule (do not violate — wrong verb is rejected):
- Preview / test-send methods are POST (
--method POST) with a flat JSON body whose keys are exactly the parameter names — no envelope, no wrapper object. - Sender / permission lookups are GET (default method) with args packed into a single
methodParamsquery param (URL-encoded JSON string) appended to the path; no-arg methods omit it.
Core Operating Rules
- Never fabricate a
segmentId,profileId, DMO name, or destination. Every value is either given by the user or derived from the org via the chain below. - Preview needs no sender — skip sender lookups (Part 3 of the reference) unless test-sending.
- Personalized vs unpersonalized: unpersonalized needs only
contentKey; personalized needssegmentId+recipientDataModelObjectName+contentKey+profileId, and theprofileIdmust be a member of thatsegmentId(it comes from that segment's member list — see the chain). - Personalization is supported ONLY with a valid segment and a member of it. The recipient (
profileId) comes solely from the chosen segment's member list, and must be a member of thesegmentIdpassed to the render. If the segment has no members — or no segment exists in the content's data space — the personalization render cannot be performed: reportcannot personalize — no valid segment memberand stop. The unpersonalized preview (contentKeyonly) is still available; offer it. Never source the recipient from anywhere else (e.g. by listing individuals from the DMO) — a non-memberprofileIdis rejected by the render with an opaque error. - A test send is a real send to the supplied destinations — confirm the user asked for it and has provided their own destinations before firing any test send (the
send-test-…steps). If the user asked only to preview, never fire a test send. - Check send permission before every test send. Before firing any
send-test-<channel>, call that channel's permission check (stepperm-can-send-test-<channel>— a no-arg GET returning a Boolean). If it returns false, surface a permission error and stop — do not attempt the send. Preview never needs this check. profileIdis a Data Cloud profile key (e.g.a1b2c3d4…), NOT a CRM id (003…) or a segment id (1sg…).- Run the derivation yourself end to end via
sf api request rest; don't hand the user raw commands to run.
Derivation Chain (personalized preview / test send, from contentKey)
Detailed payloads for every step: references/api-contract.md.
- Fetch content —
GET /services/data/vXX.0/connect/cms/contents/{contentKey}(v57+) →managedContentId,contentBody. Use the CMS Authoring API (keyed bycontentKey), not the delivery API — it returns the draft the preview renders. - Recipient DMO (from content) →
recipientDataModelObjectName. ParsecontentBody['lightning:dataProviders']forobjectApiName(that is the DMO) ordataGraphApiName— for the latter,GET /ssot/data-graphs/{dataGraphApiName}(v58+) and readprimaryObjectName(its response also returnsdataspaceName, usable directly in step 3). If the content declares no data provider, leave the DMO unresolved for now — it comes from the org fallback in step 3, which needs the data space first. - Data space →
dataSpaceName(scopes every/ssot/…call below).GET /ssot/data-spaces(v62+).- DMO declared in step 2 — derive the data space from the DMO name:
ssot__…prefix or an unrecognized name →default; a<prefix>_token → match against theprefixfield and take that entry'sname.defaulton single-space orgs. - No DMO declared — there's no name to match, so choose the data space first:
defaulton a single-space org, otherwise ask the user which space. Then resolve the DMO from the org via the org-fallback lookup (stepmessaging-selected-uma-dmo, body{ dataSpaceName }). Anullresult means "no personalized preview available for this org" — report that, do NOT retry.
- DMO declared in step 2 — derive the data space from the DMO name:
- Segment →
GET /ssot/segments?dataspace=<name>(v55+). Keep theapiName(fetches members) and the1sg…marketSegmentId(thesegmentId). - profileId — from the chosen segment's members, the only valid source:
GET /ssot/segments/{segmentApiName}/members?limit=10&dataspace=<name>(v58+) → memberid. Thatidis theprofileId; because it's a member of the segment, the render accepts it.- If the member list is empty (or no segment exists in the data space), stop — personalization cannot be performed. There is no valid recipient: report
cannot personalize — no valid segment memberand offer the unpersonalized preview instead. Do not source the recipient elsewhere (e.g. by listing individuals from the DMO) — aprofileIdthat isn't a member of the passedsegmentIdis rejected by the render with an opaque error.
- Render — call the channel's personalized-preview operation (step
preview-<channel>-personalized, or the…-by-localevariant) with the flat body{ segmentId, recipientDataModelObjectName, contentKey, profileId, … }.
Unpersonalized short-circuits after step 1: call the channel's unpersonalized-preview operation (step preview-<channel>-unpersonalized) with { contentKey } (add locale for Email's …-by-locale variant).
Test send = the channel's permission check (step perm-can-send-test-<channel>; stop if false), then steps 1–6, then a sender lookup (Part 3 of the reference) for fromAddress/senderId, then the channel's test-send operation (step send-test-<channel>) with the user's supplied destination array.
Reading the Rendered Output
- Email — flat object of string fields. Rendered HTML is in
bodyfor personalized calls,nonAggregateHtmlBodyfor unpersonalized — readbodyfirst, fall back tononAggregateHtmlBody. Also surfacesubjectandpreheader. - SMS — rendered text is the
contentstring; MMS also carriesmediaUrl. - WhatsApp — returns
{ content, metadata }as JSON strings; reassemble{{k}}slots from the fetched template (see the reference — this is the fiddly one). - Push / In-App / RCS — read the returned rendered fields per channel.
Fetch media from the rendered
<img src>, not the content definition. Rendered srcs are public-CDN URLs (…salesforce-experience.com/_scs/cms/…) that fetch unauthenticated.contentBody/cms/media/…refs are session-gated — unauthenticated they return an HTML login page (HTTP 200, no error), not image bytes.
Content Variables
If the content declares content variables under sfdc_cms:schema.properties, they render empty unless supplied. Ask the user for a value for each — never invent one — and pass them via recipientAttributesJson (a JSON string), on Email/SMS/WhatsApp/RCS only.
Push and In-App personalized previews do not support content variables. If any are declared in the content definition (
sfdc_cms:schema.propertiesnon-empty) for one of those channels, stop and inform the user it's not supported — do not troubleshoot.
High-Signal Gotchas
- Wrong verb is rejected — preview/send = POST + flat body; sender lookups = GET +
methodParams. contentKeyis irreducible — every path bottoms out at it; the user must supply it.- Data space must match the content, or segments/profiles won't personalize correctly.
- DMO may need the org fallback; the org-fallback DMO lookup (
messaging-selected-uma-dmo) returningnull= "no personalized preview available," not a retryable error. - Recipient must be a segment member: the
profileIdcomes only from the chosen segment's member list and must be a member of thesegmentIdpassed to the render. No members (or no segment) → personalization cannot be performed; stop and offer unpersonalized. Never source the recipient from a DMO listing — a non-memberprofileIdis rejected with an opaque error. - Profile-key field: segment members return the profile key as
id(a Data Cloud key, e.g.a1b2c3d4…— not a1sg…segment id or a003…CRM id); thatidis theprofileId. - Version floors are per-resource — LWR-Apex is not version-gated (pin
v68.0; the path segment just needs to be well-formed); the REST derivation resources have per-resource minimums. Highest REST floor is data-spaces at v62.0, so target v62.0+ for full six-channel coverage (lower channels work below that).
Output Format
Message: <contentKey> (channel: <Email/SMS/WhatsApp/Push/In-App/RCS>)
Mode: <personalized preview / unpersonalized preview / test send>
Target org: <alias>
Derived inputs: dataSpace=<name> DMO=<…__dlm> segmentId=<1sg…> profileId=<profile key>
API calls: <method — verb — endpoint> (one line each)
Rendered output:
<subject / rendered text / HTML body, per channel>
Next step: <act / adjust inputs / follow-up>
References
- references/api-contract.md — full endpoint tables, request bodies, response-field rules, per-resource version floors, and the end-to-end recipe for all six channels.
Operations Reference
Operations this skill uses — routing and dependency reference.
| Operation | Purpose | Status | Call | Depends on |
|---|---|---|---|---|
cms-content-fetch |
read | — | GET /services/data/v63.0/connect/cms/contents/{contentKeyOrId} |
— |
messaging-selected-uma-dmo |
read | — | POST lwr/apex/v68.0/interaction__MessagingController/getSelectedUnifiedIndividualDmoForUma |
data-spaces-list |
data-graph-primary-object |
read | — | GET /services/data/v63.0/ssot/data-graphs/{dataGraphApiName} |
— |
data-spaces-list |
read | — | GET /services/data/v63.0/ssot/data-spaces |
— |
dmo-describe |
read | — | GET /services/data/v63.0/ssot/data-model-objects/{dmoName} |
— |
segments-list |
read | — | GET /services/data/v63.0/ssot/segments |
data-spaces-list |
segment-members-list |
read | — | GET /services/data/v63.0/ssot/segments/{segmentApiName}/members |
segments-list |
profile-fetch-by-id |
verify | — | GET /services/data/v63.0/ssot/profile/{dmoName}/{profileId} |
— |
cms-spaces-list |
read | — | GET /services/data/v63.0/connect/cms/spaces |
— |
preview-email-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getEmailUnpersonalizedPreviewContent |
cms-content-fetch |
preview-email-unpersonalized-by-locale |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getEmailUnpersonalizedPreviewContentByLocale |
cms-content-fetch |
preview-email-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getEmailPersonalizedPreviewContent |
cms-content-fetch, segments-list |
preview-email-personalized-by-locale |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getEmailPersonalizedPreviewContentByLocale |
cms-content-fetch, segments-list |
preview-sms-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getSmsUnpersonalizedPreviewContent |
cms-content-fetch |
preview-sms-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getSmsPersonalizedPreviewContent |
cms-content-fetch, segments-list |
preview-whatsapp-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getWhatsappUnpersonalizedPreviewContent |
cms-content-fetch |
preview-whatsapp-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getWhatsappPersonalizedPreviewContent |
cms-content-fetch, segments-list |
preview-push-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getPushUnpersonalizedPreviewContent |
cms-content-fetch |
preview-push-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getPushPersonalizedPreviewContent |
cms-content-fetch, segments-list |
preview-inapp-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getInAppUnpersonalizedPreviewContent |
cms-content-fetch |
preview-inapp-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getInAppPersonalizedPreviewContent |
cms-content-fetch, segments-list |
preview-rcs-unpersonalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getRcsUnpersonalizedPreviewContent |
cms-content-fetch |
preview-rcs-personalized |
read | — | POST lwr/apex/v68.0/interaction__JourneyController/getRcsPersonalizedPreviewContent |
cms-content-fetch, segments-list |
whatsapp-template-fetch |
read | — | GET /headless/invoke/platform/conversation-setup/whats-app-templates/get-by-id |
— |
send-test-email |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestEmail |
cms-content-fetch, segments-list, sender-email-from-addresses, perm-can-send-test-email |
send-test-email-by-locale |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestEmailByLocale |
cms-content-fetch, segments-list, sender-email-from-addresses, perm-can-send-test-email |
send-test-sms |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestSms |
cms-content-fetch, segments-list, sender-sms-codes, perm-can-send-test-sms |
send-test-whatsapp |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestWhatsapp |
cms-content-fetch, segments-list, sender-whatsapp-numbers, perm-can-send-test-whatsapp |
send-test-push |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestPush |
cms-content-fetch, segments-list, perm-can-send-test-push |
send-test-inapp |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestInApp |
cms-content-fetch, segments-list, perm-can-send-test-inapp |
send-test-rcs |
write | — | POST lwr/apex/v68.0/interaction__JourneyController/sendTestRcs |
cms-content-fetch, segments-list, sender-rcs-agents, perm-can-send-test-rcs |
sender-email-from-addresses |
read | — | GET lwr/apex/v68.0/interaction__JourneyController/getFromAddresses |
— |
sender-email-dkim-valid |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/fromAddressHasValidDKIM |
sender-email-from-addresses |
sender-sms-codes |
read | — | GET lwr/apex/v68.0/interaction__JourneyController/getSenderSmsCodes |
— |
sender-whatsapp-numbers |
read | — | GET lwr/apex/v68.0/interaction__JourneyController/getSenderWhatsappNumbers |
— |
sender-whatsapp-waba-ids |
read | — | GET lwr/apex/v68.0/interaction__JourneyController/getSenderWabaIdsAndNames |
— |
sender-rcs-agents |
read | — | GET lwr/apex/v68.0/interaction__JourneyController/getSenderRcsAgents |
— |
perm-can-send-test-email |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestEmail |
— |
perm-can-send-test-sms |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestSms |
— |
perm-can-send-test-whatsapp |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestWhatsapp |
— |
perm-can-send-test-push |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestPush |
— |
perm-can-send-test-inapp |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestInApp |
— |
perm-can-send-test-rcs |
verify | — | GET lwr/apex/v68.0/interaction__JourneyController/canUserSendTestRcs |
— |