# Adobe Journey Optimizer

Use this option when you have **Adobe Journey Optimizer (AJO)** and you want **API-triggered sends** that fit modern journey orchestration.

Before you go further, make sure you understand how the script provider works in The Wallet Crew. Start with [Adobe Marketing Cloud](https://docs.thewalletcrew.io/connect/email-provider/adobe-marketing-cloud) and the prerequisite [EmailSender extensibility](https://docs.thewalletcrew.io/connect/custom-connector/emailsender-extensibility).

### When to use this

You typically pick AJO when you want near real-time sends and your 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 your 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). Your script typically requests an access token, then calls the AJO execution endpoint for your campaign or journey.

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

Store these values as secrets before you test:

* `adobe-clientId`
* `adobe-clientSecret`
* `adobe-orgId`
* `adobe-sandboxName`
* `adobe-ajo-triggerUrl` (full AJO trigger URL)
* `adobe-scope` (optional, depends on your 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 you send.
* If you pass HTML, the final email renders correctly in common clients.

### FAQ

<details>

<summary><strong>Do we need an “API-triggered journey” in AJO?</strong></summary>

Yes. Your script needs a published AJO asset that can be triggered by an API call. The exact asset type (journey vs campaign) depends on what your 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 your script, then pass `subject` and `body` as context to AJO. Your AJO email action must be configured to use these fields.

</details>

<details>

<summary><strong>How do we test without emailing real customers?</strong></summary>

Use an AJO sandbox if you have one. If you don’t, restrict tests to internal addresses and create a dedicated “test” journey/campaign. Keep the payload contract identical to production to avoid last-minute schema breaks.

</details>

<details>

<summary><strong>Why do we need so many Adobe headers?</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 Adobe’s official docs as the source of truth for your tenant.

</details>
