Skip to main content

Create a campaign

This creates a draft campaign and a related email message in one request. Only a name value is required.
Save the returned emailMessageContentRevisionId. Pass it as expectedRevisionId when updating an email message to avoid 409 Conflict errors caused by stale revisions.
API reference
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 emailMessageId = data.emailMessageId;
const emailMessageContentRevisionId = 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
List components API reference
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();
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.
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
Get theme API reference
Get component API reference
const lmxContent = `
<Style themeId="default" />
<Paragraph>
  <Text>Hey there, here is what's new.</Text>
</Paragraph>
<Component componentId="logo" />
<Section> 
  ...
</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;

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
Complete upload API reference
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();
Last modified on May 27, 2026