Module 1 — AMPscript fundamentals

AMPscript is SFMC's proprietary scripting language for email, SMS, and Cloud Pages. It runs server-side at send time, letting you personalise content dynamically for each subscriber. It's not a general-purpose language — it's purpose-built for SFMC personalisation.

AMPscript syntax basics

AMPscript is wrapped in %%[ ]%% blocks (for logic) or %%variable%% (for output). It's case-insensitive and whitespace-tolerant.

AMPscript Variables and output
%%[ /* Declare and set a variable */ ]%%

%%[
  VAR @firstName, @greeting
  SET @firstName = AttributeValue("FirstName")

  IF Empty(@firstName) THEN
    SET @firstName = "there"
  ENDIF

  SET @greeting = Concat("Hi ", @firstName, "!")
]%%

<!-- Output the variable -->
<p>%%=v(@greeting)=%%</p>
📘 Output syntax

Use %%=v(@variable)=%% to output a variable inline. Use %%[SET @x = ...]%% for logic. The v() function means "value of" — it's how you print variables into HTML.

Reading subscriber data with AttributeValue

AMPscript Reading subscriber attributes
%%[
  /* AttributeValue reads from the sending Data Extension */
  VAR @email, @firstName, @country, @loyaltyTier

  SET @email       = AttributeValue("EmailAddress")
  SET @firstName  = AttributeValue("FirstName")
  SET @country    = AttributeValue("Country")
  SET @loyaltyTier = AttributeValue("LoyaltyTier")
]%%

<p>Hello %%=v(@firstName)=%%, your loyalty tier is %%=v(@loyaltyTier)=%%.</p>

Module 2 — AMPscript for dynamic content

AMPscript shines when you need to look up data from a second table, loop through arrays, or build conditional content blocks that go beyond what Content Builder's rules engine can do.

Lookup — fetching data from another DE

AMPscript Lookup() — single row lookup
%%[
  /* Look up a single value from another DE */
  /* Lookup(DE_name, return_field, match_field, match_value) */

  VAR @subKey, @tier, @discountCode
  SET @subKey = AttributeValue("SubscriberKey")

  /* Fetch loyalty tier from a separate DE */
  SET @tier = Lookup("LoyaltyDE", "Tier", "SubscriberKey", @subKey)

  /* Lookup a discount code based on the tier */
  SET @discountCode = Lookup("DiscountCodesDE", "Code", "Tier", @tier)
]%%

<p>Your exclusive code: <strong>%%=v(@discountCode)=%%</strong></p>

LookupRows — returning multiple rows

AMPscript LookupRows() + FOR loop — product recommendations
%%[
  VAR @subKey, @rows, @row, @rowCount, @i
  VAR @productName, @productPrice, @productURL

  SET @subKey   = AttributeValue("SubscriberKey")
  SET @rows     = LookupRows("RecommendationsDE", "SubscriberKey", @subKey)
  SET @rowCount = RowCount(@rows)
]%%

%%[ IF @rowCount > 0 THEN ]%%
  <h2>Your recommendations</h2>
  <table>

  %%[
    FOR @i = 1 TO @rowCount DO
      SET @row          = Row(@rows, @i)
      SET @productName  = Field(@row, "ProductName")
      SET @productPrice = Field(@row, "Price")
      SET @productURL   = Field(@row, "URL")
  ]%%

    <tr>
      <td><a href="%%=v(@productURL)=%%">%%=v(@productName)=%%</a></td>
      <td>£%%=v(@productPrice)=%%</td>
    </tr>

  %%[ NEXT @i ]%%

  </table>
%%[ ELSE ]%%
  <p>Check out our latest collection below.</p>
%%[ ENDIF ]%%

Date functions

AMPscript Date formatting and calculations
%%[
  VAR @today, @expiryDate, @daysLeft, @formattedExpiry

  SET @today          = Now()
  SET @expiryDate     = AttributeValue("OfferExpiryDate")
  SET @daysLeft       = DateDiff(@today, @expiryDate, "D")
  SET @formattedExpiry = FormatDate(@expiryDate, "MMMM d, yyyy")
]%%

