> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/connectors/email-provider/salesforce-marketing-cloud.md).

# Salesforce Marketing Cloud

Configure The Wallet Crew to send transactional emails through Salesforce Marketing Cloud.

Use this connector to have The Wallet Crew send **transactional emails** through **Salesforce Marketing Cloud**, while Salesforce Marketing Cloud remains the place where the brand manages email assets, tracking, and reporting.

This integration uses the **script email provider**. The Wallet Crew triggers the send. The tenant script forwards the event to Salesforce Marketing Cloud.

## How it works

The Wallet Crew calls the tenant implementation of `runtime.scriptable.emailEngine.SendEmail`. That script then calls the Salesforce Marketing Cloud REST API to fire an event (using an `EventDefinitionKey`). Salesforce Marketing Cloud uses that event payload to render and send the email.

This model avoids duplicating templates. Salesforce Marketing Cloud owns the template. The Wallet Crew only sends the variables Salesforce Marketing Cloud needs, like the pass download URL and localized CTA label.

When the script provider is not set up yet, start with [EmailSender extensibility](/connectors/custom-connector/emailsender-extensibility.md).

## Before starting

Three things are required:

* A Salesforce Marketing Cloud **Installed Package** that can call REST APIs (client id + client secret).
* A Salesforce Marketing Cloud asset that can be **triggered by API**, exposing an `EventDefinitionKey`.
* A stable identifier in The Wallet Crew email payload (example: `id.customerId`) to generate the pass URL.

## Setup required in Salesforce Marketing Cloud

In Salesforce Marketing Cloud, create an API-triggered entry point and the associated email content.

1. Create an **Installed Package** and keep the **Client Id** and **Client Secret**.
2. Create the send asset to trigger (typically a journey entry event or an API event), then copy its `EventDefinitionKey`.
3. Make sure the Salesforce Marketing Cloud email template expects the same variable names sent in `Data`.

{% hint style="info" %}
Salesforce Marketing Cloud setup varies by account configuration and feature set. Keep the payload contract stable: the field names in `Data` should match what the Salesforce Marketing Cloud asset expects.
{% endhint %}

## Enable Salesforce Marketing Cloud in The Wallet Crew

Set the provider to `script` so The Wallet Crew calls the tenant `SendEmail` implementation.

{% code title="/server/emails.yml" %}

```yaml
provider:
  type: script
resources:
  - /locales/emails/
```

{% endcode %}

### Script example (trigger a Salesforce Marketing Cloud event)

This example triggers a Salesforce Marketing Cloud event using the REST endpoint `interaction/v1/events`. It sends a localized title, a localized CTA label, and a The Wallet Crew pass URL built from a customer identifier.

{% hint style="warning" %}
This implementation delegates rendering to Salesforce Marketing Cloud. The Wallet Crew `buildEmail()` callback is intentionally not used.
{% endhint %}

{% code title="Salesforce Marketing Cloud example (SendEmail implementation)" %}

