> ## Documentation Index
> Fetch the complete documentation index at: https://loops.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactional email API examples

> Code examples for creating transactional emails, updating drafts with content revisions, previewing, publishing, and sending via the Loops API, CLI, and SDKs.

## Create a transactional email

This creates a transactional email and a related draft email message in one request.

Only a `name` value is required.

<Tip>
  Save the returned `draftEmailMessageContentRevisionId`. Pass it as
  `expectedRevisionId` when updating the draft email message to avoid `409 Conflict` errors caused by stale revisions.
</Tip>

[API reference](/docs/api-reference/create-transactional-email)

There is no CLI command for creating transactional emails yet — use the API below.

<CodeGroup>
  ```js JavaScript theme={"dark"}
  const response = await fetch("https://app.loops.so/api/v1/transactional-emails", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Password reset",
    }),
  });

  const data = await response.json();
  const transactionalId = data.id;
  const draftEmailMessageId = data.draftEmailMessageId;
  const draftEmailMessageContentRevisionId =
    data.draftEmailMessageContentRevisionId;
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const data = await loops.createTransactionalEmail({
    name: "Password reset",
  });

  const transactionalId = data.id;
  const draftEmailMessageId = data.draftEmailMessageId;
  const draftEmailMessageContentRevisionId =
    data.draftEmailMessageContentRevisionId;
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $result = $loops->transactional->create(name: 'Password reset');

  $transactional_id = $result['id'];
  $draft_email_message_id = $result['draftEmailMessageId'];
  $draft_email_message_content_revision_id =
    $result['draftEmailMessageContentRevisionId'];
  ```

  ```ruby Ruby SDK theme={"dark"}
  response = LoopsSdk::Transactional.create(
    name: "Password reset",
  )

  transactional_id = response["id"]
  draft_email_message_id = response["draftEmailMessageId"]
  draft_email_message_content_revision_id =
    response["draftEmailMessageContentRevisionId"]
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.post(
      "https://app.loops.so/api/v1/transactional-emails",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "name": "Password reset",
      },
  )

  data = response.json()
  transactional_id = data["id"]
  draft_email_message_id = data["draftEmailMessageId"]
  draft_email_message_content_revision_id = data[
      "draftEmailMessageContentRevisionId"
  ]
  ```
</CodeGroup>

## Query themes and components for your LMX

You can fetch your available themes and reusable components before building
the `lmx` payload.