<p>Your offer expires on <strong>%%=v(@formattedExpiry)=%%</strong>
— that's only %%=v(@daysLeft)=%% days away!</p>

See the full AMPscript guide for a complete function reference.

Module 3 — Server-Side JavaScript (SSJS)

SSJS is JavaScript that executes server-side in SFMC. Unlike AMPscript, it can make HTTP requests, work with complex data structures, and interact with the SFMC platform API directly from within an email or Cloud Page.

📘 SSJS vs AMPscript

Use AMPscript for email personalisation — it's faster and purpose-built for DE lookups and content rendering. Use SSJS for Cloud Pages, complex logic, external HTTP calls, or when you need JavaScript's data structures and methods.

SSJS basics

SSJS SSJS syntax and Platform object
<script runat="server">
  Platform.Load("Core", "1");

  // Read a subscriber attribute
  var firstName = Platform.Variable.GetValue("@firstName");

  // Write to a variable (usable by AMPscript)
  Platform.Variable.SetValue("@ssjs_output", "Hello from SSJS!");

  // Write to the page output
  Write("<p>Processed by SSJS</p>");
</script>

Writing to a Data Extension from SSJS

SSJS Upsert a record into a Data Extension
<script runat="server">
  Platform.Load("Core", "1");

  var de = DataExtension.Init("EventRegistrations");

  // Upsert: update if key exists, insert if not
  var result = de.Rows.Add({
    SubscriberKey: "12345",
    EmailAddress:  "kwame@example.com",
    EventName:     "SFMC Summit 2026",
    RegistrationDate: new Date().toISOString()
  });

  if (result) {
    Write("Registration saved successfully.");
  } else {
    Write("Error saving registration.");
  }
</script>

HTTP call from SSJS (Cloud Page)

SSJS HTTP GET request to external API
<script runat="server">
  Platform.Load("Core", "1");

  var url     = "https://api.example.com/products?category=shoes";
  var headers = { "Authorization": "Bearer YOUR_TOKEN" };

  var req  = new Script.Util.HttpRequest(url);
  req.emptyContentHandling = 0;
  req.retryCount           = 2;
  req.setHeader("Authorization", "Bearer YOUR_TOKEN");
  req.method               = "GET";

  var resp    = req.send();
  var content = String(resp.content);
  var data    = Platform.Function.ParseJSON(content);

  if (data && data.products) {
    for (var i = 0; i < data.products.length; i++) {
      Write("<p>" + data.products[i].name + "</p>");
    }
  }
</script>

Module 4 — SQL deep dive

SQL in SFMC uses T-SQL and is executed in Automation Studio query activities. It's the most powerful segmentation and data manipulation tool in SFMC. See the full SQL reference guide for a comprehensive library of queries.

Working with System Data Views

SQL Engagement scoring query
-- Score subscribers by engagement over 90 days
SELECT
    s.SubscriberKey,
    s.EmailAddress,
    COUNT(DISTINCT o.JobID)  AS TotalOpens,
    COUNT(DISTINCT c.JobID)  AS TotalClicks,
    CASE
        WHEN COUNT(DISTINCT c.JobID) >= 3  THEN 'Highly Engaged'
        WHEN COUNT(DISTINCT o.JobID) >= 2  THEN 'Engaged'
        WHEN COUNT(DISTINCT o.JobID) =  1  THEN 'Low Engagement'
        ELSE 'Unengaged'
    END AS EngagementTier
FROM [MasterSubscriberDE] s
LEFT JOIN [_Open] o
    ON s.SubscriberKey = o.SubscriberKey
    AND o.EventDate >= DATEADD(day, -90, GETDATE())
LEFT JOIN [_Click] c
    ON s.SubscriberKey = c.SubscriberKey
    AND c.EventDate >= DATEADD(day, -90, GETDATE())
WHERE s.OptInStatus = 'Subscribed'
GROUP BY s.SubscriberKey, s.EmailAddress

Module 5 — REST API

The SFMC REST API lets you interact with SFMC programmatically — creating contacts, triggering sends, managing journeys, and more. It's the modern API for most SFMC integrations.

Authentication — getting an access token

