PHPackages                             loops-so/loops - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. loops-so/loops

ActiveLibrary[API Development](/categories/api)

loops-so/loops
==============

The official Loops PHP client.

v3.1.1(3d ago)125.2k↓24.3%1MITPHPPHP ^8.1

Since Mar 24Pushed 3d agoCompare

[ Source](https://github.com/Loops-so/loops-php)[ Packagist](https://packagist.org/packages/loops-so/loops)[ Docs](https://github.com/Loops-so/loops-php)[ RSS](/packages/loops-so-loops/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (7)Dependencies (6)Versions (13)Used By (0)

Loops PHP Package
=================

[](#loops-php-package)

[![Packagist Total Downloads](https://camo.githubusercontent.com/438832da5480bab52398dfe646dc35e0383b292e969969b73cf120bea3d7b2d3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6c6f6f70732d736f2f6c6f6f70733f7374796c653d736f6369616c)](https://packagist.org/packages/loops-so/loops)

Introduction
------------

[](#introduction)

This is the official PHP package for [Loops](https://loops.so), an email platform for modern software companies.

Installation
------------

[](#installation)

Install the Loops package [using Composer](https://packagist.org/packages/loops-so/loops):

```
composer require loops-so/loops
```

Requires PHP 8.1.

Usage
-----

[](#usage)

You will need a Loops API key to use the package.

In your Loops account, go to the [API Settings page](https://app.loops.so/settings?page=api) and click **Generate key**.

Copy this key and save it in your application code (for example, in an environment variable).

See the API documentation to learn more about [rate limiting](https://loops.so/docs/api-reference#rate-limiting) and [error handling](https://loops.so/docs/api-reference#debugging).

To use the package, first initialise the client with your API key, then you can call one of the methods.

API rate limits can be handled with the included `RateLimitExceededError` exception.

```
use Loops\LoopsClient;

$loops = new LoopsClient(env('LOOPS_API_KEY'));

// Test API key
$result = $loops->apiKey->test();

// Create a contact and catch errors
try {
    $result = $loops->contacts->create('user@example.com', [
      'firstName' => 'John'
    ]);
} catch (Loops\Exceptions\APIError $e) {
    // Handle API errors (400, 401, 403, etc)
    echo $e->getMessage();
    $returnedJson = $e->getJson(); // JSON returned by the API
    $statusCode = $e->getStatusCode(); // HTTP status code from the response
} catch (\Exception $e) {
    // Handle any other unexpected errors
    echo "Unexpected error: " . $e->getMessage();
}
```

Handling rate limits
--------------------

[](#handling-rate-limits)

You can use the check for rate limit issues with your requests.

You can access details about the rate limits from the `getLimit` and `getRemaining` functions.

```
try {
    $result = $loops->contacts->create('user@example.com', [
      'firstName' => 'John'
    ]);
} catch (Loops\Exceptions\RateLimitExceededError $e) {
    // Handle rate limiting
    echo "Rate limit hit. Limit: " . $e->getLimit() . ", requests remaining: " . $e->getRemaining();
} catch (Loops\Exceptions\APIError $e) {
    // Handle API errors (400, 401, 403, etc)
    echo $e->getMessage();
} catch (\Exception $e) {
    // Handle any other unexpected errors
    echo "Unexpected error: " . $e->getMessage();
}
```

Default contact properties
--------------------------

[](#default-contact-properties)

Each contact in Loops has a set of default properties. These will always be returned in API results.

- `id`
- `email`
- `firstName`
- `lastName`
- `source`
- `subscribed`
- `userGroup`
- `userId`
- `optInStatus`

Custom contact properties
-------------------------

[](#custom-contact-properties)

You can use custom contact properties in API calls. Please make sure to [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.

Methods
-------

[](#methods)

- [apiKey-&gt;test()](#apikey-test)
- [contacts-&gt;create()](#contacts-create)
- [contacts-&gt;update()](#contacts-update)
- [contacts-&gt;find()](#contacts-find)
- [contacts-&gt;delete()](#contacts-delete)
- [contacts-&gt;checkSuppression()](#contacts-checksuppression)
- [contacts-&gt;removeSuppression()](#contacts-removesuppression)
- [contactProperties-&gt;create()](#contactproperties-create)
- [contactProperties-&gt;list()](#contactproperties-list)
- [mailingLists-&gt;list()](#mailinglists-list)
- [events-&gt;send()](#events-send)
- [transactional-&gt;send()](#transactional-send)
- [transactional-&gt;list()](#transactional-list)
- [transactional-&gt;create()](#transactional-create)
- [transactional-&gt;get()](#transactional-get)
- [transactional-&gt;update()](#transactional-update)
- [transactional-&gt;ensureDraft()](#transactional-ensuredraft)
- [transactional-&gt;publish()](#transactional-publish)
- [dedicatedSendingIps-&gt;list()](#dedicatedsendingips-list)
- [themes-&gt;list()](#themes-list)
- [themes-&gt;get()](#themes-get)
- [components-&gt;list()](#components-list)
- [components-&gt;get()](#components-get)
- [campaigns-&gt;list()](#campaigns-list)
- [campaigns-&gt;create()](#campaigns-create)
- [campaigns-&gt;get()](#campaigns-get)
- [campaigns-&gt;update()](#campaigns-update)
- [campaignGroups-&gt;list()](#campaigngroups-list)
- [campaignGroups-&gt;create()](#campaigngroups-create)
- [campaignGroups-&gt;get()](#campaigngroups-get)
- [campaignGroups-&gt;update()](#campaigngroups-update)
- [audienceSegments-&gt;list()](#audiencesegments-list)
- [audienceSegments-&gt;get()](#audiencesegments-get)
- [workflows-&gt;list()](#workflows-list)
- [workflows-&gt;get()](#workflows-get)
- [workflows-&gt;getNode()](#workflows-getnode)
- [emailMessages-&gt;get()](#emailmessages-get)
- [emailMessages-&gt;update()](#emailmessages-update)
- [emailMessages-&gt;preview()](#emailmessages-preview)
- [transactionalGroups-&gt;list()](#transactionalgroups-list)
- [transactionalGroups-&gt;create()](#transactionalgroups-create)
- [transactionalGroups-&gt;get()](#transactionalgroups-get)
- [transactionalGroups-&gt;update()](#transactionalgroups-update)
- [uploads-&gt;upload()](#uploads-upload)

---

### apiKey-&gt;test()

[](#apikey-test)

Test if your API key is valid.

[API Reference](https://loops.so/docs/api-reference/api-key)

#### Parameters

[](#parameters)

None

#### Example

[](#example)

```
$result = $loops->apiKey->test();
```

#### Response

[](#response)

This method will return a success or error message:

```
{
  "success": true,
  "teamName": "Company name"
}
```

```
{
  "error": "Invalid API key"
}
```

---

### contacts-&gt;create()

[](#contacts-create)

Create a new contact.

[API Reference](https://loops.so/docs/api-reference/create-contact)

#### Parameters

[](#parameters-1)

NameTypeRequiredNotes`$email`stringYesIf a contact already exists with this email address, an `APIError` will be thrown.`$properties`arrayNoAn array containing default and any custom properties for your contact.
Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)).`$mailing_lists`arrayNoAn array of mailing list IDs and boolean subscription statuses.#### Examples

[](#examples)

```
$result = $loops->contacts->create("hello@gmail.com");

$contact_properties = [
  'firstName' => "Bob" /* Default property */,
  'favoriteColor' => "Red" /* Custom property */,
];
$mailing_lists = [
  'cm06f5v0e45nf0ml5754o9cix' => TRUE,
  'cm16k73gq014h0mmj5b6jdi9r' => FALSE,
];
$result = $loops->contacts->create(
  email: "hello@gmail.com",
  properties: $contact_properties,
  mailing_lists: $mailing_lists
);
```

#### Response

[](#response-1)

```
{
  "success": true,
  "id": "cll6b3i8901a9jx0oyktl2m4u"
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "An error message here."
}
```

---

### contacts-&gt;update()

[](#contacts-update)

Update a contact.

Note: To update a contact's email address, the contact requires a `$user_id` value. Then you can make a request with their `$user_id` and an updated email address.

[API Reference](https://loops.so/docs/api-reference/update-contact)

#### Parameters

[](#parameters-2)

NameTypeRequiredNotes`$email`stringNoThe email address of the contact to update. If there is no contact with this email address, a new contact will be created using the email and properties in this request. Required if `$user_id` is not present.`$user_id`stringNoThe contact's unique user ID. If you use `$user_id` without `$email`, this value must have already been added to your contact in Loops. Required if `$email` is not present.`$properties`arrayNoAn array containing default and any custom properties for your contact.
Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)).`$mailing_lists`arrayNoAn array of mailing list IDs and boolean subscription statuses.#### Example

[](#example-1)

```
$result = $loops->contacts->update(
  email: 'hello@gmail.com',
  properties: [
    'firstName' => 'Bob', /* Default property */
    'favoriteColor' => 'Blue' /* Custom property */
  ]
);

// Updating a contact's email address using $user_id
$result = $loops->contacts->update(
  user_id: '1234',
  email: 'newemail@gmail.com'
);

// Subscribe a contact to a mailing list
$result = $loops->contacts->update(
  email: 'hello@gmail.com',
  mailing_lists: [
    'cm06f5v0e45nf0ml5754o9cix' => true /* Subscribe */,
    'cm16k73gq014h0mmj5b6jdi9r' => false /* Unsubscribe */
  ]
);
```

#### Response

[](#response-2)

```
{
  "success": true,
  "id": "cll6b3i8901a9jx0oyktl2m4u"
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "An error message here."
}
```

---

### contacts-&gt;find()

[](#contacts-find)

Find a contact.

[API Reference](https://loops.so/docs/api-reference/find-contact)

#### Parameters

[](#parameters-3)

You must use one parameter in the request.

NameTypeRequiredNotes`$email`stringNo`$user_id`stringNo#### Examples

[](#examples-1)

```
$result = $loops->contacts->find(email: 'hello@gmail.com');

$result = $loops->contacts->find(user_id: '12345');
```

#### Response

[](#response-3)

This method will return a list containing a single contact object, which will include all default properties and any custom properties.

If no contact is found, an empty list will be returned.

```
[
  {
    "id": "cll6b3i8901a9jx0oyktl2m4u",
    "email": "hello@gmail.com",
    "firstName": "Bob",
    "lastName": null,
    "source": "API",
    "subscribed": true,
    "userGroup": "",
    "userId": "12345",
    "mailingLists": {
      "cm06f5v0e45nf0ml5754o9cix": true
    },
    "optInStatus": null,
    "favoriteColor": "Blue" /* Custom property */
  }
]
```

---

### contacts-&gt;delete()

[](#contacts-delete)

Delete a contact.

[API Reference](https://loops.so/docs/api-reference/delete-contact)

#### Parameters

[](#parameters-4)

You must use one parameter in the request.

NameTypeRequiredNotes`$email`stringNo`$user_id`stringNo#### Example

[](#example-2)

```
$result = $loops->contacts->delete(email: 'hello@gmail.com')

$result = $loops->contacts->delete(user_id: '12345')
```

#### Response

[](#response-4)

```
{
  "success": true,
  "message": "Contact deleted."
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Reuqest
{
  "success": false,
  "message": "An error message here."
}
```

```
// HTTP 404 Not Found
{
  "success": false,
  "message": "An error message here."
}
```

---

### contacts-&gt;checkSuppression()

[](#contacts-checksuppression)

Check if a contact is currently suppressed.

[API Reference](https://loops.so/docs/api-reference/check-contact-suppression)

#### Parameters

[](#parameters-5)

You must use one parameter in the request.

NameTypeRequiredNotes`$email`stringNo`$user_id`stringNo#### Example

[](#example-3)

```
$result = $loops->contacts->checkSuppression(email: 'hello@gmail.com');

$result = $loops->contacts->checkSuppression(user_id: '12345');
```

#### Response

[](#response-5)

```
{
  "contact": {
    "id": "cll6b3i8901a9jx0oyktl2m4u",
    "email": "adam@loops.so",
    "userId": null
  },
  "isSuppressed": true,
  "removalQuota": {
    "limit": 100,
    "remaining": 10
  }
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "An email or userId is required."
}
```

```
// HTTP 404 Not Found
{
  "success": false,
  "message": "This contact was not found."
}
```

---

### contacts-&gt;removeSuppression()

[](#contacts-removesuppression)

Remove suppression for a contact.

[API Reference](https://loops.so/docs/api-reference/remove-contact-suppression)

#### Parameters

[](#parameters-6)

You must use one parameter in the request.

NameTypeRequiredNotes`$email`stringNo`$user_id`stringNo#### Example

[](#example-4)

```
$result = $loops->contacts->removeSuppression(email: 'hello@gmail.com');

$result = $loops->contacts->removeSuppression(user_id: '12345');
```

#### Response

[](#response-6)

```
{
  "success": true,
  "message": "Email removed from suppression list.",
  "removalQuota": {
    "limit": 100,
    "remaining": 4
  }
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "This contact is not suppressed."
}
```

```
// HTTP 404 Not Found
{
  "success": false,
  "message": "This contact was not found."
}
```

---

### contactProperties-&gt;create()

[](#contactproperties-create)

Create a new contact property.

[API Reference](https://loops.so/docs/api-reference/create-contact-property)

#### Parameters

[](#parameters-7)

NameTypeRequiredNotes`$name`stringYesThe name of the property. Should be in camelCase, like `planName` or `favouriteColor`.`$type`stringYesThe property's value type.
Can be one of `string`, `number`, `boolean` or `date`.#### Examples

[](#examples-2)

```
$result = $loops->contactProperties->create("planName", "string");
```

#### Response

[](#response-7)

```
{
  "success": true
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "An error message here."
}
```

---

### contactProperties-&gt;list()

[](#contactproperties-list)

Get a list of your account's contact properties.

[API Reference](https://loops.so/docs/api-reference/list-contact-properties)

#### Parameters

[](#parameters-8)

NameTypeRequiredNotes`$list`stringNoUse "custom" to retrieve only your account's custom properties.#### Example

[](#example-5)

```
$result = $loops->contactProperties->list();

$result = $loops->contactProperties->list(list: "custom");
```

#### Response

[](#response-8)

This method will return a list of contact property objects containing `key`, `label` and `type` attributes.

```
[
  {
    "key": "firstName",
    "label": "First Name",
    "type": "string"
  },
  {
    "key": "lastName",
    "label": "Last Name",
    "type": "string"
  },
  {
    "key": "email",
    "label": "Email",
    "type": "string"
  },
  {
    "key": "notes",
    "label": "Notes",
    "type": "string"
  },
  {
    "key": "source",
    "label": "Source",
    "type": "string"
  },
  {
    "key": "userGroup",
    "label": "User Group",
    "type": "string"
  },
  {
    "key": "userId",
    "label": "User Id",
    "type": "string"
  },
  {
    "key": "subscribed",
    "label": "Subscribed",
    "type": "boolean"
  },
  {
    "key": "createdAt",
    "label": "Created At",
    "type": "date"
  },
  {
    "key": "favoriteColor",
    "label": "Favorite Color",
    "type": "string"
  },
  {
    "key": "plan",
    "label": "Plan",
    "type": "string"
  }
]
```

---

### mailingLists-&gt;list()

[](#mailinglists-list)

Get a list of your account's mailing lists. [Read more about mailing lists](https://loops.so/docs/contacts/mailing-lists)

[API Reference](https://loops.so/docs/api-reference/list-mailing-lists)

#### Parameters

[](#parameters-9)

None

#### Example

[](#example-6)

```
$result = $loops->mailingLists->list();
```

#### Response

[](#response-9)

This method will return a list of mailing list objects containing `id`, `name`, `description` and `isPublic` attributes.

If your account has no mailing lists, an empty list will be returned.

```
[
  {
    "id": "cm06f5v0e45nf0ml5754o9cix",
    "name": "Main list",
    "description": "All customers.",
    "isPublic": true
  },
  {
    "id": "cm16k73gq014h0mmj5b6jdi9r",
    "name": "Investors",
    "description": null,
    "isPublic": false
  }
]
```

---

### events-&gt;send()

[](#events-send)

Send an event to trigger an email in Loops. [Read more about events](https://loops.so/docs/events)

[API Reference](https://loops.so/docs/api-reference/send-event)

#### Parameters

[](#parameters-10)

NameTypeRequiredNotes`$event_name`stringYes`$email`stringNoThe contact's email address. Required if `$user_id` is not present.`$user_id`stringNoThe contact's unique user ID. If you use `$user_id` without `$email`, this value must have already been added to your contact in Loops. Required if `$email` is not present.`$contact_properties`arrayNoAn array containing contact properties, which will be updated or added to the contact when the event is received.
Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)).`$event_properties`arrayNoAn array containing event properties, which will be made available in emails that are triggered by this event.
Values can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](https://loops.so/docs/events/properties#important-information-about-event-properties)).`$mailing_lists`arrayNoAn array of mailing list IDs and boolean subscription statuses.`$headers`arrayNoAdditional headers to send with the request.#### Examples

[](#examples-3)

```
$result = $loops->events->send(
  event_name: 'signup',
  email: 'hello@gmail.com'
);

$result = $loops->events->send(
  event_name: 'signup',
  email: 'hello@gmail.com',
  event_properties: [
    'username' => 'user1234',
    'signupDate' => '2024-03-21T10:09:23Z'
  ],
  mailing_lists: [
    'cm06f5v0e45nf0ml5754o9cix' => true,
    'cm16k73gq014h0mmj5b6jdi9r' => false
  ]
;

# In this case with both email and user_id present, the system will look for a contact with either a
#  matching `email` or `user_id` value.
# If a contact is found for one of the values (e.g. `email`), the other value (e.g. `user_id`) will be updated.
# If a contact is not found, a new contact will be created using both `email` and `user_id` values.
# Any values added in `contact_properties` will also be updated on the contact.
$result = $loops->events->send(
  event_name: 'signup',
  email: 'hello@gmail.com',
  user_id: '1234567890',
  contact_properties: [
    'firstName' => 'Bob',
    'plan' => 'pro',
  }]
});

# Example with Idempotency-Key header
$result = $loops->events->send(
  event_name: 'signup',
  email: 'hello@gmail.com',
  headers: [
    'Idempotency-Key' => '550e8400-e29b-41d4-a716-446655440000'
  ]
);
```

#### Response

[](#response-10)

```
{
  "success": true
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

```
// HTTP 400 Bad Request
{
  "success": false,
  "message": "An error message here."
}
```

---

### transactional-&gt;send()

[](#transactional-send)

Send a transactional email to a contact. [Learn about sending transactional email](https://loops.so/docs/transactional/guide)

[API Reference](https://loops.so/docs/api-reference/send-transactional-email)

#### Parameters

[](#parameters-11)

NameTypeRequiredNotes`$transactional_id`stringYesThe ID of the transactional email to send.`$email`stringYesThe email address of the recipient.`$add_to_audience`booleanNoIf `true`, a contact will be created in your audience using the `$email` value (if a matching contact doesn’t already exist).`$data_variables`arrayNoAn array containing data as defined by the data variables added to the transactional email template.
Values can be of type `string` or `number`.`$attachments`array\[\]NoA list of attachments objects.
**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](https://loops.so/docs/transactional/attachments)`$attachments[]["filename"]`stringNoThe name of the file, shown in email clients.`$attachments[]["contentType"]`stringNoThe MIME type of the file.`$attachments[]["data"]`stringNoThe base64-encoded content of the file.`$headers`arrayNoAdditional headers to send with the request.#### Examples

[](#examples-4)

```
$result = $loops->transactional->send(
  transactional_id: 'clfq6dinn000yl70fgwwyp82l',
  email: 'hello@gmail.com',
  data_variables: [
    'loginUrl' => 'https://myapp.com/login/',
  ]
);

# Example with Idempotency-Key header
$result = $loops->transactional->send(
  transactional_id: 'clfq6dinn000yl70fgwwyp82l',
  email: 'hello@gmail.com',
  data_variables: [
    'loginUrl' => 'https://myapp.com/login/',
  ],
  headers: [
    'Idempotency-Key' => '550e8400-e29b-41d4-a716-446655440000'
  ]
);

# Please contact us to enable attachments on your account.
$result = $loops->transactional->send(
  transactional_id: 'clfq6dinn000yl70fgwwyp82l',
  email: 'hello@gmail.com',
  data_variables: [
    'loginUrl' => 'https://myapp.com/login/',
  ],
  attachments: [
    [
      'filename' => 'presentation.pdf',
      'contentType' => 'application/pdf',
      'data' => base64_encode(file_get_contents('path/to/presentation.pdf'))
    ]
  ]
);
```

#### Response

[](#response-11)

```
{
  "success": true
}
```

Error handling is done through the `APIError` class, which provides `getStatusCode()` and `getJson()` methods for retrieving the API's error response details. For implementation examples, see the [Usage section](#usage).

If there is a problem with the request, a descriptive error message will be returned:

```
// HTTP 400 Bad Request
{
  "success": false,
  "path": "dataVariables",
  "message": "There are required fields for this email. You need to include a 'dataVariables' object with the required fields."
}
```

```
// HTTP 400 Bad Request
{
  "success": false,
  "error": {
    "path": "dataVariables",
    "message": "Missing required fields: login_url"
  },
  "transactionalId": "clfq6dinn000yl70fgwwyp82l"
}
```

---

### transactional-&gt;list()

[](#transactional-list)

Get a paginated list of transactional emails, most recently created first.

[API Reference](https://loops.so/docs/api-reference/list-transactional-emails)

#### Parameters

[](#parameters-12)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-7)

```
$result = $loops->transactional->list();

$result = $loops->transactional->list(per_page: 15);
```

#### Response

[](#response-12)

```
{
  "pagination": {
    "totalResults": 23,
    "returnedResults": 20,
    "perPage": 20,
    "totalPages": 2,
    "nextCursor": "clyo0q4wo01p59fsecyxqsh38",
    "nextPage": "https://app.loops.so/api/v1/transactional-emails?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20"
  },
  "data": [
    {
      "id": "clfn0k1yg001imo0fdeqg30i8",
      "name": "Welcome email",
      "draftEmailMessageId": null,
      "publishedEmailMessageId": "cly8k3m0n0044jpx2bghepq45",
      "createdAt": "2023-11-06T17:48:07.249Z",
      "updatedAt": "2023-11-06T17:48:07.249Z",
      "dataVariables": []
    },
    {
      "id": "cll42l54f20i1la0lfooe3z12",
      "name": "Password reset",
      "draftEmailMessageId": "cla3r8s9t0422ua56iqovab01",
      "publishedEmailMessageId": "clb4s9t0u0533vb67jrpwbc12",
      "createdAt": "2025-01-15T10:00:00.000Z",
      "updatedAt": "2025-02-02T02:56:28.845Z",
      "dataVariables": [
        "confirmationUrl"
      ]
    },
    {
      "id": "clw6rbuwp01rmeiyndm80155l",
      "name": "Team invite",
      "draftEmailMessageId": "clc5t0u1v0644wc78ksqxcd23",
      "publishedEmailMessageId": null,
      "createdAt": "2024-05-14T19:02:52.000Z",
      "updatedAt": "2024-05-14T19:02:52.000Z",
      "dataVariables": [
        "firstName",
        "lastName",
        "inviteLink"
      ]
    },
    ...
  ]
}
```

---

### transactional-&gt;create()

[](#transactional-create)

Create a new transactional email. An empty draft email message is created automatically.

[API Reference](https://loops.so/docs/api-reference/create-transactional-email)

#### Parameters

[](#parameters-13)

NameTypeRequiredNotes`$name`stringYesThe name of the transactional email.`$transactional_group_id`stringNoThe ID of the group to add this transactional email to. Defaults to the team's default group when omitted.#### Example

[](#example-8)

```
$result = $loops->transactional->create(name: 'Welcome email');
```

#### Response

[](#response-13)

```
{
  "id": "clfq6dinn000yl70fgwwyp82l",
  "name": "Welcome email",
  "draftEmailMessageId": "cly8k3m0n0044jpx2bghepq45",
  "draftEmailMessageContentRevisionId": "clm9n4o6p0088lrz4dijslt67",
  "publishedEmailMessageId": null,
  "createdAt": "2025-01-01T00:00:00.000Z",
  "updatedAt": "2025-01-01T00:00:00.000Z",
  "dataVariables": []
}
```

---

### transactional-&gt;get()

[](#transactional-get)

Get a single transactional email by ID.

[API Reference](https://loops.so/docs/api-reference/get-transactional-email)

#### Parameters

[](#parameters-14)

NameTypeRequiredNotes`$transactional_id`stringYesThe ID of the transactional email.#### Example

[](#example-9)

```
$result = $loops->transactional->get(transactional_id: 'clfq6dinn000yl70fgwwyp82l');
```

---

### transactional-&gt;update()

[](#transactional-update)

Update a transactional email.

At least one field alongside `transactional_id` must be provided.

[API Reference](https://loops.so/docs/api-reference/update-transactional-email)

#### Parameters

[](#parameters-15)

NameTypeRequiredNotes`$transactional_id`stringYesThe ID of the transactional email.`$name`stringNoThe updated name.`$transactional_group_id`stringNoThe ID of the group to move this transactional email to.#### Example

[](#example-10)

```
$result = $loops->transactional->update(
  transactional_id: 'clfq6dinn000yl70fgwwyp82l',
  name: 'Updated welcome email'
);
```

---

### transactional-&gt;ensureDraft()

[](#transactional-ensuredraft)

Ensure a transactional email has a draft email message. If a draft already exists it is returned unchanged; otherwise a new empty draft is created.

[API Reference](https://loops.so/docs/api-reference/ensure-transactional-email-draft)

#### Parameters

[](#parameters-16)

NameTypeRequiredNotes`$transactional_id`stringYesThe ID of the transactional email.#### Example

[](#example-11)

```
$result = $loops->transactional->ensureDraft(transactional_id: 'clfq6dinn000yl70fgwwyp82l');
```

---

### transactional-&gt;publish()

[](#transactional-publish)

Publish the transactional email's current draft email message.

[API Reference](https://loops.so/docs/api-reference/publish-transactional-email)

#### Parameters

[](#parameters-17)

NameTypeRequiredNotes`$transactional_id`stringYesThe ID of the transactional email.#### Example

[](#example-12)

```
$result = $loops->transactional->publish(transactional_id: 'clfq6dinn000yl70fgwwyp82l');
```

---

### uploads-&gt;upload()

[](#uploads-upload)

Upload an image asset for use in LMX email content. The returned `finalUrl` can be used in an `` tag in your [LMX content](https://loops.so/docs/creating-emails/lmx).

[API Reference](https://loops.so/docs/api-reference/create-upload)

#### Parameters

[](#parameters-18)

NameTypeRequiredNotes`$path`stringYesPath to an image file. Supported types: JPEG, PNG, GIF, and WebP. Maximum file size is 4,000,000 bytes (4 MB).#### Example

[](#example-13)

```
$result = $loops->uploads->upload(path: '/path/to/image.png');

$imageUrl = $result['finalUrl'];
```

#### Response

[](#response-14)

```
{
  "emailAssetId": "clu1v4w6x0254tz42lrcwat45",
  "finalUrl": "https://cdn.example.com/image.png"
}
```

---

### dedicatedSendingIps-&gt;list()

[](#dedicatedsendingips-list)

Get a list of Loops' dedicated sending IP addresses.

[API Reference](https://loops.so/docs/api-reference/get-dedicated-sending-ips)

#### Parameters

[](#parameters-19)

None

#### Example

[](#example-14)

```
$result = $loops->dedicatedSendingIps->list();
```

#### Response

[](#response-15)

```
["1.2.3.4", "5.6.7.8"]
```

---

### themes-&gt;list()

[](#themes-list)

List email themes.

[API Reference](https://loops.so/docs/api-reference/list-themes)

#### Parameters

[](#parameters-20)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-15)

```
$result = $loops->themes->list();

$result = $loops->themes->list(per_page: 15, cursor: 'clyo0q4wo01p59fsecyxqsh38');
```

---

### themes-&gt;get()

[](#themes-get)

Get a single email theme by ID.

[API Reference](https://loops.so/docs/api-reference/get-theme)

#### Parameters

[](#parameters-21)

NameTypeRequiredNotes`$theme_id`stringYesThe ID of the theme.#### Example

[](#example-16)

```
$result = $loops->themes->get(theme_id: 'clo5p8q0r0132ntx6flkunw89');
```

---

### components-&gt;list()

[](#components-list)

List email components.

[API Reference](https://loops.so/docs/api-reference/list-components)

#### Parameters

[](#parameters-22)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-17)

```
$result = $loops->components->list();
```

---

### components-&gt;get()

[](#components-get)

Get a single email component by ID.

[API Reference](https://loops.so/docs/api-reference/get-component)

#### Parameters

[](#parameters-23)

NameTypeRequiredNotes`$component_id`stringYesThe ID of the component.#### Example

[](#example-18)

```
$result = $loops->components->get(component_id: 'clp6q9r1s0154ouy7gmlovx90');
```

---

### campaigns-&gt;list()

[](#campaigns-list)

List campaigns.

[API Reference](https://loops.so/docs/api-reference/list-campaigns)

#### Parameters

[](#parameters-24)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-19)

```
$result = $loops->campaigns->list();
```

---

### campaigns-&gt;create()

[](#campaigns-create)

Create a new draft campaign.

[API Reference](https://loops.so/docs/api-reference/create-campaign)

#### Parameters

[](#parameters-25)

NameTypeRequiredNotes`$name`stringYesThe campaign name.`$campaign_group_id`stringNoThe ID of the group to add this campaign to.`$mailing_list_id`stringNoThe ID of the mailing list to send to.`$audience_segment_id`stringNoThe ID of an audience segment. Setting this clears any `audience_filter`.`$audience_filter`arrayNoA tree of audience conditions. See the API reference for the filter schema.`$scheduling`arrayNoWhen the campaign should send. Use `['method' => 'now']` or `['method' => 'schedule', 'timestamp' => '...']`.#### Example

[](#example-20)

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

$result = $loops->campaigns->create(
  name: 'Spring announcement',
  mailing_list_id: 'cm06f5v0e45nf0ml5754o9cix',
  scheduling: ['method' => 'schedule', 'timestamp' => '2026-06-01T10:00:00Z']
);
```

#### Response

[](#response-16)

```
{
  "success": true,
  "campaignId": "cln4o7p9q0110msw5ekjtmv78",
  "name": "Spring announcement",
  "status": "Draft",
  "createdAt": "2025-01-01T00:00:00.000Z",
  "updatedAt": "2025-01-01T00:00:00.000Z",
  "emailMessageId": "cly8k3m0n0044jpx2bghepq45",
  "emailMessageContentRevisionId": "clm9n4o6p0088lrz4dijslt67"
}
```

---

### campaigns-&gt;get()

[](#campaigns-get)

Get a single campaign by ID.

[API Reference](https://loops.so/docs/api-reference/get-campaign)

#### Parameters

[](#parameters-26)

NameTypeRequiredNotes`$campaign_id`stringYesThe ID of the campaign.#### Example

[](#example-21)

```
$result = $loops->campaigns->get(campaign_id: 'cln4o7p9q0110msw5ekjtmv78');
```

---

### campaigns-&gt;update()

[](#campaigns-update)

Update a draft campaign's name, group, audience, or scheduling.

At least one field alongside `campaign_id` must be provided.

[API Reference](https://loops.so/docs/api-reference/update-campaign)

#### Parameters

[](#parameters-27)

NameTypeRequiredNotes`$campaign_id`stringYesThe ID of the campaign.`$name`stringNoThe updated name.`$campaign_group_id`stringNoThe ID of the group to move this campaign to.`$scheduling`arrayNoWhen the campaign should send. Use `['method' => 'now']` or `['method' => 'schedule', 'timestamp' => '...']`.`$mailing_list_id`stringNoThe ID of the mailing list to send to. Pass `null` to clear.`$audience_segment_id`stringNoThe ID of an audience segment. Setting this clears any `audience_filter`. Pass `null` to clear.`$audience_filter`arrayNoA tree of audience conditions. See the API reference for the filter schema. Pass `null` to clear.#### Example

[](#example-22)

```
$result = $loops->campaigns->update(
  campaign_id: 'cln4o7p9q0110msw5ekjtmv78',
  name: 'Updated name'
);

// Clear the mailing list audience target
$result = $loops->campaigns->update(
  campaign_id: 'cln4o7p9q0110msw5ekjtmv78',
  mailing_list_id: null
);
```

---

### campaignGroups-&gt;list()

[](#campaigngroups-list)

List campaign groups.

[API Reference](https://loops.so/docs/api-reference/list-campaign-groups)

#### Parameters

[](#parameters-28)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-23)

```
$result = $loops->campaignGroups->list();
```

---

### campaignGroups-&gt;create()

[](#campaigngroups-create)

Create a campaign group.

[API Reference](https://loops.so/docs/api-reference/create-campaign-group)

#### Parameters

[](#parameters-29)

NameTypeRequiredNotes`$name`stringYesCannot be the reserved name "Unsorted".`$description`stringNoAn optional description for the group.#### Example

[](#example-24)

```
$result = $loops->campaignGroups->create(name: 'Newsletters', description: 'Monthly updates');
```

---

### campaignGroups-&gt;get()

[](#campaigngroups-get)

Get a campaign group by ID.

[API Reference](https://loops.so/docs/api-reference/get-campaign-group)

#### Parameters

[](#parameters-30)

NameTypeRequiredNotes`$campaign_group_id`stringYesThe ID of the campaign group.#### Example

[](#example-25)

```
$result = $loops->campaignGroups->get(campaign_group_id: 'clq7r0s2t0176pvz8hnmpwy01');
```

---

### campaignGroups-&gt;update()

[](#campaigngroups-update)

Update a campaign group's name or description.

At least one field alongside `campaign_group_id` must be provided.

[API Reference](https://loops.so/docs/api-reference/update-campaign-group)

#### Parameters

[](#parameters-31)

NameTypeRequiredNotes`$campaign_group_id`stringYesThe ID of the campaign group.`$name`stringNoCannot be the reserved name "Unsorted".`$description`stringNo#### Example

[](#example-26)

```
$result = $loops->campaignGroups->update(
  campaign_group_id: 'clq7r0s2t0176pvz8hnmpwy01',
  name: 'Updated name'
);
```

---

### audienceSegments-&gt;list()

[](#audiencesegments-list)

List audience segments.

[API Reference](https://loops.so/docs/api-reference/list-audience-segments)

#### Parameters

[](#parameters-32)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-27)

```
$result = $loops->audienceSegments->list();
```

---

### audienceSegments-&gt;get()

[](#audiencesegments-get)

Get an audience segment by ID.

[API Reference](https://loops.so/docs/api-reference/get-audience-segment)

#### Parameters

[](#parameters-33)

NameTypeRequiredNotes`$audience_segment_id`stringYesThe ID of the audience segment.#### Example

[](#example-28)

```
$result = $loops->audienceSegments->get(audience_segment_id: 'clr8s1t3u0198qw09iotqzx12');
```

---

### workflows-&gt;list()

[](#workflows-list)

List workflows.

[API Reference](https://loops.so/docs/api-reference/list-workflows)

#### Parameters

[](#parameters-34)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-29)

```
$result = $loops->workflows->list();
```

---

### workflows-&gt;get()

[](#workflows-get)

Get a simplified workflow graph.

[API Reference](https://loops.so/docs/api-reference/get-workflow)

#### Parameters

[](#parameters-35)

NameTypeRequiredNotes`$workflow_id`stringYesThe ID of the workflow.#### Example

[](#example-30)

```
$result = $loops->workflows->get(workflow_id: 'cls9t2u4v0210rx20jpuary23');
```

---

### workflows-&gt;getNode()

[](#workflows-getnode)

Get detailed data for a single workflow node.

[API Reference](https://loops.so/docs/api-reference/get-workflow-node)

#### Parameters

[](#parameters-36)

NameTypeRequiredNotes`$workflow_id`stringYesThe ID of the workflow.`$node_id`stringYesThe ID of the workflow node.#### Example

[](#example-31)

```
$result = $loops->workflows->getNode(
  workflow_id: 'cls9t2u4v0210rx20jpuary23',
  node_id: 'clt0u3v5w0232sy31kqvbzs34'
);
```

---

### emailMessages-&gt;get()

[](#emailmessages-get)

Get an email message, including its compiled LMX content.

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

#### Parameters

[](#parameters-37)

NameTypeRequiredNotes`$email_message_id`stringYesThe ID of the email message.#### Example

[](#example-32)

```
$result = $loops->emailMessages->get(email_message_id: 'cly8k3m0n0044jpx2bghepq45');
```

---

### emailMessages-&gt;update()

[](#emailmessages-update)

Update an email message.

At least one field alongside `email_message_id` must be provided.

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

#### Parameters

[](#parameters-38)

NameTypeRequiredNotes`$email_message_id`stringYesThe ID of the email message.`$expected_revision_id`stringNoSupply a value matching the current `contentRevisionId` to avoid 409 conflicts.`$subject`stringNoThe email subject line.`$preview_text`stringNoThe email preview text.`$from_name`stringNoThe sender name.`$from_email`stringNoThe sender email address (the name before the `@`; your sending domain will be automatically appended).`$reply_to_email`stringNoThe reply-to email address.`$lmx`stringNoThe LMX content for the email message.`$contact_properties_fallbacks`arrayNoContact property fallback values. Pass `null` as a value to remove an individual fallback entry.`$event_properties_fallbacks`arrayNoEvent property fallback values. Pass `null` as a value to remove an individual fallback entry.`$data_variables_fallbacks`arrayNoData variable fallback values. Pass `null` as a value to remove an individual fallback entry.#### Example

[](#example-33)

```
$result = $loops->emailMessages->update(
  email_message_id: 'cly8k3m0n0044jpx2bghepq45',
  expected_revision_id: 'clm9n4o6p0088lrz4dijslt67',
  subject: 'Updated subject',
  lmx: 'Hello'
);

// Example with contact property fallbacks
$result = $loops->emailMessages->update(
  email_message_id: 'cly8k3m0n0044jpx2bghepq45',
  contact_properties_fallbacks: [
    'firstName' => 'there',      // If firstName is missing, use "there"
    'company' => 'your company', // If company is missing, use "your company"
    'planName' => null           // null removes the fallback for "planName"
  ]
);
```

---

### emailMessages-&gt;preview()

[](#emailmessages-preview)

Send a test preview of an email message to one or more addresses.

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

#### Parameters

[](#parameters-39)

NameTypeRequiredNotes`$email_message_id`stringYesThe ID of the email message.`$emails`arrayYesOne or more addresses to send the preview to.`$contact_properties`arrayNoContact property values to render. Accepted for campaign and workflow previews.`$event_properties`arrayNoEvent property values to render. Accepted for workflow previews only.`$data_variables`arrayNoTransactional data variables to render. Accepted for transactional previews only.#### Example

[](#example-34)

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

---

### transactionalGroups-&gt;list()

[](#transactionalgroups-list)

List transactional groups.

[API Reference](https://loops.so/docs/api-reference/list-transactional-groups)

#### Parameters

[](#parameters-40)

NameTypeRequiredNotes`$per_page`integerNoHow many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.`$cursor`stringNoA cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response.#### Example

[](#example-35)

```
$result = $loops->transactionalGroups->list();
```

---

### transactionalGroups-&gt;create()

[](#transactionalgroups-create)

Create a transactional group.

[API Reference](https://loops.so/docs/api-reference/create-transactional-group)

#### Parameters

[](#parameters-41)

NameTypeRequiredNotes`$name`stringYesCannot be the reserved name "Unsorted".`$description`stringNoAn optional description for the group.#### Example

[](#example-36)

```
$result = $loops->transactionalGroups->create(name: 'Account emails');
```

---

### transactionalGroups-&gt;get()

[](#transactionalgroups-get)

Get a transactional group by ID.

[API Reference](https://loops.so/docs/api-reference/get-transactional-group)

#### Parameters

[](#parameters-42)

NameTypeRequiredNotes`$transactional_group_id`stringYesThe ID of the transactional group.#### Example

[](#example-37)

```
$result = $loops->transactionalGroups->get(transactional_group_id: 'clv2w3x4y0288xbb0kqrsuv67');
```

---

### transactionalGroups-&gt;update()

[](#transactionalgroups-update)

Update a transactional group's name or description.

At least one field alongside `transactional_group_id` must be provided.

[API Reference](https://loops.so/docs/api-reference/update-transactional-group)

#### Parameters

[](#parameters-43)

NameTypeRequiredNotes`$transactional_group_id`stringYesThe ID of the transactional group.`$name`stringNoCannot be the reserved name "Unsorted".`$description`stringNo#### Example

[](#example-38)

```
$result = $loops->transactionalGroups->update(
  transactional_group_id: 'clv2w3x4y0288xbb0kqrsuv67',
  name: 'Updated name'
);
```

---

Testing
-------

[](#testing)

```
vendor/bin/phpunit
```

---

Contributing
------------

[](#contributing)

Bug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance99

Actively maintained with recent releases

Popularity31

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~57 days

Recently: every ~75 days

Total

9

Last Release

3d ago

Major Versions

v0.1.0 → v1.0.02025-05-06

v1.0.2 → v2.0.02025-09-01

v2.1.0 → v3.0.02026-05-21

PHP version history (2 changes)v0.1.0PHP ^8.0

v3.1.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/63fd256c61e7fa59f97ef683f6a182d97766b41554e22a53ff1a7c5e9914d7fe?d=identicon)[danrowden](/maintainers/danrowden)

---

Top Contributors

[![danrowden](https://avatars.githubusercontent.com/u/935215?v=4)](https://github.com/danrowden "danrowden (40 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/loops-so-loops/health.svg)

```
[![Health](https://phpackages.com/badges/loops-so-loops/health.svg)](https://phpackages.com/packages/loops-so-loops)
```

###  Alternatives

[statamic/cms

The Statamic CMS Core Package

4.8k3.6M985](/packages/statamic-cms)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3741.3M47](/packages/tencentcloud-tencentcloud-sdk-php)[neuron-core/neuron-ai

The PHP Agentic Framework.

2.0k656.1k38](/packages/neuron-core-neuron-ai)[avalara/avataxclient

Client library for Avalara's AvaTax suite of business tax calculation and processing services. Uses the REST v2 API.

528.5M7](/packages/avalara-avataxclient)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

273.0k](/packages/eslazarev-wildberries-sdk)[files.com/files-php-sdk

Files.com PHP SDK

2481.1k](/packages/filescom-files-php-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
