This is the technical round of a Salesforce Marketing Cloud interview, the one that follows the Email Studio and Journey Builder screening. The panel stops asking what a tool does and starts asking what you have written and what you have connected. Expect AMPscript in a shared editor, then questions on how Marketing Cloud talks to the outside world through REST, SOAP and an installed package. The questions below come from real interview rounds collected by our trainers, answered at the level a working developer is expected to give.
AMPscript interview questions
What is AMPscript?
AMPscript is Marketing Cloud's scripting language for personalising content at the moment a message is built. It runs server side, inside the tenant, once per subscriber. When a send starts, the platform takes each row of the send audience and processes the AMPscript in that message against that row.
It runs in emails, SMS, push messages, CloudPages and Landing Pages, and in content blocks included in any of those. It does not run in the browser, so it cannot react to anything that happens at open time. It also does not run in an Automation Studio activity or a query, because those are not message contexts. A Script Activity takes SSJS, not AMPscript.
The follow-up is which language to reach for:
| Language | Runs where | Right choice for |
|---|---|---|
| AMPscript | Messages, CloudPages | Personalisation, lookups, conditional content, small writes |
| SSJS | CloudPages, Script Activities, messages | Loops over large data, try and catch error handling, API calls through WSProxy, anything AMPscript cannot express |
| GTL (Guide Template Language) | Content blocks and templates | Iterating a rowset into repeated HTML, such as order line items, with the markup kept readable |
Candidates lose marks on the second half of this answer. Our trainers see the same gap every batch: they know the syntax but not the execution, when it runs, where it runs and what data it can reach.
What are the three ways to write AMPscript?
| Form | Syntax | Use it for |
|---|---|---|
| Block | %%[ ... ]%% |
Variables, lookups, conditional logic, anything multi-line |
| Inline output | %%=v(@name)=%% |
Printing one value into the HTML |
| Tag-based | <script runat="server" language="ampscript"> |
Blocks in a CloudPage where the percent syntax is awkward |
Put the logic in a block at the top and print with inline output lower down. Scattering lookups through the HTML is what makes a template impossible to debug later.
Difference between AMPscript and Personalization Strings?
A personalisation string such as %%FirstName%% prints an attribute from the send context and nothing more. No logic, no fallback, no ability to reach another data extension.
AMPscript does everything a personalisation string does, plus lookups, conditions, data writes and API calls. The real difference is failure behaviour. A personalisation string fails silently when the field is empty, which is how an email goes out reading "Dear ,". AMPscript with AttributeValue() and an Empty() check gives you a controlled fallback.
Difference between Lookup and LookupRows?
Lookup() returns one value from the first matching row. LookupRows() returns a rowset, which you must unpack before you can print anything from it.
%%[
/* Single value: Lookup returns the first match, or an empty string */
VAR @tier, @rows, @rowCount, @row, @offerCode
SET @tier = Lookup("Loyalty_Master", "TierName", "SubscriberKey", _subscriberkey)
/* Many rows: LookupRows returns a rowset, not something you can print */
SET @rows = LookupRows("Active_Offers", "TierName", @tier)
SET @rowCount = RowCount(@rows)
IF @rowCount > 0 THEN
SET @row = Row(@rows, 1)
SET @offerCode = Field(@row, "OfferCode")
ELSE
SET @offerCode = "WELCOME10"
ENDIF
]%%
Lookup() on a missing row returns an empty string and carries on, so the damage is cosmetic. LookupRows() on a missing row returns an empty rowset, and calling Row() on it throws a runtime error. The RowCount() guard is the difference between a template that degrades and one that fails. Where order matters, LookupOrderedRows() takes a row limit and a sort clause as well, so you can ask for the three newest orders instead of everything.
What is RowCount() and Row() function?
RowCount(@rowset) returns how many rows a rowset holds. Row(@rowset, 1) returns one row from it, and AMPscript rowsets are one-indexed. Field(@row, "ColumnName") reads a column from that row. The sequence never changes: look up, count, guard on the count, then read.
An unguarded lookup is the usual reason a send breaks. If the rowset is empty and you call Row(@rows, 1) anyway, AMPscript raises a runtime error for that subscriber. The message is not built and not delivered, and the error goes to the send log. Bad data for one person gives you a handful of errors. A renamed column or a wrong data extension name gives every subscriber the same error, and the send produces nothing but failures. That is why interviewers ask for the guard by name.
What is AttributeValue()?
AttributeValue("FieldName") reads an attribute from the send context by its name as a string, and returns an empty string when the attribute is missing instead of erroring.
It is also the only way to read a field whose name contains a space, and the only way to read a field whose name you build at runtime, for example AttributeValue(Concat("Offer_", @tier)).
How do you handle null or blank values?
Read the value with AttributeValue(), trim it, test it with Empty(), then set a fallback. Never print a raw attribute into customer-facing copy.
%%[
VAR @firstName, @greeting
SET @firstName = Trim(AttributeValue("FirstName"))
IF Empty(@firstName) THEN
SET @greeting = "Hello there"
ELSE
SET @greeting = Concat("Hello ", ProperCase(@firstName))
ENDIF
]%%
<p>%%=v(@greeting)=%%,</p>
Empty() covers null and empty string together, which is why it beats comparing to "". The Trim() matters more than it looks, because imported CRM data carries trailing spaces and a single space is not empty. To hide a whole section, wrap the HTML itself in the IF block rather than the value, so the empty table row and its padding never render.
What is IIF vs IF statement?
IF ... THEN ... ELSE ... ENDIF is a statement. It can wrap HTML, run to several lines and hold further logic in each branch. IIF(condition, trueValue, falseValue) is a function that returns one of two values, meant for a single inline decision such as %%=IIF(@gender == "M", "Mr", "Ms")=%%.
Use IIF for one-line substitutions and IF for anything that changes the structure of the message. Nesting IIF inside IIF is legal and unreadable.
How do you use UpsertDE()?
UpsertDE() updates a matching row in a data extension and inserts one when no match is found. The modern equivalent is UpsertData(), which does the same and returns the number of rows affected, so you can test whether the write happened.
%%[
VAR @rowsAffected
SET @rowsAffected = UpsertData("Email_Preferences", 1, "SubscriberKey", _subscriberkey, "LastEmailOpened", Now(), "TierName", @tier)
IF @rowsAffected == 0 THEN
RaiseError("Preference upsert failed", true)
ENDIF
]%%
The number after the data extension name is how many of the following column and value pairs are match keys. Here it is 1, so SubscriberKey identifies the row and the remaining pairs are the fields written. The target needs a primary key matching what you match on, otherwise the upsert inserts duplicates instead of updating. The same split runs through the family: InsertData() returns rows inserted, InsertDE() returns nothing and fails quietly.
What is the order of AMPscript execution in email?
Within one part of the message, AMPscript runs top to bottom as the content is assembled, once per subscriber at send time.
The part that catches people is that the subject line, the HTML body and the text body are separate contexts. A variable set in the HTML body is not available in the subject line, so a subject that needs a looked-up value has to repeat the lookup.
Content blocks pulled in with ContentBlockByName() are processed where they are included and can read variables declared earlier in the body, which is how you pass values into a shared block. Declare them above the include, not below it.
How do you encode personalized links safely?
Build the URL in a variable, encode each parameter value with URLEncode(), then print it in the href.
%%[ SET @link = Concat("https://www.example.com/offers?sk=", URLEncode(_subscriberkey), "&code=", URLEncode(@offerCode)) ]%%
<a href="%%=RedirectTo(v(@link))=%%">View your offer</a>
URLEncode() stops an ampersand or a space inside a value from breaking the query string. RedirectTo() keeps the link trackable when AMPscript produces the URL. For sensitive values, pass them to a CloudPage and read them there rather than exposing customer data in a query string.
What is RaiseError()?
RaiseError("message", true) deliberately stops the message for that subscriber and writes your message into the send error log. The second argument controls whether the whole send halts or only that message is skipped.
Use it as a data contract. A receipt with no order total is worse sent than not sent, so you raise an error and let support see why. It is not a general error handler.
How do you debug AMPscript?
Print the intermediate values into the body with %%=v(@variable)=%% and use Preview and Test in Content Builder against a real subscriber from the sending data extension. Preview resolves AMPscript with actual data, so most logic errors surface before a send. For anything that writes, add a logging data extension and InsertData() the subscriber key, a timestamp and the values you looked up. Check RowCount() before blaming the syntax, because an empty rowset and a broken lookup look identical from outside.
This separates people quickly. Candidates cannot debug. Our trainers put it plainly: "Your Business Rule is not firing, what do you check?" beats "what is a Business Rule?" every time, and the Marketing Cloud version is "your personalisation is blank for half the audience, what do you check first?"
How do you fetch more than 2000 records?
You do not, at send time. LookupRows() is capped, and a template needing thousands of rows for one subscriber is a design fault, not a syntax problem.
Move the work upstream. Aggregate with a SQL Query Activity in Automation Studio so each subscriber has one prepared row, then look up that single row in the email. Where a genuine bulk read is needed outside a message, use SSJS with WSProxy and page through the results. Interviewers ask this to see whether you know what belongs in a send and what belongs in an automation.
What the interviewer is actually checking on AMPscript
- Whether you guard every rowset with
RowCount()before touchingRow(), or only remember it when prompted - Whether you can say where AMPscript runs and where it does not, including that a Script Activity takes SSJS
- Whether your fallbacks are deliberate, using
AttributeValue()andEmpty(), instead of hoping the data is clean - Whether you can find a blank personalisation without rewriting the template
- Whether you know when to push work into a Query Activity instead of into the email
SFMC API and integration interview questions
What is REST API vs SOAP API in SFMC?
| REST API | SOAP API | |
|---|---|---|
| Format | JSON over HTTPS | XML over HTTPS |
| Endpoint | {subdomain}.rest.marketingcloudapis.com |
{subdomain}.soap.marketingcloudapis.com |
| Covers | Journey events, transactional messaging, Contacts, Assets, Automation | Subscribers, Data Extension objects, Triggered Sends, tracking objects |
| Typical use | Real time events from a website or app | Bulk retrieves, admin operations, anything with no REST equivalent |
Both use the same OAuth 2.0 access token from the same installed package, so this is not a choice of platform, only a choice of endpoint. REST covers the newer features and is easier to work with. SOAP still covers retrieve operations and objects that REST does not expose.
How do you authenticate API calls?
Create an API Integration component in an installed package, then exchange the client credentials for an access token at the tenant's auth endpoint. Send that token as a bearer token on REST calls and in the SOAP header on SOAP calls. There is no username and password authentication for integrations.
Tokens are short lived. The token response carries its expiry, and a correct integration caches the token and refreshes it shortly before it lapses rather than requesting one per call.
What is OAuth 2.0 Client Credentials flow?
A server to server flow with no user involved. Your application holds a client ID and secret, posts them to the token endpoint, and receives an access token scoped to the permissions the package was granted.
curl -X POST https://<subdomain>.auth.marketingcloudapis.com/v2/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "<client id>",
"client_secret": "<client secret>",
"account_id": "<target business unit MID>"
}'
The response returns the access token, its lifetime, and the REST and SOAP instance URLs for that tenant. Use the returned instance URLs rather than hard-coding them. The optional account_id is how one integration acts against a specific business unit, provided the package has access to it.
Setting up the package is part of the answer. In Setup, open Installed Packages, add a package, add an API Integration component, choose the server to server type, then grant only the scopes the integration needs.
How do you secure API credentials?
Keep the client secret off the client entirely. It belongs on a server or in middleware, never in an email, a CloudPage or any JavaScript a customer can view.
Scope the package to the minimum permissions and to the business units it needs, rotate the secret on a schedule and immediately when someone with access leaves. For values that must sit inside Marketing Cloud, use Key Management for encryption keys and a restricted data extension for lookups.
What is Event API (/interaction/v1/events)?
The REST endpoint that injects a contact into a journey whose entry source is an API Event. You post a contact key, the event definition key from that entry source, and a data payload.
POST /interaction/v1/events
{
"ContactKey": "CUST-10432",
"EventDefinitionKey": "APIEvent-8f2c9a41",
"Data": {
"OrderId": "SO-99213",
"CartValue": 4999
}
}
The fields inside Data must match the data extension attached to the event definition. If they do not, the call is accepted and the contact still does not enter, which is the most common support ticket on this endpoint.
What is Transactional Messaging API?
The REST API for one to one messages triggered by a business event, such as an order confirmation or a password reset. You create a send definition once, referencing an email and a sending classification, then post individual messages against it with a message key you supply.
It is also the cleanest place to explain the two API patterns.
| Synchronous | Asynchronous | |
|---|---|---|
| Response | Carries the result you asked for | Carries an acknowledgement and an identifier |
| Examples | SOAP Retrieve, REST read of a data extension row | Event API journey entry, transactional send, async row inserts |
| Caller must | Handle data and error in the same call | Store the identifier and check status separately |
| Risk | Timeouts when the work is large | Assuming accepted means delivered |
The trap is treating a 201 or 202 as proof of delivery. An accepted event means the platform has queued it. Delivery status comes from a later status call or from tracking data.
How do you trigger transactional emails via API?
The older route, still widely used, is a Triggered Send. Create a Triggered Send Definition in Email Studio, point it at the email and the sending data extension, start it, then call the SOAP Create operation on the TriggeredSend object with the subscriber and the attributes the email needs.
The newer route is the Transactional Messaging API, which gives per-message status and does not need a definition to be started by hand. Prefer REST for new builds. For an existing estate of running definitions, know how to keep them alive, because a paused triggered send queues messages rather than failing loudly.
How do you retrieve subscriber data using API?
For subscriber records, use the SOAP Retrieve operation on the Subscriber object filtered on SubscriberKey, or the Contacts REST endpoints for contact-level data. For rows in a data extension, use the REST row set endpoint for that data extension key with a filter, or a SOAP Retrieve on the DataExtensionObject.
Large retrieves are paged. With SOAP you keep the request ID returned and continue the request until the platform reports no more data. Saying you would pull everything in one call is a red flag.
How do you handle API retries?
Retry only what is safe to retry. A 5xx or a timeout is retryable, a 400 is not, because the same bad payload fails again. Use exponential backoff with jitter, cap the attempts, and push anything that exhausts them onto a dead letter queue with the payload and the last response.
Rate limiting belongs in the same answer. Marketing Cloud enforces limits per tenant and per endpoint family, and exceeding them returns HTTP 429, so honour any retry header, keep concurrency low, batch writes and cache tokens. A loop that fetches a fresh token for every message hits the limit before the messages do.
How do you implement idempotency in event calls?
Give every business event a unique key generated at the source, such as the order ID plus the event type, carry it in the payload, store processed keys and drop a call whose key has already been handled.
Add a second layer inside Marketing Cloud. Supply your own message key for transactional sends so a repeated call refers to the same message, and set journey entry rules so a contact cannot enter twice for the same event. Network retries duplicate calls routinely, so the design has to assume it.
SFCC and MuleSoft integration questions
These come up as architecture context rather than daily work. Keep the answers short.
What are integration options between SFCC and SFMC?
Three routes in practice. The packaged Marketing Cloud cartridge for standard commerce events and data extensions, direct API calls from B2C Commerce to the REST endpoints, and file based batch over SFTP for bulk catalogue or order history. Most retail programmes use the cartridge for real time events and SFTP for nightly volume.
Difference between OCAPI and SCAPI?
OCAPI is the older Open Commerce API, organised around shop and data resources. SCAPI is the newer Salesforce Commerce API, built as separate domain APIs with modern authentication and better scaling. New integrations are expected to use SCAPI unless there is a specific gap.
How do you design abandoned cart journey architecture?
Commerce posts a cart event with a cart ID, a contact key and the line items. The event enters an API Event journey with a wait, and a decision split checks whether an order exists for that cart before the message goes out. The email reads line items from a cart data extension with a RowCount() guard. The order check before send is the part most candidates forget.
What is MuleSoft role in integration?
MuleSoft sits between the systems as the integration layer. It holds credentials, transforms payloads, handles retries and queuing, and gives one place to monitor traffic. Its API-led pattern separates system APIs that expose raw data from process APIs that hold business logic, so the same commerce API serves a marketing journey and a service console.
How do you implement retry and dead-letter queue?
Retry transient failures with backoff inside the integration layer, then route anything that exhausts its attempts to a dead letter queue holding the original payload, the error and a correlation ID. Alert on queue depth, and make replay deliberate once the cause is fixed.
What the interviewer is actually checking on APIs
- Whether you can name the installed package, the token endpoint and the scopes, or only say "we used the API"
- Whether you know REST and SOAP share one token and differ by capability, not by generation
- Whether you treat an accepted response as queued work rather than as delivery
- Whether your retry story includes a dead letter queue and idempotency keys, not just a loop
- Whether you can point at one integration you built and describe its payload
That last point is where most interviews end. Our trainers hear the same exchange every hiring season. "Do you know integrations?" "Yes." "Where have you used it?" Silence. Specificity creates credibility, so prepare one integration end to end, the trigger, the endpoint, the payload, the failure you hit and how you fixed it.
Preparing for this round
AMPscript and the APIs are where a Marketing Cloud interview stops being a conversation and becomes a test. Write the code by hand before you sit the round. Build a small journey with an API Event entry source, fire it with a token you generated yourself, and break it on purpose so you know what the error looks like.
For structured practice on a real tenant with a trainer reviewing your code, our Salesforce Marketing Cloud training covers AMPscript, SQL, Journey Builder and the API layer with project work you can defend in an interview.