JavaScript OAuth 2.0 — get access token
const response = await fetch(
  "https://YOUR_SUBDOMAIN.auth.marketingcloudapis.com/v2/token",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type:    "client_credentials",
      client_id:     "YOUR_CLIENT_ID",
      client_secret: "YOUR_CLIENT_SECRET",
      account_id:    "YOUR_MID"  // Optional: target BU
    })
  }
);

const { access_token, rest_instance_url } = await response.json();
// Store these — tokens expire after ~20 minutes

Triggering a transactional send via REST

JavaScript REST API — trigger a transactional email
async function sendTransactionalEmail(accessToken, restUrl, recipient) {
  const payload = {
    definitionKey: "welcome-email-trigger",  // Triggered Send key
    recipients: [
      {
        contactKey: recipient.subscriberKey,
        to:         recipient.emailAddress,
        attributes: {
          FirstName:   recipient.firstName,
          ProductName: recipient.productName
        }
      }
    ]
  };

  const res = await fetch(
    `${restUrl}messaging/v1/email/messages/`,
    {
      method:  "POST",
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type":  "application/json"
      },
      body: JSON.stringify(payload)
    }
  );

  return res.json();
}

Upserting contacts via REST

JavaScript REST API — upsert contact into a Data Extension
async function upsertContact(accessToken, restUrl, contactData) {
  const payload = {
    items: [
      {
        keys:   { SubscriberKey: contactData.subscriberKey },
        values: {
          EmailAddress: contactData.email,
          FirstName:    contactData.firstName,
          LastName:     contactData.lastName,
          Country:      contactData.country,
          CreatedDate:  new Date().toISOString()
        }
      }
    ]
  };

  const res = await fetch(
    `${restUrl}hub/v1/dataevents/key:YourDE_ExternalKey/rowset`,
    {
      method:  "POST",
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type":  "application/json"
      },
      body: JSON.stringify(payload)
    }
  );

  return res.json();
}

Module 6 — SOAP API

The SOAP API is SFMC's older API — it's more verbose than REST but covers some operations that REST doesn't yet support (particularly around email send management and subscriber data).

📘 REST vs SOAP

Use REST for new integrations wherever possible — it's simpler and more modern. Use SOAP when you need operations not available in REST, such as complex subscriber management, send monitoring, or working with older SFMC components.

SOAP request structure

XML SOAP — retrieve subscriber data
<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
             xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
             xmlns:u="http://docs.oasis-open.org/wss/...">
  <s:Header>
    <fueloauth xmlns="http://exacttarget.com">YOUR_ACCESS_TOKEN</fueloauth>
  </s:Header>
  <s:Body>
    <RetrieveRequestMsg xmlns="http://exacttarget.com/wsdl/partnerAPI">
      <RetrieveRequest>
        <ObjectType>Subscriber</ObjectType>
        <Properties>SubscriberKey</Properties>
        <Properties>EmailAddress</Properties>
        <Properties>Status</Properties>
        <Filter xsi:type="SimpleFilterPart">
          <Property>SubscriberKey</Property>
          <SimpleOperator>equals</SimpleOperator>
          <Value>customer_12345</Value>
        </Filter>
      </RetrieveRequest>
    </RetrieveRequestMsg>
  </s:Body>
</s:Envelope>

Module 7 — Cloud Pages & microsites

Cloud Pages are web pages hosted by SFMC. You can build landing pages, preference centres, unsubscribe pages, registration forms, and full microsites — all with access to SFMC data via AMPscript or SSJS.

Cloud Page types

TypeCommon use
Landing pagePost-click pages from email campaigns
MicrositeMulti-page web experiences
Smart capture formData capture forms that write directly to a DE
Reference pageShareable content referenced in emails (e.g. view in browser)

Form that writes to a Data Extension

AMPscript + HTML Cloud Page — form submission to Data Extension
%%[
  VAR @submitted, @email, @firstName, @result

  SET @submitted = RequestParameter("submitted")

  IF @submitted == "true" THEN
    SET @email     = RequestParameter("email")
    SET @firstName = RequestParameter("firstname")

    /* Upsert to Data Extension */
    SET @result = UpsertDE(
      "EventRegistrations",
      1,
      "EmailAddress", @email,
      "FirstName",    @firstName,
      "RegistrationDate", NOW()
    )
  ENDIF
]%%

