> 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/adobe-marketing-cloud/adobe-journey-optimizer.md).

# Adobe Journey Optimizer

Send The Wallet Crew transactional emails via Adobe Journey Optimizer (AJO) using the script email provider.

Use this option when **Adobe Journey Optimizer (AJO)** is available and **API-triggered sends** fit the journey orchestration model in place.

Before going further, make sure the script provider model used by The Wallet Crew is clear. Start with [Adobe Marketing Cloud](/connectors/email-provider/adobe-marketing-cloud.md) and the prerequisite [EmailSender extensibility](/connectors/custom-connector/emailsender-extensibility.md).

### When to use this

AJO is typically the choice when near real-time sends are needed and the marketing team already operates journeys in AJO.

### Setup required in Adobe

Create an API-triggered journey or campaign that can accept an execution call. Then configure the email action.

At minimum, agree on what the script sends to AJO.

* **Fully rendered HTML** from The Wallet Crew (`Subject`, `Body`).
* Or **context variables only**, with the template managed in AJO.

{% hint style="warning" %}
Pick one rendering strategy. Avoid duplicating templating in both The Wallet Crew and Adobe.
{% endhint %}

### Implementation notes (OAuth + trigger)

AJO uses Adobe IMS for OAuth2 (client credentials). The script typically requests an access token, then calls the AJO execution endpoint for the target campaign or journey.

{% hint style="info" %}
Adobe endpoints and payload shapes depend on the organization setup, region, and the AJO feature in use (journey vs campaign). Treat the snippet below as a skeleton, and align it with the official Adobe documentation for the relevant product version.
{% endhint %}

Store these values as secrets before testing:

* `adobe-clientId`
* `adobe-clientSecret`
* `adobe-orgId`
* `adobe-sandboxName`
* `adobe-ajo-triggerUrl` (full AJO trigger URL)
* `adobe-scope` (optional, depends on the Adobe setup)

{% code title="Adobe Journey Optimizer example (SendEmail implementation)" %}

```javascript
import { getSecret } from "neo/secrets";

const CUSTOM_DOMAIN = "wallet.brand.com";
const TENANTID = "brand";

// Adobe IMS (OAuth2 client credentials)
// Your runtime provides a custom `fetch` that can perform OAuth client credentials flows.
const IMS_TOKEN_URL = "https://ims-na1.adobelogin.com/ims/token/v3";

/**
 * @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 email = recipient;
  const countryCode = data["country"] || "fr";
  const language = `${cultures[0]?.toLowerCase()?.substring(0, 2) || "fr"}_${countryCode.toLowerCase()}`;
  const customerId = data["id.customerId"];

  if (!customerId) {
    throw new Error('Missing "id.customerId" in email data.');
  }

  // URL the customer should click to download their pass.
  // You can change the path to match your distribution layout.
  const url = `https://${CUSTOM_DOMAIN}/${TENANTID}/pass?id.customerId=${encodeURIComponent(customerId)}`;

  // Optional: keep The Wallet Crew rendering available.
  // If your AJO template is fully managed in Adobe, you can remove this.
  const rendered = await buildEmail(emailTemplate);

  // AJO config must be provided as secrets.
  // Use a full URL to avoid hardcoding product-specific paths in this script.
  // Example (varies by setup): "https://platform.adobe.io/journey/execute/<CAMPAIGN_OR_JOURNEY_ID>"
  const ajoTriggerUrl = await getSecret("adobe-ajo-triggerUrl");
  const orgId = await getSecret("adobe-orgId");
  const sandboxName = await getSecret("adobe-sandboxName");
  const clientId = await getSecret("adobe-clientId");
  const clientSecret = await getSecret("adobe-clientSecret");

  // Keep scope configurable. Different Adobe setups require different scopes.
  // Example values seen in the wild: "openid,AdobeID"
  let scope = "openid,AdobeID";
  try {
    scope = (await getSecret("adobe-scope")) || scope;
  } catch (e) {
    // Optional secret. Keep the default scope.
  }

  // Your runtime `fetch` handles OAuth client credentials and attaches the token.
  // This avoids manually calling IMS then forwarding the Bearer token.
  const result = await fetch(ajoTriggerUrl, {
    Method: "POST",
    Authentication: {
      mode: "OAuth2.0",
      grantType: "clientCredentials",
      sendCredentialsAsFormParams: true,
      accessTokenUrl: IMS_TOKEN_URL,
      clientId,
      clientSecret,
      scope,
    },
    Headers: {
      "x-api-key": clientId,
      "x-gw-ims-org-id": orgId,
      "x-sandbox-name": sandboxName,
      "Content-Type": "application/json",
    },
    Body: JSON.stringify({
      // The exact payload shape must match your AJO API-triggered campaign/journey configuration.
      // Keep this contract stable between your script and your AJO asset.
      recipient: {
        email,
        language,
      },
      context: {
        // Minimal variable for an Adobe-managed template:
        url,

        // Optional variables if you want to reuse The Wallet Crew rendering inside Adobe:
        subject: rendered.Subject,
        body: rendered.Body,

        // Useful metadata for routing / debugging:
        template: emailTemplate,
        customerId,
      },
    }),
    ThrowOnError: false,
  });

  if (result.StatusCode < 200 || result.StatusCode >= 300) {
    throw new Error(`AJO trigger call failed (${result.StatusCode}): ${result.ResponseText}`);
  }
}

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

{% endcode %}

### What to validate

Trigger a real transactional email, then validate these points:

* AJO receives the execution call and accepts it.
* The AJO email action has access to the context fields that were sent.
* When HTML is passed, the final email renders correctly in common clients.

### FAQ

<details>

<summary><strong>Is an “API-triggered journey” required in AJO?</strong></summary>

Yes. The script needs a published AJO asset that can be triggered by an API call. The exact asset type (journey vs campaign) depends on what the Adobe organization uses.

</details>

<details>

<summary><strong>Can AJO send an email built by The Wallet Crew?</strong></summary>

Yes. A common pattern is to call `buildEmail()` in the script, then pass `subject` and `body` as context to AJO. The AJO email action must be configured to use these fields.

</details>

<details>

<summary><strong>How can this be tested without emailing real customers?</strong></summary>

Use an AJO sandbox when one is available. Otherwise, restrict tests to internal addresses and create a dedicated “test” journey or campaign. Keep the payload contract identical to production to avoid last-minute schema breaks.

</details>

<details>

<summary><strong>Why are so many Adobe headers needed?</strong></summary>

AJO gateways commonly require org, sandbox, and API key headers for routing and authorization. The exact set can vary by Adobe setup and region, so treat the official Adobe documentation as the source of truth for the tenant.

</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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
