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.

Tags: , , , , , , , , ,
Subscribe
Notify of
guest
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Naveen
Naveen
3 years ago

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.

Stuart Kregor
Stuart Kregor
3 years ago

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.

sara
sara
Reply to  Stuart Kregor
3 years ago

Please can i get your help i have some questions

Guest9875
Guest9875
2 years ago

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

Dennis
Dennis
2 years ago

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”
},

Supraja
Supraja
2 years ago

Nice article, can you help how to send and preview for multiple rows/contacts of Dataxtension.

Supraja
Supraja
Reply to  Gortonington
2 years ago

I have tried that solution.. with out row value, request is throwing out an error.

Priyam
Priyam
1 year ago

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 emailID

Tobias V
Tobias V
1 year ago

It can be worth mentioning that scope required for at least the test sends, the package needs to have “Email Send” included