<!DOCTYPE html>
<html><body>

%%[ IF @submitted == "true" THEN ]%%
  <h1>You're registered!</h1>
  <p>We'll be in touch, %%=v(@firstName)=%%.</p>
%%[ ELSE ]%%
  <form method="post">
    <input type="hidden" name="submitted" value="true" />
    <input type="text"   name="firstname" placeholder="First name" />
    <input type="email"  name="email"     placeholder="Email address" />
    <button type="submit">Register</button>
  </form>
%%[ ENDIF ]%%

</body></html>

Module 8 — Custom Journey Builder activities

Custom activities extend Journey Builder with your own logic. You build a small web app that SFMC calls at each execution, enabling you to integrate any external system directly into a journey.

Custom activity flow

A custom activity is a web app hosted externally (or on a Cloud Page) that implements three endpoints SFMC calls at different stages:

EndpointWhen calledPurpose
/config.jsonOn load in JB canvasDefines the activity's UI, name, and argument schema
/execute (POST)When a contact reaches the activityYour business logic — update CRM, fire webhook, etc.
/publish (POST)On journey activationOptional validation before go-live

Execute endpoint example

JavaScript (Node.js) Custom activity execute endpoint
// POST /execute — called for each contact
app.post("/execute", async (req, res) => {
  const { inArguments, keyValue } = req.body;

  // inArguments contains journey data for this contact
  const subscriberKey = keyValue;
  const email         = inArguments[0]?.emailAddress;
  const eventType     = inArguments[0]?.eventType;

  try {
    // Your logic here — e.g. update Salesforce CRM
    await updateCRM({ subscriberKey, email, eventType });

    // Must return 200 for JB to continue the journey
    res.status(200).json({ status: "ok" });
  } catch (err) {
    console.error(err);
    // Return 200 even on failure to avoid blocking the journey
    res.status(200).json({ status: "error", message: err.message });
  }
});

Module 9 — Advanced data modelling

At the advanced level, your data model in SFMC should be a deliberate architectural decision, not an accident. A well-designed model is the difference between an SFMC instance that scales gracefully and one that becomes a maintenance nightmare.

Core data model principles

Contact Builder relationships

Contact Builder lets you define relationships between your DEs — like a visual ER diagram for SFMC. Link your DEs to the Contact record via Subscriber Key. This powers:

Module 10 — Performance & debugging

Debugging SFMC is a skill in itself. Here are the most common issues and how to diagnose them.

AMPscript debugging

AMPscript Debug output — check variable values
%%[
  VAR @debugMode
  /* Set to 1 in test, 0 in production */
  SET @debugMode = 1
]%%

%%[ IF @debugMode == 1 THEN ]%%
  <div style="background:#fffbe6;padding:10px;font-family:monospace;font-size:12px;">
    <strong>DEBUG:</strong><br/>
    SubscriberKey: %%=v(AttributeValue("SubscriberKey"))=%%<br/>
    EmailAddress: %%=v(AttributeValue("EmailAddress"))=%%<br/>
    Country: %%=v(AttributeValue("Country"))=%%<br/>
    LoyaltyTier: %%=v(AttributeValue("LoyaltyTier"))=%%
  </div>
%%[ ENDIF ]%%

Common issues and fixes

IssueLikely causeFix
AMPscript outputs blankField name mismatch (case sensitive in some contexts)Check DE field name exactly matches AttributeValue() call
Lookup returns nothingNo matching row, or wrong DE nameAdd RaiseError() to surface issues; verify key values
SQL query produces no rowsDate range too narrow, wrong join fieldRun manually in Automation Studio with broader dates first
Journey not picking up contactsEntry source DE not refreshed or wrong filterCheck automation is running before journey entry window
REST API 401 errorAccess token expired (20 min TTL)Implement token refresh logic; cache with expiry check
Emails going to spamIP not warmed, authentication missingCheck SAP setup, DMARC alignment, list hygiene
Advanced path complete ⚙️
You now have the full technical toolkit for SFMC. The Developer certification is well within reach — and so is building anything the platform can do.
Certification prep → AMPscript deep dive