# Salesforce Marketing Cloud

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

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

## How it works

The Wallet Crew calls your implementation of `runtime.scriptable.emailEngine.SendEmail`. Your 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.

If you haven’t set up the script provider yet, start with [EmailSender extensibility](https://docs.thewalletcrew.io/connect/custom-connector/emailsender-extensibility).

## Before you start

You need three things:

* 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 your 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 you want to trigger (typically a journey entry event or an API event), then copy its `EventDefinitionKey`.
3. Make sure your Salesforce Marketing Cloud email template expects the same variable names you will send in `Data`.

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

## Enable Salesforce Marketing Cloud in The Wallet Crew

Set the provider to `script` so The Wallet Crew calls your `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 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 your 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 what you send in `Data`.
* **Wrong language**: `cultures[0]` does not match your 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 your marketing team can iterate on the template without shipping Wallet Crew changes.

</details>

<details>

<summary><strong>Can we still use Wallet Crew templates and <code>buildEmail()</code>?</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. If you want that, align the Salesforce Marketing Cloud asset to accept rendered HTML and avoid double-templating.

</details>

<details>

<summary><strong>Where do we store the SFMC credentials?</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 we use as <code>ContactKey</code>?</strong></summary>

Use a stable Salesforce Marketing Cloud contact identifier. Many Brands use the email address, but you can also use a CRM id if that is your Salesforce Marketing Cloud contact key strategy. Keep it consistent with how your Journey/asset resolves recipients.

</details>