```javascript
const CUSTOM_DOMAIN = "wallet.brand.com";
const TENANTID = "brand";
const SMFC_DOMAIN = "xxx";

/**
 * @typedef {Object} EmailData
 * @property {string} Subject - Rendered subject line.
 * @property {string} Body - Rendered body content.
 */

/**
 * Send an email using the provided template name and data.
 * The caller supplies the supported cultures and a callback
 * to build the rendered email content.
 *
 * @param {string} recipient - Target email address.
 * @param {string} emailTemplate - Template name to render.
 * @param {Object.<string, any>} data - Template data passed to the script.
 * @param {string[]} cultures - Available culture names from the culture provider.
 * @param {(templateName: string) => Promise<EmailData>} buildEmail
 * Callback that returns the final rendered email content.
 *
 * @returns {Promise<void>}
 */
async function sendEmail(recipient, emailTemplate, data, cultures, buildEmail){

  const customerId = data["id.customerId"];
  const url = `https://${CUSTOM_DOMAIN}/${TENANTID}/pass?id.customerId=${customerId}`;  

  const locales = {
    "en": {
      title: "🎉 Your Account is Ready – Add Your Pass to Wallet!",
      label: "Add to Wallet"
    },
    "fr": {
      title: "🎉 Votre compte est prêt – Ajoutez votre pass au Wallet !",
      label: "Ajouter au Wallet"
    }
  };

  const locale = locales[cultures[0]];

  const response = await fetch(`https://${SMFC_DOMAIN}.rest.marketingcloudapis.com/interaction/v1/events`,  {
    Method : "POST", 
    Authentication : {
        mode : 'OAuth2.0', 
        grantType : 'clientCredentials',
        accessTokenUrl : `https://${SMFC_DOMAIN}.auth.marketingcloudapis.com/v2/token`, 
        sendCredentialsAsFormParams: true,
        clientId: await getSecret('SFMC-CLIENTID'), 
        clientSecret : await getSecret('SFMC-CLIENTSECRET')
    }, 
    Headers: {
      "Content-Type": "application/json",
    },
    Body: {
      "ContactKey": recipient,
      "EventDefinitionKey": "confirmationCompteNeostore",
      "Data": {
          "SubscriberKey": recipient,
          "EmailAddress": recipient,
          "Title": locale.title,
          "Url": url,
          "LabelCTA": locale.label,
      }
    }
  });
  const responseData = JSON.parse(response.ResponseText); 
  if(!responseData?.eventInstanceId){
    throw new Error(`error while sending custom email status : ${response.StatusCode} - content : ${response.ResponseText}`)
  }
}

export default function(context) {
  context.register('runtime.scriptable.emailEngine', {
    SendEmail: sendEmail 
  })
}
```

{% endcode %}

### What to validate

Trigger a real transactional email, then validate end-to-end:

* The Wallet Crew calls the script without errors.
* The Salesforce Marketing Cloud API call returns an `eventInstanceId`.
* Salesforce Marketing Cloud renders the email with `Title`, `Url`, and `LabelCTA`.
* The CTA opens The Wallet Crew URL and the pass can be installed.

## Troubleshooting

If sends fail, isolate the problem in this order:

* **OAuth fails (401/403)**: client id/secret is wrong, revoked, or the installed package lacks API access.
* **No `eventInstanceId`**: the `EventDefinitionKey` is invalid, unpublished, or the payload schema is rejected.
* **Empty variables in the email**: the Salesforce Marketing Cloud template expects different field names than those sent in `Data`.
* **Wrong language**: `cultures[0]` does not match the locale map. Add a fallback (example: default to `en`).

## FAQ

<details>

<summary><strong>Does Salesforce Marketing Cloud or The Wallet Crew own the email HTML?</strong></summary>

Salesforce Marketing Cloud owns the HTML in this model. The Wallet Crew sends only variables (title, CTA label, URL) so the marketing team can iterate on the template without shipping The Wallet Crew changes.

</details>

<details>

<summary><strong>Can The Wallet Crew templates and <code>buildEmail()</code> still be used?</strong></summary>

Yes, but it becomes a different strategy. In that model, The Wallet Crew renders `Subject` and `Body`, and Salesforce Marketing Cloud is only used as a delivery gateway. In that case, align the Salesforce Marketing Cloud asset to accept rendered HTML and avoid double-templating.

</details>

<details>

<summary><strong>Where should the SFMC credentials be stored?</strong></summary>

Store them as tenant secrets and load them at runtime (as in the example with `getSecret('SFMC-CLIENTID')` and `getSecret('SFMC-CLIENTSECRET')`). Do not hardcode credentials in scripts.

</details>

<details>

<summary><strong>What should be used as <code>ContactKey</code>?</strong></summary>

Use a stable Salesforce Marketing Cloud contact identifier. Many brands use the email address, but a CRM id also works when it is the Salesforce Marketing Cloud contact key strategy. Keep it consistent with how the journey or asset resolves recipients.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.thewalletcrew.io/connectors/email-provider/salesforce-marketing-cloud.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