List themes [API reference](/docs/api-reference/list-themes) [CLI reference](/docs/cli/themes#list)\
List components [API reference](/docs/api-reference/list-components) [CLI reference](/docs/cli/components#list)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops themes list -o json
  loops components list -o json
  ```

  ```js JavaScript theme={"dark"}
  const [themesResponse, componentsResponse] = await Promise.all([
    fetch("https://app.loops.so/api/v1/themes?perPage=20", {
      method: "GET",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    }),
    fetch("https://app.loops.so/api/v1/components?perPage=20", {
      method: "GET",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    }),
  ]);

  const themes = await themesResponse.json();
  const components = await componentsResponse.json();
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const [themes, components] = await Promise.all([
    loops.listThemes({ perPage: 20 }),
    loops.listComponents({ perPage: 20 }),
  ]);
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $themes = $loops->themes->list(per_page: 20);
  $components = $loops->components->list(per_page: 20);
  ```

  ```ruby Ruby SDK theme={"dark"}
  themes = LoopsSdk::Themes.list(perPage: 20)
  components = LoopsSdk::Components.list(perPage: 20)
  ```

  ```python Python theme={"dark"}
  import requests

  themes_response = requests.get(
      "https://app.loops.so/api/v1/themes",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
      params={"perPage": 20},
  )

  components_response = requests.get(
      "https://app.loops.so/api/v1/components",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
      params={"perPage": 20},
  )

  themes = themes_response.json()
  components = components_response.json()
  ```
</CodeGroup>

## Update the draft email message with `contentRevisionId`

Use `draftEmailMessageId` from when you created the transactional as the path parameter, and pass `draftEmailMessageContentRevisionId` as `expectedRevisionId`.

Apply styles or a theme in `<Style />`, and build the email using LMX elements.

Themes and components you queried in the previous step can be referenced by their IDs.

<Tip>
  Save the returned `contentRevisionId` after each update. Pass it as
  `expectedRevisionId` on the next update to avoid `409 Conflict` errors caused
  by stale revisions.
</Tip>

[API reference](/docs/api-reference/update-email-message)\
[CLI reference](/docs/cli/email-messages#update)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops email-messages update $draftEmailMessageId \
    --expected-revision-id $draftEmailMessageContentRevisionId \
    --subject "Reset your password" \
    --preview-text "Your password reset link" \
    --from-name "Loops" \
    --from-email hello \
    --reply-to support@example.com \
    --lmx-file ./email.lmx
  ```

  ```js JavaScript theme={"dark"}
  const lmxContent = `
  <Style themeId="default" />
  <Paragraph>
    <Text>Click the link below to reset your password.</Text>
  </Paragraph>
  <Component componentId="logo" />
  <Section>
    ...
  </Section>`;

  const response = await fetch(
    `https://app.loops.so/api/v1/email-messages/${draftEmailMessageId}`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        expectedRevisionId: draftEmailMessageContentRevisionId,
        subject: "Reset your password",
        previewText: "Your password reset link",
        fromName: "Loops",
        fromEmail: "hello",
        replyToEmail: "support@example.com",
        lmx: lmxContent,
      }),
    },
  );

  const updated = await response.json();
  const nextContentRevisionId = updated.contentRevisionId;
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const lmxContent = `
  <Style themeId="default" />
  <Paragraph>
    <Text>Click the link below to reset your password.</Text>
  </Paragraph>
  <Component componentId="logo" />
  <Section>
    ...
  </Section>`;

  const updated = await loops.updateEmailMessage(draftEmailMessageId, {
    expectedRevisionId: draftEmailMessageContentRevisionId,
    subject: "Reset your password",
    previewText: "Your password reset link",
    fromName: "Loops",
    fromEmail: "hello",
    replyToEmail: "support@example.com",
    lmx: lmxContent,
  });

  const nextContentRevisionId = updated.contentRevisionId;
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $lmx_content = <<<LMX
  <Style themeId="default" />
  <Paragraph>
    <Text>Click the link below to reset your password.</Text>
  </Paragraph>
  <Component componentId="logo" />
  <Section>
    ...
  </Section>
  LMX;

  $updated = $loops->emailMessages->update(
    email_message_id: $draft_email_message_id,
    expected_revision_id: $draft_email_message_content_revision_id,
    subject: 'Reset your password',
    preview_text: 'Your password reset link',
    from_name: 'Loops',
    from_email: 'hello',
    reply_to_email: 'support@example.com',
    lmx: $lmx_content,
  );

  $next_content_revision_id = $updated['contentRevisionId'];
  ```

  ```ruby Ruby SDK theme={"dark"}
  lmx_content = <<~LMX
    <Style themeId="default" />
    <Paragraph>
      <Text>Click the link below to reset your password.</Text>
    </Paragraph>
    <Component componentId="logo" />
    <Section>
      ...
    </Section>
  LMX

  updated = LoopsSdk::EmailMessages.update(
    email_message_id: draft_email_message_id,
    expected_revision_id: draft_email_message_content_revision_id,
    subject: "Reset your password",
    preview_text: "Your password reset link",
    from_name: "Loops",
    from_email: "hello",
    reply_to_email: "support@example.com",
    lmx: lmx_content,
  )

  next_content_revision_id = updated["contentRevisionId"]
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.post(
      f"https://app.loops.so/api/v1/email-messages/{draft_email_message_id}",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "expectedRevisionId": draft_email_message_content_revision_id,
          "subject": "Reset your password",
          "previewText": "Your password reset link",
          "fromName": "Loops",
          "fromEmail": "hello",
          "replyToEmail": "support@example.com",
          "lmx": "<Email><Style backgroundColor=\"#ffffff\" textBaseColor=\"#111111\" /><Section><Text>Click the link below to reset your password.</Text></Section></Email>",
      },
  )

  updated = response.json()
  next_content_revision_id = updated["contentRevisionId"]
  ```
</CodeGroup>

## Send a transactional preview

After updating the draft email message, send a test preview to one or more
addresses before publishing. Transactional previews accept `dataVariables` for
personalization.

Use `draftEmailMessageId` from when you created the transactional as the path
parameter.

There is no CLI command for previews yet — use the API below.

[API reference](/docs/api-reference/preview-email-message)

<CodeGroup>
  ```js JavaScript theme={"dark"}
  const previewResponse = await fetch(
    `https://app.loops.so/api/v1/email-messages/${draftEmailMessageId}/preview`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        emails: ["you@example.com"],
        dataVariables: {
          loginUrl: "https://example.com/reset?token=abc123",
          firstName: "Alex",
        },
      }),
    },
  );

  const preview = await previewResponse.json();
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const preview = await loops.sendEmailMessagePreview(draftEmailMessageId, {
    emails: ["you@example.com"],
    dataVariables: {
      loginUrl: "https://example.com/reset?token=abc123",
      firstName: "Alex",
    },
  });
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $preview = $loops->emailMessages->preview(
    email_message_id: $draft_email_message_id,
    emails: ['you@example.com'],
    data_variables: [
      'loginUrl' => 'https://example.com/reset?token=abc123',
      'firstName' => 'Alex',
    ],
  );
  ```

  ```ruby Ruby SDK theme={"dark"}
  preview = LoopsSdk::EmailMessages.preview(
    email_message_id: draft_email_message_id,
    emails: ["you@example.com"],
    data_variables: {
      loginUrl: "https://example.com/reset?token=abc123",
      firstName: "Alex",
    },
  )
  ```

  ```python Python theme={"dark"}
  import requests

  preview_response = requests.post(
      f"https://app.loops.so/api/v1/email-messages/{draft_email_message_id}/preview",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "emails": ["you@example.com"],
          "dataVariables": {
              "loginUrl": "https://example.com/reset?token=abc123",
              "firstName": "Alex",
          },
      },
  )

  preview = preview_response.json()
  ```
</CodeGroup>

## Upload an image asset

If your LMX includes `<Image />` tags, upload image files with the Upload API
and use the returned `finalUrl` as the image `src`.

[Create upload API reference](/docs/api-reference/create-upload)\
[Complete upload API reference](/docs/api-reference/complete-upload)
[Upload CLI reference](/docs/cli/uploads#create)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops uploads create ./header.png -o json
  ```

  ```js JavaScript theme={"dark"}
  const createResponse = await fetch("https://app.loops.so/api/v1/uploads", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      contentType: "image/png",
      contentLength: imageBuffer.byteLength,
    }),
  });

  const { emailAssetId, presignedUrl } = await createResponse.json();

  await fetch(presignedUrl, {
    method: "PUT",
    headers: {
      "Content-Type": "image/png",
      "Content-Length": String(imageBuffer.byteLength),
    },
    body: imageBuffer,
  });

  const completeResponse = await fetch(
    `https://app.loops.so/api/v1/uploads/${emailAssetId}/complete`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    },
  );

  const { finalUrl } = await completeResponse.json();
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const { emailAssetId, presignedUrl } = await loops.createUpload({
    contentType: "image/png",
    contentLength: imageBuffer.byteLength,
  });

  await fetch(presignedUrl, {
    method: "PUT",
    headers: {
      "Content-Type": "image/png",
      "Content-Length": String(imageBuffer.byteLength),
    },
    body: imageBuffer,
  });

  const { finalUrl } = await loops.completeUpload(emailAssetId);
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $result = $loops->uploads->upload(path: './header.png');

  $final_url = $result['finalUrl'];
  ```

  ```ruby Ruby SDK theme={"dark"}
  response = LoopsSdk::Uploads.upload(path: "./header.png")

  final_url = response["finalUrl"]
  ```

  ```python Python theme={"dark"}
  import requests

  create_response = requests.post(
      "https://app.loops.so/api/v1/uploads",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "contentType": "image/png",
          "contentLength": len(image_bytes),
      },
  )

  create_data = create_response.json()
  email_asset_id = create_data["emailAssetId"]
  presigned_url = create_data["presignedUrl"]

  requests.put(
      presigned_url,
      headers={
          "Content-Type": "image/png",
          "Content-Length": str(len(image_bytes)),
      },
      data=image_bytes,
  )

  complete_response = requests.post(
      f"https://app.loops.so/api/v1/uploads/{email_asset_id}/complete",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  )

  final_url = complete_response.json()["finalUrl"]
  ```
</CodeGroup>

## Publish the transactional email

Publish the draft email message so it can be sent with the transactional send endpoint.

There is no CLI command for publishing transactional emails yet — use the API below.

[API reference](/docs/api-reference/publish-transactional-email)

<CodeGroup>
  ```js JavaScript theme={"dark"}
  const publishResponse = await fetch(
    `https://app.loops.so/api/v1/transactional-emails/${id}/publish`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    },
  );

  const published = await publishResponse.json();
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const published = await loops.publishTransactionalEmail(transactionalId);
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $published = $loops->transactional->publish(transactional_id: $transactional_id);
  ```

  ```ruby Ruby SDK theme={"dark"}
  published = LoopsSdk::Transactional.publish(
    transactional_id: transactional_id,
  )
  ```

  ```python Python theme={"dark"}
  import requests

  publish_response = requests.post(
      f"https://app.loops.so/api/v1/transactional-emails/{id}/publish",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  )

  published = publish_response.json()
  ```
</CodeGroup>

## Send a transactional email

Use the transactional `id` from create (or publish) as `transactionalId` in the send request.

[API reference](/docs/api-reference/send-transactional-email)\
[CLI reference](/docs/cli/transactional#send)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops transactional send <transactional-id> \
    --email test@example.com \
    --var loginUrl=https://example.com/login
  ```

  ```js JavaScript theme={"dark"}
  await fetch("https://app.loops.so/api/v1/transactional", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email: "test@example.com",
      transactionalId: id,
      dataVariables: {
        loginUrl: "https://example.com/login",
      },
    }),
  });
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const response = await loops.sendTransactionalEmail({
    email: "test@example.com",
    transactionalId: "<transactional-id>",
    dataVariables: {
      loginUrl: "https://example.com/login",
    },
  });
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient("<your-api-key>");

  $result = $loops->transactional->send(
    email: 'test@example.com',
    transactional_id: '<transactional-id>',
    data_variables: [
      'loginUrl' => 'https://example.com/login',
    ],
  );
  ```

  ```ruby Ruby SDK theme={"dark"}
  response = LoopsSdk::Transactional.send(
    email: "test@example.com",
    transactional_id: "<transactional-id>",
    data_variables: {
      loginUrl: "https://example.com/login",
    },
  )
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.post(
      "https://app.loops.so/api/v1/transactional",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "email": "test@example.com",
          "transactionalId": id,
          "dataVariables": {
              "loginUrl": "https://example.com/login",
          },
      },
  )
  ```
