# Loops (/docs/v/0.6.0/adapters/loops)



<ProviderBadge adapter="loops" />

Loops sends through a published Loops transactional email. It is best for product-triggered lifecycle messages where the template lives in Loops and your app passes data variables.

## Before live sends [#before-live-sends]

Create or publish the transactional email in Loops, copy its `transactionalId`, and create an API key with permission to send transactional email. Loops may require account-level enablement for attachments, so omit attachments until that capability is enabled.

## Configure [#configure]

```ts
import { createEmailClient } from "@opencoredev/email-sdk";
import { loops } from "@opencoredev/email-sdk/loops";

const email = createEmailClient({
  adapters: [
    loops({
      apiKey: process.env.LOOPS_API_KEY!,
      transactionalId: process.env.LOOPS_TRANSACTIONAL_ID!,
    }),
  ],
});
```

## Send [#send]

```ts
const result = await email.send({
  from: "Acme <hello@acme.com>",
  to: "user@example.com",
  subject: "Welcome",
  html: "<p>Your workspace is ready.</p>",
  metadata: {
    firstName: "Ada",
    workspaceName: "Acme",
  },
});

console.log(result.provider, result.id);
```

Loops maps `metadata` into `dataVariables` together with `subject`, `html`, `text`, and `from`. It only supports one recipient per transactional send.

## Send with an attachment [#send-with-an-attachment]

```ts
await email.send({
  from: "Acme <hello@acme.com>",
  to: "user@example.com",
  subject: "Export ready",
  text: "Your export is attached.",
  metadata: {
    firstName: "Ada",
  },
  attachments: [
    {
      filename: "export.txt",
      content: "name,total\nAda,42",
      contentType: "text/plain",
    },
  ],
});
```

## Options [#options]

| Option            | Type           | Required | Notes                                |
| ----------------- | -------------- | -------- | ------------------------------------ |
| `apiKey`          | `string`       | Yes      | Loops API key.                       |
| `transactionalId` | `string`       | Yes      | Transactional email ID.              |
| `baseUrl`         | `string`       | No       | Defaults to `https://app.loops.so`.  |
| `fetch`           | `typeof fetch` | No       | Useful for tests or custom runtimes. |

Loops transactional sends support one recipient, metadata through `dataVariables`, and attachments through the transactional attachments payload. Loops documents that attachments require support enablement on the account, so accounts without that enablement should omit `attachments`. Unsupported fields throw before the API call.

## Response [#response]

The adapter maps Loops `id` or `transactionalId` to the normalized `id` field when the API returns one.

## CLI smoke test [#cli-smoke-test]

```bash
LOOPS_API_KEY="loops_..." \
LOOPS_TRANSACTIONAL_ID="cm..." \
npx email-sdk send \
  --adapter loops \
  --from "Acme <hello@acme.com>" \
  --to user@example.com \
  --subject "Loops test" \
  --text "It works"
```
