---
name: loops
description: Use when building email infrastructure for SaaS products, creating
  marketing campaigns, setting up automated workflows, sending transactional
  emails, managing contacts and audiences, or integrating email with external
  platforms via API, CLI, or webhooks.
metadata:
  mintlify-proj: loops
  version: "1.0"
---

# Loops Skill

## Product summary

Loops is an email platform for sending marketing campaigns, automated workflows, and transactional emails. Agents use Loops to manage contacts, create email content with LMX (Loops Markup Language), trigger workflows via events, and integrate email into applications via REST API, CLI, or webhooks.

**Key files and commands:**
- API key: Generate at `Settings > API` in Loops dashboard
- CLI: Install with `curl -fsSL https://install.loops.so/cli | sh`
- API base URL: `https://app.loops.so/api/v1`
- LMX files: XML-based email markup stored in `.lmx` files
- Primary docs: https://loops.so/docs

## When to use

Reach for this skill when:
- Building email infrastructure for a SaaS product (onboarding, transactional, marketing)
- Creating or updating email campaigns, workflows, or transactional templates
- Managing contacts, properties, and audience segments programmatically
- Triggering automated email sequences based on user events or actions
- Integrating email sending into an application via API or CLI
- Setting up domain authentication (SPF, DKIM, DMARC) for email deliverability
- Sending events to trigger workflows or create contacts
- Testing email workflows with `@example.com` or `@test.com` addresses

## Quick reference

### Email types

| Type | Use case | Tracking | Unsubscribe required |
|------|----------|----------|----------------------|
| **Campaign** | One-off sends to groups (newsletters, product updates) | Opens, clicks | Yes |
| **Workflow** | Automated sequences triggered by events or contact changes | Opens, clicks | Yes |
| **Transactional** | 1:1 system emails (password reset, receipts, confirmations) | No | No |

### CLI authentication

```bash
# Store API key locally
loops auth login my-team

# Switch between stored keys
loops auth use my-team

# Use environment variable (CI/automation)
export LOOPS_API_KEY=your_api_key
```

### Common CLI commands

| Command | Purpose |
|---------|---------|
| `loops campaigns create` | Create draft campaign |
| `loops email-messages update <id>` | Update email content with LMX |
| `loops contacts create --email <email>` | Add contact |
| `loops events send --email <email> --event-name <name>` | Trigger workflow |
| `loops workflows list` | List workflows |
| `loops transactional send` | Send transactional email |

### API endpoints (core)

| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/v1/contacts` | Create or update contact |
| POST | `/v1/events/send` | Send event to trigger workflows |
| POST | `/v1/campaigns` | Create campaign |
| PUT | `/v1/email-messages/{id}` | Update email content |
| POST | `/v1/transactional/send` | Send transactional email |
| GET | `/v1/workflows` | List workflows |

### LMX tags (common)

```xml
<Style themeId="st_default" bodyColor="#ffffff" />
<H1>Heading</H1>
<Paragraph>Text content</Paragraph>
<Button href="https://example.com">Click me</Button>
<Image src="https://images.vialoops.com/..." alt="Alt text" />
<Columns gap="16" widths="50,50">
  <ColumnItem>Left</ColumnItem>
  <ColumnItem>Right</ColumnItem>