</CodeGroup>

## Send a transactional email with an array data variable

Learn more about [arrays](/docs/creating-emails/editor#arrays).

[API reference](/docs/api-reference/send-transactional-email#param-data-variables)\
[CLI reference](/docs/cli/transactional#send)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops transactional send <id> \
    --email test@example.com \
    --json-vars ./vars.json
  ```

  ```js JavaScript {11-14} theme={"dark"}
  await fetch("https://app.loops.so/api/v1/transactional", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email: "test@example.com",
      transactionalId: "<id>",
      dataVariables: {
        items: [
          { name: "Item 1", description: "Description of Item 1" },
          { name: "Item 2", description: "Description of Item 2" },
        ],
      },
    }),
  });
  ```

  ```js JavaScript SDK {9-12} theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const response = await loops.sendTransactionalEmail({
    email: "test@example.com",
    transactionalId: "<id>",
    dataVariables: {
      items: [
        { name: "Item 1", description: "Description of Item 1" },
        { name: "Item 2", description: "Description of Item 2" },
      ],
    },
  });
  ```

  ```php PHP SDK {9-18} theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient("<your-api-key>");

  $result = $loops->transactional->send(
    email: 'test@example.com',
    transactional_id: '<id>',
    data_variables: [
      'items' => [
        [
          'name' => 'Item 1',
          'description' => 'Description of Item 1',
        ],
        [
          'name' => 'Item 2',
          'description' => 'Description of Item 2',
        ],
      ],
    ],
  );
  ```

  ```ruby Ruby SDK {5-8} theme={"dark"}
  response = LoopsSdk::Transactional.send(
    email: "test@example.com",
    transactional_id: "<id>",
    data_variables: {
      items: [
        { name: "Item 1", description: "Description of Item 1" },
        { name: "Item 2", description: "Description of Item 2" },
      ],
    },
  )
  ```

  ```python Python {13-16} theme={"dark"}
  import requests

  response = requests.post(
    "https://app.loops.so/api/v1/transactional",
    headers={
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    json={
      "email": "test@example.com",
      "transactionalId": "<id>",
      "dataVariables": {
        "items": [
          { "name": "Item 1", "description": "Description of Item 1" },
          { "name": "Item 2", "description": "Description of Item 2" },
        ],
      },
    },
  )
  ```
</CodeGroup>

## Send a transactional email with attachments

<Note>
  You must request attachments to be enabled in your account before you can send emails with them.
</Note>

[API reference](/docs/api-reference/send-transactional-email#param-attachments)\
[CLI reference](/docs/cli/transactional#send)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops transactional send <id> \
    --email test@example.com \
    --var loginUrl=https://example.com/login \
    --attachment ./example.pdf
  ```

  ```js JavaScript {13-19} theme={"dark"}
  await fetch("https://app.loops.so/api/v1/transactional", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email: "test@example.com",
      transactionalId: "<id>",
      dataVariables: {
        loginUrl: "https://example.com/login",
      },
      attachments: [
        {
          filename: "example.pdf",
          contentType: "application/pdf",
          data: "<base64-encoded-file-content>",
        },
      ],
    }),
  });
  ```

  ```js JavaScript SDK {11-17} theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const response = await loops.sendTransactionalEmail({
    email: "test@example.com",
    transactionalId: "<id>",
    dataVariables: {
      loginUrl: "https://example.com/login",
    },
    attachments: [
      {
        filename: "example.pdf",
        contentType: "application/pdf",
        data: "<base64-encoded-file-content>",
      },
    ],
  });
  ```

  ```php PHP SDK {11-17} theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient("<your-api-key>");

  $result = $loops->transactional->send(
    email: 'test@example.com',
    transactional_id: '<id>',
    data_variables: [
      'loginUrl' => 'https://example.com/login',
    ],
    attachments: [
      [
        'filename' => 'example.pdf',
        'content_type' => 'application/pdf',
        'data' => base64_encode(file_get_contents('path/to/example.pdf')),
      ],
    ],
  );
  ```

  ```ruby Ruby SDK {7-13} theme={"dark"}
  response = LoopsSdk::Transactional.send(
    email: "test@example.com",
    transactional_id: "<id>",
    data_variables: {
      loginUrl: "https://example.com/login",
    },
    attachments: [
      {
        filename: 'example.pdf',
        content_type: 'application/pdf',
        data: '<base64-encoded-file-content>',
      },
    ],
  )
  ```

  ```python Python {15-21} theme={"dark"}
  import requests

  response = requests.post(
      "https://app.loops.so/api/v1/transactional",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "email": "test@example.com",
          "transactionalId": "<id>",
          "dataVariables": {
              "loginUrl": "https://example.com/login",
          },
          "attachments": [
              {
                  "filename": "example.pdf",
                  "contentType": "application/pdf",
                  "data": "<base64-encoded-file-content>",
              },
          ],
      },
  )
  ```
</CodeGroup>

## Send a transactional email with an idempotency key

Add an `Idempotency-Key` header to the request to prevent duplicate requests.

[API reference](/docs/api-reference/send-transactional-email#param-idempotency-key)\
[CLI reference](/docs/cli/transactional#send)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops transactional send <id> \
    --email test@example.com \
    --var loginUrl=https://example.com/login \
    --idempotency-key 550e8400-e29b-41d4-a716-446655440000
  ```

  ```js JavaScript {6} theme={"dark"}
  await fetch("https://app.loops.so/api/v1/transactional", {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
      "Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
    },
    body: JSON.stringify({
      email: "test@example.com",
      transactionalId: "<id>",
      dataVariables: {
        loginUrl: "https://example.com/login",
      },
    }),
  });
  ```

  ```js JavaScript SDK {11-13} theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const response = await loops.sendTransactionalEmail({
    email: "test@example.com",
    transactionalId: "<id>",
    dataVariables: {
      loginUrl: "https://example.com/login",
    },
    headers: {
      "Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
    },
  });
  ```

  ```php PHP SDK {11-13} theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient("<your-api-key>");

  $result = $loops->transactional->send(
    email: 'test@example.com',
    transactional_id: '<id>',
    data_variables: [
      'loginUrl' => 'https://example.com/login',
    ],
    headers: [
      'Idempotency-Key' => '550e8400-e29b-41d4-a716-446655440000',
    ],
  );
  ```

  ```ruby Ruby SDK {7-9} theme={"dark"}
  response = LoopsSdk::Transactional.send(
    email: "test@example.com",
    transactional_id: "<id>",
    data_variables: {
      loginUrl: "https://example.com/login",
    },
    headers: {
      'Idempotency-Key' => '550e8400-e29b-41d4-a716-446655440000',
    },
  )
  ```

  ```python Python {8} theme={"dark"}
  import requests

  response = requests.post(
      "https://app.loops.so/api/v1/transactional",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
          "Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
      },
      json={
          "email": "test@example.com",
          "transactionalId": "<id>",
          "dataVariables": {
              "loginUrl": "https://example.com/login",
          },
      },
  )
  ```
</CodeGroup>

## List transactional emails

[API reference](/docs/api-reference/list-transactional-emails)\
[CLI reference](/docs/cli/transactional#list)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops transactional list -o json
  ```

  ```js JavaScript theme={"dark"}
  await fetch("https://app.loops.so/api/v1/transactional-emails", {
    method: "GET",
    headers: {
      "Authorization": "Bearer <your-api-key>",
    },
  });
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const response = await loops.listTransactionalEmails();
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient("<your-api-key>");

  $result = $loops->transactional->list();
  ```

  ```ruby Ruby SDK theme={"dark"}
  response = LoopsSdk::Transactional.list
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.get(
      "https://app.loops.so/api/v1/transactional-emails",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  )
  ```
</CodeGroup>


## Related topics

- [Sending settings](/docs/creating-emails/sending-settings.md)
- [API Examples](/docs/api-reference/examples.md)
- [Transactional email](/docs/transactional.md)
- [Transactional](/docs/cli/transactional.md)
- [API Introduction](/docs/api-reference/intro.md)
