Create a campaign
This creates a draft campaign and a related email message in one request. Only aname 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.CLI reference
loops campaigns create --name "Spring product announcement" -o json
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;
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;
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'];
response = LoopsSdk::Campaigns.create(
name: "Spring product announcement",
)
campaign_id = response["id"]
email_message_id = response["emailMessageId"]
content_revision_id = response["emailMessageContentRevisionId"]
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 thelmx payload.
List themes API reference CLI referenceList components API reference CLI reference
loops themes list -o json
loops components list -o json
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();
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 }),
]);
use Loops\LoopsClient;
$loops = new LoopsClient('<your-api-key>');
$themes = $loops->themes->list(per_page: 20);
$components = $loops->components->list(per_page: 20);
themes = LoopsSdk::Themes.list(perPage: 20)
components = LoopsSdk::Components.list(perPage: 20)
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 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.CLI reference
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
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;
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;
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'];
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"]
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 referenceComplete upload API reference
Upload CLI reference
loops uploads create ./header.png -o json
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();
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);
use Loops\LoopsClient;
$loops = new LoopsClient('<your-api-key>');
$result = $loops->uploads->upload(path: './header.png');
$final_url = $result['finalUrl'];
response = LoopsSdk::Uploads.upload(path: "./header.png")
final_url = response["finalUrl"]
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 to find a segment ID, then apply it to a draft campaign with Update a campaign. SettingaudienceSegmentId clears any existing audienceFilter on the campaign.
API referenceCLI reference
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();
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,
});
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,
);
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,
)
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()
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",
}),
});
import { LoopsClient } from "loops";
const loops = new LoopsClient("<your-api-key>");
const data = await loops.createCampaign({
name: "Spring product announcement",
audienceSegmentId: "clr4c8t0v008yl70x3y4z5a6b",
});
use Loops\LoopsClient;
$loops = new LoopsClient('<your-api-key>');
$result = $loops->campaigns->create(
name: 'Spring product announcement',
audience_segment_id: 'clr4c8t0v008yl70x3y4z5a6b',
);
response = LoopsSdk::Campaigns.create(
name: "Spring product announcement",
audience_segment_id: "clr4c8t0v008yl70x3y4z5a6b",
)
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 acceptcontactProperties for personalization.
Use emailMessageId from when you created the campaign as the path parameter.
API reference
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();
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",
},
});
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',
],
);
preview = LoopsSdk::EmailMessages.preview(
email_message_id: email_message_id,
emails: ["you@example.com"],
contact_properties: {
firstName: "Alex",
plan: "Pro",
},
)
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()