</Columns>
<Component componentId="header" />
```

### Dynamic content variables

- **Campaigns/Workflows:** `{contact.firstName}`, `{contact.email}`, `{event.propertyName}`
- **Transactional:** `{data.variableName}` (defined when sending)
- **Fallbacks:** Set default values in email message for preview/testing

## Decision guidance

### When to use Campaign vs Workflow vs Transactional

| Scenario | Type | Reason |
|----------|------|--------|
| Send product update to all users | Campaign | One-time send to many |
| Welcome sequence after signup | Workflow | Triggered by contact creation, multi-step |
| Password reset email | Transactional | 1:1, system-triggered, no tracking needed |
| Re-engagement to inactive users | Campaign | Batch send, one-time |
| Onboarding sequence with delays | Workflow | Multi-step with timers, event-triggered |
| Order confirmation | Transactional | Immediate, per-user, system event |

### When to use API vs CLI vs UI

| Approach | Best for | Example |
|----------|----------|---------|
| **API** | Programmatic integration, production systems | Sending transactional email from app, syncing contacts |
| **CLI** | Local development, Git-based workflows, CI/CD | Managing email content in version control, testing |
| **UI** | One-off sends, quick testing, non-technical users | Creating a campaign, previewing emails |

### Contact management: CSV vs API vs Integration

| Method | Best for | Workflow |
|--------|----------|----------|
| **CSV upload** | Bulk import, one-time loads | Download, edit, re-upload to trigger workflows |
| **API** | Real-time sync, programmatic | Create/update contacts as users sign up |
| **Integration** | External platforms | Sync from Segment, Stripe, Supabase, etc. |

## Workflow

### Typical task: Create and send a campaign

1. **Set up domain** (one-time)
   - Go to `Settings > Domain` in Loops
   - Copy SPF, DKIM, MX records to your DNS provider
   - Click "Verify records" when DNS propagates

2. **Create campaign**
   - CLI: `loops campaigns create --name "Product Update"`
   - Or use API: `POST /v1/campaigns` with `name`, `subject`, `previewText`
   - Returns campaign ID

3. **Write email content**
   - Create `.lmx` file with email markup
   - Include `<Style />`, headings, paragraphs, buttons, images
   - Use `{contact.firstName}` for personalization
   - Test with `@example.com` addresses

4. **Update email message**
   - CLI: `loops email-messages update <id> --lmx-file email.lmx --subject "Subject"`
   - Or API: `PUT /v1/email-messages/{id}` with LMX content
   - Pass `--expected-revision-id` to prevent concurrent edits

5. **Target audience**
   - CLI: `loops campaigns update <id> --audience-segment-id <segment-id>`
   - Or API: `PUT /v1/campaigns/{id}` with `audienceSegmentId`
   - Use filters for contact properties or activity

6. **Schedule send**
   - CLI: `loops campaigns update <id> --scheduled-for "2024-03-15T10:00:00Z"`
   - Or API: Set `scheduledFor` timestamp
   - Can send immediately or schedule for later

7. **Verify before sending**
   - Preview email in Loops UI or CLI
   - Check audience count matches expectations
   - Confirm subject, sender, and preview text

### Typical task: Create and start a workflow

1. **Create workflow**
   - CLI: `loops workflows create --name "Welcome" --trigger-type contact_added`
   - Or use UI: `Workflows > New`

2. **Add trigger**
   - Event-based: `--trigger-type event_received --event-name "signup"`
   - Contact-based: `--trigger-type contact_added` or `contact_updated`
   - List-based: `--trigger-type contact_added_to_list --list-id <id>`

3. **Add email node**
   - CLI: `loops workflows create-node --workflow-id <id> --type email`
   - Create email content with LMX (same as campaigns)

4. **Add delays/branches** (optional)
   - Timer: `--type timer --duration "3 days"`
   - Branch: `--type branch --condition "contact.plan == 'pro'"`
   - Audience filter: `--type audience_filter --filter "subscribed == true"`

5. **Start workflow**
   - CLI: `loops workflows update <id> --status active`
   - Or UI: Click "Start" button
   - New contacts matching trigger will enter workflow

### Typical task: Send transactional email

1. **Create transactional template**
   - CLI: `loops transactional create --name "Password Reset"`
   - Or API: `POST /v1/transactional-emails`

2. **Add data variables**
   - In LMX: `{data.resetLink}`, `{data.firstName}`
   - Define in email message with `dataVariablesFallbacks`

3. **Write email content**
   - Create `.lmx` file with `{data.*}` variables
   - Update email message with LMX

4. **Publish template**
   - CLI: `loops transactional publish <id>`
   - Or API: `POST /v1/transactional-emails/{id}/publish`

5. **Send email**
   - API: `POST /v1/transactional/send` with `transactionalId`, `email`, `dataVariables`
   - CLI: `loops transactional send --transactional-id <id> --email user@example.com --data-variable resetLink=https://...`

