Content Builder has a great Send Preview and Test Send process inside the User Interface, but that does not help if you need to automate your previews or test sends.
You could utilize some methods I have seen, including creating ‘test’ Data Extensions that have only internal contacts. You then building a Send Definition using your email and that test DE as the recipients via API. Then execute that Send Definition.
This is not only a fairly ‘clunky’ process (as you may need to build multiple test DEs depending on required fields and content) but it also creates a lot of unnecessary super message usage (as these are live sends), but also adds in ‘test’ sends into your data views and tracking data.
So, what the heck? How do we solve this? REST and SOAP have nothing documented to help with this….so are we out of luck?
NOPE! Thanks to a bit of investigation, I have found a few endpoints that will help solve this issue. As a note, as always, the below are UNDOCUMENTED, so they are use at your own risk.
Send Preview
First section I am going to talk about the send preview options via REST api. This includes options based on contact, lists, data extension row and list contact.
You will need the following information (depending on the call) in order to utilize the below calls:
{{subdomain}} - the subdomain for your tenant endpoint from your API application
{{emailid}} - the id of your email (can be found in UI or retrieved by API)
{{listid}} - the id of your list (can be found in UI or retrieved by API)
{{deid}} - the id of your data extension ( retrieved by API)
{{contactid}} - the contact id of your subscriber (aka SubscriberID)
EMAIL PREVIEW – NO CONTEXT
POST /guide/v1/emails/{{emailid}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This will output processed html with empty values for the ampscript variables and personalization strings.
Sample output below:
{ "message": { "links": { "self": { "href": "/v1/emails/{{emailid}}/preview" } }, "views": [ { "contentType": "vnd.exacttarget.message.email.htmlBody", "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n</head>\r\n<body>\r\n HTML BODY HERE \r\n</body>\r\n</html>" }, { "contentType": "vnd.exacttarget.message.email.subjectLine", "content": "Subject Line" }, { "contentType": "vnd.exacttarget.message.email.textBody", "content": "\r\n TEXT BODY HERE \r\n" }, { "contentType": "application/vnd.etmc.email.View; kind=preheader", "content": "Preheader" } ] } }
EMAIL PREVIEW BY CONTACTID
POST /guide/v1/emails/{{emailid}}/contacts/{{contactid}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This call will output a rendered version of the email utilizing information stored in Contact Attributes (aka Subscriber Attributes) only.
Output for this as well as the rest is identical to the one above, just the content inside htmlbody and textbody properties will adjust according to ‘sendable data’ assigned inside the url.
EMAIL PREVIEW BY LIST
POST /guide/v1/emails/{{emailid}}/lists/{{listid}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This call will output a rendered version of the email utilizing the architecture of a list (but not a specific contact).
This is an interesting one that I do not really understand the purpose of. It outputs the same empty content that you get in the ‘No Context’ version. Maybe this could be used to verify if a list contains the required attributes to pass validation or not, but not sure if that is even relevant.
EMAIL PREVIEW BY LIST SUBSCRIBER
POST /guide/v1/emails/{{emailid}}/lists/{{listid}}/contacts/{{contactid}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This call will output a rendered version of the email utilizing contact information (via ContactID) stored in a specific list or group (assigned by ListID or GroupID).
EMAIL PREVIEW BY DATA EXTENSION AND CONTACTID
POST /guide/v1/emails/{{emailid}}/dataExtension/{{deid}}/contacts/{{contactid}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This call will output a rendered version of the email utilizing contact information (via ContactID) stored in a specific data extension (assigned by DE ID).
EMAIL PREVIEW BY DATA EXTENSION ROW
POST /guide/v1/emails/{{emailid}}/dataExtension/{{deid}}/row/{{row}}/preview Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json
This call will output a rendered version of the email utilizing a row number (assigned by inside of the specified data extension
As a note, there are also duplicates of the above in the /’hub/’ REST endpoints, but its literally identical in input/output, so I have not listed them here.
Test Sends
Next we will discuss the capabilities to create actual test sends to an email address via the REST API. This can include single sends from a preview of a specific subscriber or utilizing a full DE of previews.
You will need the following information (depending on the call) in order to utilize the below calls:
{{subdomain}} - the subdomain for your tenant endpoint from your API application
{{emailid}} - the id of your email (UI or API)
{{contactid}} - the contact id of your subscriber aka SubscriberID
{{contactkey}} - the contact key of your subscriber aka SubscriberKey
{{listid}} - the list id that your subscriber is on (UI or API )
{{sendClassificationID}} - the ID assigned to a Send Classification (API)
{{deliveryProfileID}} - the ID assigned to a Delivery Profile (API)
{{senderProfileID}} - the ID assigned to a Sender Profile (API)
NOTES ON TEST EMAIL CALLS:
- You will only be required to include the sendClassificationID in order to have the test send function, but the others are nice if you want to utilize something other than the defaults assigned to that send classification.
- Groups are considered ‘lists’ (e.g. GroupID = ListID) and Filtered, Salesforce, Shared, etc. DEs are considered ‘Data Extensions’. Use ID same as regular DEs.
- If you want to use ‘All Subscribers’ for {{listid}}, rather than rely on a subscriber existing in a specific list, you just need to gather the All Subscribers List ID and use that.
Also as a note, the Send Classification, Delivery Profile and Sender Profile IDs will need to be collected via the associated SOAP API objects as there currently is not any REST endpoints (that I can find) that can gather this information.
SEND TEST EMAIL – SUBSCRIBER BY ID
POST /guide/v1/emails/preview/send Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json { "dataSource": { "contact": "id:{{contactid}}", "id": "{{listid}}", "type": "List" }, "emailID": "{{emailid}}", "isMultipart": true, "options": { "EnableETURLs": "true" }, "recipients": [ "{{insertYourEmailHere}}" ], "sendManagement": { "sendClassificationID": "{{sendClassificationID}}", "deliveryProfileID": "{{deliveryProfileID}}", "senderProfileID": "{{senderProfileID}}" }, "subjectPrefix": "[Test]:", "suppressTracking": true, "trackLinks": true }
The response on success will just be a regurgitation of your payload. E.g. the above call would send back:
{ "emailID": "{{emailid}}", "subjectPrefix": "[Test]:", "dataSource": { "contact": "id:{{contactid}}", "type": "List", "id": "{{listid}}" }, "recipients": [ "{{insertYourEmailHere}}" ], "sendManagement": { "sendClassificationID": "{{sendClassificationID}}", "deliveryProfileID": "{{deliveryProfileID}}", "senderProfileID": "{{deliveryProfileID}}" }, "sendID": 12345, "jobID": 123435, "sendScheduledTime": "2025-04-21T12:53:34.7750955-36:00", "trackLinks": true, "suppressTracking": true, "isMultipart": true, "options": { "enableETURLs": "true" } }
If it fails, it will send over a simple object that contains the error message and code in it. E.g.
{ "message": "A valid email ID (integer) or CustomerKey (string) must be provided to send a preview email.", "errorcode": 10002, "documentation": "" }
SEND TEST EMAIL – Contact Key
POST /guide/v1/emails/preview/send Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json { "dataSource": { "contact": "key:{{contactkey}}", "id": "{{listid}}", "type": "List" }, "emailID": "{{emailid}}", "isMultipart": true, "options": { "EnableETURLs": "true" }, "recipients": [ "{{insertYourEmailHere}}" ], "sendManagement": { "sendClassificationID": "{{sendClassificationID}}", "deliveryProfileID": "{{deliveryProfileID}}", "senderProfileID": "{{senderProfileID}}" }, "subjectPrefix": "[Test]:", "suppressTracking": true, "trackLinks": true }
This call is near 100% identical to the ‘Subscriber by ID’ call above, except instead of "contact": "id:{{contactid}}"
it instead is "contact": "key:{{contactkey}}"
. The responses will be the same as well with the adjustment of showing key instead of id.
SEND TEST EMAIL – DE ROW PREVIEW
POST /guide/v1/emails/preview/send Host: {{subdomain}}.rest.marketingcloudapis.com Authorization: Bearer {{authToken}} Content-Type: application/json { "dataSource": { "id": "{{deid}}", "row": 1, "type": "DataExtension" }, "emailID": "{{emailid}}", "isMultipart": true, "options": { "EnableETURLs": "true" }, "recipients": [ "{{insertYourEmailHere}}" ], "sendManagement": { "sendClassificationID": "{{sendClassificationID}}", "deliveryProfileID": "{{deliveryProfileID}}", "senderProfileID": "{{senderProfileID}}" }, "subjectPrefix": "[Test]:", "suppressTracking": true, "trackLinks": true }
With this one you can chose the row of the DE you want to use – changing "row": 1
to any numerical value you want (within rowcount) to select a subscriber.
The really cool part of this one is that if you remove the row part and just put in the DE ID and the type part in data source, it will send a test send of EVERY SINGLE RECORD inside that DE. I have not verified the volume limitations, so keep an eye on that – but for most use cases for bulk test sends, this should be a great option.
That is pretty much it for the send preview and testing. Feel free to drop a line to me if you have anything specific you are looking for me to go over. Also, please check out howtosfmc.com (A new site I am working with a few other awesome sfmc geeks on) for some really cool and useful things.
Hi Gregory,
This is a great blog. I just wanted to let you know that I am one of your biggest fan on SFMC 🙂
I have a question, When I try to do a post call to the end point – “/guide/v1/emails/{{emailid}}/preview”.
Am getting error – 596 service not found.
Am getting an access token and when i pass the access token and do a POST call to the URL “/guide/v1/emails/{{emailid}}/preview” am getting error.
I get it these API’s are un-documented in SFMC. But just wanted to check with you is it still working or there an issue from my end only.
Thanks for your help.
SFMC have recently been commenting on forums – even old ones – that the preview API is not supported and could ‘cease working at any time’.
This API is public and ‘out in the world’ and my view is they are trying to sell their archiving solution.
Any thoughts on this?
Personally this solution is clean, easy and much simpler that the expensive and clumsy solution SFMC sell…and it may take push back and feedback from SFMC engineers such as yourself to get them to properly support this API.
As a note, the API calls I have here may be undocumented, but if you use the DEV tools in Chrome or similar and view the network tab, you will notice these are actually the API calls they are using internally to make test send and send previews for the UI. I do not foresee them pulling the plug on these any time soon and for now should be a safe bet. That being said, they are not officially documented or published API endpoints, so there is always the inherit risk of change/removal at any time without notice. As far as their archiving solution – I think the simpler solution is to collect the View As Webpage links from each send and then have an external service that does a GET on these links to grab the content for each email. This combined with a send log should handle most of your archiving needs.
Please can i get your help i have some questions
Please feel free to drop me a message in my contact form or go to https://salesforce.stackexchange.com/ and post your questions there. I usually lurk there and there are a ton of equally smart people that may also be able to assist.
Hi, first of all: great article about some really useful hidden features!
Has anybody been able to build a working example for the “Email Preview by Data Extension Row” part?
I always receive the error “Must provide a valid row key (integer) to preview an email.”
From the chrome DEV tools I can see that the correct endpoint would be
guide/v1/emails/MY-EMAIL-ID/dataExtension/MY-DATA-EXTENSION/row/22/preview
although my data extension only has 9 entries. After deleting my DE entries and re-importing, the row value increases when rendering a preview, so it seems there is some internal counter for the rows applied.
Do you know if and how this counter is retrievable, so I can use it to generate previews myself?
Thank you in advance
Hi Gregory. First of al, great article. Iam missing however the option I needed. A test send with API in combination with a particular contact, instead of a row number. Also I did get some error messages with the code above. It wasn’t recognising the data extension as ‘sendable’ and ‘active’. When I prepended with the word “key:” it worked for me.
“dataSource”: {
“id”: “key:{{deid}}”,
“contact”: “key:{{contactkey}}” ,
“type”: “DataExtension”
},
Nice article, can you help how to send and preview for multiple rows/contacts of Dataxtension.
I believe if you remove the ‘row’ property from the data extension one it will test against all records in that row. Outside of that, I think its only 1 contact per test.
I have tried that solution.. with out row value, request is throwing out an error.
Hi which API will give the emailID? When I create an email using
/asset/v1/content/assets
, emailID is not returned in response. An id is returned, but it isn’t the emailIDIt can be worth mentioning that scope required for at least the test sends, the package needs to have “Email Send” included