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

[API reference](/docs/api-reference/create-campaign)\
[CLI reference](/docs/cli/campaigns#create)

<CodeGroup>
  ```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"]
  ```
</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 related email message with `contentRevisionId`

Use `emailMessageId` from when you created the campaign 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.
</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 $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"]
  ```
</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>

## Target an audience segment

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

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

[API reference](/docs/api-reference/update-campaign)\
[CLI reference](/docs/cli/campaigns#update)

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

You can also set the segment when creating the campaign:

<CodeGroup>
  ```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",
      },
  )
  ```
</CodeGroup>

## 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 when you created the campaign as the path parameter.

[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/${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()
  ```
</CodeGroup>


## Related topics

- [API Examples](/docs/api-reference/examples.md)
- [Contacts API examples](/docs/api-reference/examples/contacts.md)
- [Events API examples](/docs/api-reference/examples/events.md)
- [Workflows API examples](/docs/api-reference/examples/workflows.md)
- [Transactional email API examples](/docs/api-reference/examples/transactional-emails.md)