### Typical task: Trigger workflow with event

1. **Create event** (if new)
   - Send event with new name: `POST /v1/events/send` with `eventName: "planUpgrade"`
   - Or create manually in `Settings > Events`

2. **Create workflow with event trigger**
   - Trigger type: `event_received`
   - Event name: `planUpgrade`

3. **Add event properties** (optional)
   - Define in event settings: `newPlan`, `oldPlan`, `upgradeDate`
   - Use in email: `{event.newPlan}`

4. **Send event from application**
   - API: `POST /v1/events/send` with `email`, `eventName`, `eventProperties`
   - SDK: `sendEvent({ email, eventName, eventProperties })`
   - Workflow triggers automatically for matching contacts

## Common gotchas

- **API key exposure:** Never commit API keys to Git. Use environment variables or CLI keyring storage.
- **LMX syntax errors:** LMX is XML, not HTML. Tags are case-sensitive (`<Paragraph>`, not `<paragraph>`). Use `{contact.*}`, `{event.*}`, `{data.*}` prefixes for variables.
- **Concurrent edits:** Always pass `contentRevisionId` when updating email messages via API to prevent overwriting concurrent changes. Use `--expected-revision-id` in CLI.
- **Transactional vs marketing:** Transactional emails don't track opens/clicks and don't require unsubscribe links. Don't use them for marketing.
- **Contact creation:** Sending a transactional email to a new contact doesn't add them to your audience or trigger "Contact added" workflows unless you set `addToAudience: true`.
- **Test addresses:** Use `@example.com` or `@test.com` for testing—emails won't send but workflows will trigger normally.
- **Domain setup required:** You must set up SPF, DKIM, MX records before sending any emails. Verify records in Loops before sending.
- **Rate limits:** API is rate-limited to 10 requests/second per team (60 requests/60 seconds for content endpoints). Handle 429 responses with exponential backoff.
- **Workflow pausing:** Can't edit workflow emails while active. Pause workflow first, make changes, then resume.
- **Segment updates:** Segments are dynamic—they update as contact properties change. Filters apply at send time, not at creation time.
- **Deleted nodes:** Deleting a workflow node deletes all nodes that follow it. Be careful with deletions.

## Verification checklist

Before submitting work with Loops:

- [ ] API key is stored in environment variable or CLI keyring, not hardcoded
- [ ] Domain records (SPF, DKIM, MX) are verified in Loops settings
- [ ] Email content uses correct LMX syntax (case-sensitive tags, proper prefixes for variables)
- [ ] `contentRevisionId` passed on email message updates to prevent conflicts
- [ ] Transactional emails use `{data.*}` variables; campaigns/workflows use `{contact.*}` and `{event.*}`
- [ ] Test email sent to `@example.com` or `@test.com` address before production send
- [ ] Audience segment/filter count matches expectations
- [ ] Workflow trigger type matches use case (event, contact added, contact updated, list)
- [ ] Campaign scheduled time is correct (check timezone)
- [ ] Unsubscribe link included in campaigns/workflows (auto-added to footer)
- [ ] No rate limit errors in API responses (check `x-ratelimit-remaining` header)
- [ ] Workflow paused before editing emails or nodes

## Resources

- **Full documentation:** https://loops.so/docs/llms.txt (comprehensive page-by-page navigation for agents)
- **API Reference:** https://loops.so/docs/api-reference/intro
- **CLI Reference:** https://loops.so/docs/cli
- **LMX Guide:** https://loops.so/docs/creating-emails/lmx

---

> For additional documentation and navigation, see: https://loops.so/docs/llms.txt