> ## 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.

# Campaigns API examples

> Code examples for creating campaigns, targeting audiences, sending previews, and updating email messages with content revisions via the Loops API, SDKs, and CLI.

## Create a campaign

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

Only a `name` value is required.

> [!TIP]
>
> Save the returned `emailMessageContentRevisionId`. Pass it as
> `expectedRevisionId` when updating an email message to avoid `409 Conflict` errors caused by stale revisions.

[API reference](https://loops.so/docs/api-reference/create-campaign.md)\
[CLI reference](https://loops.so/docs/cli/campaigns.md#create)

```bash CLI theme={"dark"}
loops campaigns create --name "Spring product announcement" -o json
```

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

const data = await response.json();
const campaignId = data.id;
const emailMessageId = data.emailMessageId;
const emailMessageContentRevisionId = data.emailMessageContentRevisionId;
```

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

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

const data = await loops.createCampaign({
  name: "Spring product announcement",
});

const campaignId = data.id;
const emailMessageId = data.emailMessageId;
const emailMessageContentRevisionId = data.emailMessageContentRevisionId;
```

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

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

$result = $loops->campaigns->create(name: 'Spring product announcement');

$campaign_id = $result['id'];
$email_message_id = $result['emailMessageId'];
$content_revision_id = $result['emailMessageContentRevisionId'];
```

```ruby Ruby SDK theme={"dark"}
response = LoopsSdk::Campaigns.create(
  name: "Spring product announcement",
)

campaign_id = response["id"]
email_message_id = response["emailMessageId"]
content_revision_id = response["emailMessageContentRevisionId"]
```

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

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

data = response.json()
campaign_id = data["id"]
email_message_id = data["emailMessageId"]
content_revision_id = data["emailMessageContentRevisionId"]
```

## 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](https://loops.so/docs/api-reference/list-themes.md) [CLI reference](https://loops.so/docs/cli/themes.md#list)\
List components [API reference](https://loops.so/docs/api-reference/list-components.md) [CLI reference](https://loops.so/docs/cli/components.md#list)

```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()
```

## Update the related email message with `contentRevisionId`

Use `emailMessageId` from the campaign you created as the path parameter, and pass the `emailMessageContentRevisionId` as `expectedRevisionId`.

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

Themes and components you queried in step 2 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.

[API reference](https://loops.so/docs/api-reference/update-email-message.md)\
[CLI reference](https://loops.so/docs/cli/email-messages.md#update)

```bash CLI theme={"dark"}
loops email-messages update $emailMessageId \
  --expected-revision-id $emailMessageContentRevisionId \
  --subject "Big spring updates" \
  --preview-text "A quick look at what's new" \
  --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>Hey there, here is what's new.</Text>
</Paragraph>
<Component componentId="logo" />
<Section>
  <Paragraph>Read the full changelog in your dashboard.</Paragraph>
</Section>`;

const response = await fetch(
  `https://app.loops.so/api/v1/email-messages/${emailMessageId}`,
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      expectedRevisionId: emailMessageContentRevisionId,
      subject: "Big spring updates",
      previewText: "A quick look at what's new",
      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>Hey there, here is what's new.</Text>
</Paragraph>
<Component componentId="logo" />
<Section>
  <Paragraph>Read the full changelog in your dashboard.</Paragraph>
</Section>`;

const updated = await loops.updateEmailMessage(emailMessageId, {
  expectedRevisionId: emailMessageContentRevisionId,
  subject: "Big spring updates",
  previewText: "A quick look at what's new",
  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>Hey there, here is what's new.</Text>
</Paragraph>
<Component componentId="logo" />
<Section>
  <Paragraph>Read the full changelog in your dashboard.</Paragraph>
</Section>
LMX;

$updated = $loops->emailMessages->update(
  email_message_id: $email_message_id,
  expected_revision_id: $content_revision_id,
  subject: 'Big spring updates',
  preview_text: 'A quick look at what\'s new',
  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>Hey there, here is what's new.</Text>
  </Paragraph>
  <Component componentId="logo" />
  <Section>
    <Paragraph>Read the full changelog in your dashboard.</Paragraph>
  </Section>
LMX

updated = LoopsSdk::EmailMessages.update(
  email_message_id: email_message_id,
  expected_revision_id: content_revision_id,
  subject: "Big spring updates",
  preview_text: "A quick look at what's new",
  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/{email_message_id}",
    headers={
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
    },
    json={
        "expectedRevisionId": content_revision_id,
        "subject": "Big spring updates",
        "previewText": "A quick look at what's new",
        "fromName": "Loops",
        "fromEmail": "hello",
        "replyToEmail": "support@example.com",
        "lmx": "<Style backgroundColor=\"#ffffff\" textBaseColor=\"#111111\" /><Section><Paragraph>Hey there, here is what's new.</Paragraph></Section>",
    },
)

updated = response.json()
next_content_revision_id = updated["contentRevisionId"]
```

## 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](https://loops.so/docs/api-reference/create-upload.md)\
[Complete upload API reference](https://loops.so/docs/api-reference/complete-upload.md)\
[Upload CLI reference](https://loops.so/docs/cli/uploads.md#create)

```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"]
```

## Target an audience segment

List your [audience segments](https://loops.so/docs/api-reference/list-audience-segments.md) to find a
segment ID, then apply it to a draft campaign with
[Update a campaign](https://loops.so/docs/api-reference/update-campaign.md).

Setting `audienceSegmentId` clears any existing `audienceFilter` on the campaign.

[API reference](https://loops.so/docs/api-reference/update-campaign.md)\
[CLI reference](https://loops.so/docs/cli/campaigns.md#update)

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

const { data: segments } = await segmentsResponse.json();
const audienceSegmentId = segments[0].id;

const updateResponse = await fetch(
  `https://app.loops.so/api/v1/campaigns/${campaignId}`,
  {
    method: "POST",
    headers: {
      "Authorization": "Bearer <your-api-key>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      audienceSegmentId,
    }),
  },
);

const updatedCampaign = await updateResponse.json();
```

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

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

const { data: segments } = await loops.listAudienceSegments();
const audienceSegmentId = segments[0].id;

const updatedCampaign = await loops.updateCampaign(campaignId, {
  audienceSegmentId,
});
```

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

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

$segments = $loops->audienceSegments->list();
$audience_segment_id = $segments['data'][0]['id'];

$updated_campaign = $loops->campaigns->update(
  campaign_id: $campaign_id,
  audience_segment_id: $audience_segment_id,
);
```

```ruby Ruby SDK theme={"dark"}
segments = LoopsSdk::AudienceSegments.list
audience_segment_id = segments["data"][0]["id"]

updated_campaign = LoopsSdk::Campaigns.update(
  campaign_id: campaign_id,
  audience_segment_id: audience_segment_id,
)
```

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

segments_response = requests.get(
    "https://app.loops.so/api/v1/audience-segments",
    headers={
        "Authorization": "Bearer <your-api-key>",
    },
)

segments = segments_response.json()["data"]
audience_segment_id = segments[0]["id"]

update_response = requests.post(
    f"https://app.loops.so/api/v1/campaigns/{campaign_id}",
    headers={
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
    },
    json={
        "audienceSegmentId": audience_segment_id,
    },
)

updated_campaign = update_response.json()
```

You can also set the segment when creating the campaign:

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

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

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

const data = await loops.createCampaign({
  name: "Spring product announcement",
  audienceSegmentId: "clr4c8t0v008yl70x3y4z5a6b",
});
```

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

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

$result = $loops->campaigns->create(
  name: 'Spring product announcement',
  audience_segment_id: 'clr4c8t0v008yl70x3y4z5a6b',
);
```

```ruby Ruby SDK theme={"dark"}
response = LoopsSdk::Campaigns.create(
  name: "Spring product announcement",
  audience_segment_id: "clr4c8t0v008yl70x3y4z5a6b",
)
```

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

response = requests.post(
    "https://app.loops.so/api/v1/campaigns",
    headers={
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
    },
    json={
        "name": "Spring product announcement",
        "audienceSegmentId": "clr4c8t0v008yl70x3y4z5a6b",
    },
)
```

## Send a campaign preview

After updating the campaign's email message, send a test preview to one or more
addresses. Campaign previews accept `contactProperties` for personalization.

Use `emailMessageId` from the campaign you created as the path parameter.

[API reference](https://loops.so/docs/api-reference/preview-email-message.md)

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

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(emailMessageId, {
  emails: ["you@example.com"],
  contactProperties: {
    firstName: "Alex",
    plan: "Pro",
  },
});
```

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

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

$preview = $loops->emailMessages->preview(
  email_message_id: $email_message_id,
  emails: ['you@example.com'],
  contact_properties: [
    'firstName' => 'Alex',
    'plan' => 'Pro',
  ],
);
```

```ruby Ruby SDK theme={"dark"}
preview = LoopsSdk::EmailMessages.preview(
  email_message_id: email_message_id,
  emails: ["you@example.com"],
  contact_properties: {
    firstName: "Alex",
    plan: "Pro",
  },
)
```

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

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

preview = preview_response.json()
```


## Related topics

- [Create and send your first campaign with the API or an agent](https://loops.so/docs/guides/first-campaign-api.md)
- [API Examples](https://loops.so/docs/api-reference/examples.md)
- [Contacts API examples](https://loops.so/docs/api-reference/examples/contacts.md)
- [Workflows API examples](https://loops.so/docs/api-reference/examples/workflows.md)
- [Events API examples](https://loops.so/docs/api-reference/examples/events.md)
