loops-python-unofficial


Nameloops-python-unofficial JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryUnofficial Python SDK for Loops (loops.so) with sync and async clients
upload_time2025-08-09 14:44:20
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords email loops marketing sdk transactional
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Unofficial Loops Python SDK

## Introduction

Python SDK for [Loops](https://loops.so), with both sync and async clients. Mirrors the public JS SDK where it makes sense.

## Installation

```bash
pip install loops-python-unofficial
```

You will need a Loops API key from your account settings.

## Quick start

### Sync

```python
from loops_unofficial import LoopsClient, APIError, RateLimitExceededError

client = LoopsClient("<YOUR_API_KEY>")

try:
    resp = client.create_contact("email@provider.com")
except RateLimitExceededError as e:
    print(f"Rate limit exceeded ({e.limit}/s), remaining={e.remaining}")
except APIError as e:
    print(e.status_code, e.json)
```

## Handling rate limits

If you import `RateLimitExceededError` you can check for rate limit issues with your requests.

You can access details about the rate limits from the `limit` and `remaining` attributes.

### Async

```python
import asyncio
from loops_unofficial import AsyncLoopsClient

async def main():
    client = AsyncLoopsClient("<YOUR_API_KEY>")
    try:
        resp = await client.create_contact("email@provider.com")
    finally:
        await client.aclose()

asyncio.run(main())
```

## 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`

## 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

- test_api_key()
- create_contact(email, properties=None, mailing_lists=None)
- update_contact(email, properties, mailing_lists=None)
- find_contact(email=None, user_id=None)
- delete_contact(email=None, user_id=None)
- create*contact_property(name, type*)
- get*contact_properties(list*=None)
- get_mailing_lists()
- send_event(email=None, user_id=None, event_name=..., contact_properties=None, event_properties=None, mailing_lists=None, headers=None)
- send_transactional_email(transactional_id, email, add_to_audience=None, data_variables=None, attachments=None, headers=None)
- get_transactional_emails(per_page=None, cursor=None)

---

### testApiKey()

Test that an API key is valid.

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

#### Parameters

None

#### Example (Python)

```python
from loops_unofficial import LoopsClient

client = LoopsClient("<API_KEY>")
resp = client.test_api_key()
```

#### Response

```json
{
  "success": true,
  "teamName": "My team"
}
```

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

```json
HTTP 401 Unauthorized
{
  "error": "Invalid API key"
}
```

---

### createContact()

Create a new contact.

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

#### Parameters

| Name           | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`        | string | Yes      | If a contact already exists with this email address, an error response will be returned.                                                                                                                                                                                                                                                                                                                             |
| `properties`   | object | No       | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |
| `mailingLists` | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                     |

#### Examples (Python)

```python
from loops_unofficial import LoopsClient

client = LoopsClient("<API_KEY>")

# Minimal
resp = client.create_contact("hello@gmail.com")

# With properties and mailing lists
contact_properties = {"firstName": "Bob", "favoriteColor": "Red"}
mailing_lists = {"cm06f5v0e45nf0ml5754o9cix": True, "cm16k73gq014h0mmj5b6jdi9r": False}
resp = client.create_contact("hello@gmail.com", contact_properties, mailing_lists)
```

#### Response

```json
{
  "success": true,
  "id": "id_of_contact"
}
```

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

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

---

### updateContact()

Update a contact.

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

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

#### Parameters

| Name           | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`        | string | Yes      | The 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.                                                                                                                                                                                                                                            |
| `properties`   | object | No       | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |
| `mailingLists` | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                     |

#### Example (Python)

```python
client.update_contact("hello@gmail.com", {"firstName": "Bob", "favoriteColor": "Blue"})

# Updating a contact's email address using userId
client.update_contact("newemail@gmail.com", {"userId": "1234"})
```

#### Response

```json
{
  "success": true,
  "id": "id_of_contact"
}
```

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

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

---

### findContact()

Find a contact.

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

#### Parameters

You must use one parameter in the request.

| Name     | Type   | Required | Notes |
| -------- | ------ | -------- | ----- |
| `email`  | string | No       |       |
| `userId` | string | No       |       |

#### Examples (Python)

```python
client.find_contact(email="hello@gmail.com")
client.find_contact(user_id="12345")
```

#### Response

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.

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

---

### deleteContact()

Delete a contact, either by email address or `userId`.

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

#### Parameters

You must use one parameter in the request.

| Name     | Type   | Required | Notes |
| -------- | ------ | -------- | ----- |
| `email`  | string | No       |       |
| `userId` | string | No       |       |

#### Example (Python)

```python
client.delete_contact(email="hello@gmail.com")
client.delete_contact(user_id="12345")
```

#### Response

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

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

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

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

---

### createContactProperty()

Create a new contact property.

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

#### Parameters

| Name   | Type   | Required | Notes                                                                                  |
| ------ | ------ | -------- | -------------------------------------------------------------------------------------- |
| `name` | string | Yes      | The name of the property. Should be in camelCase, like `planName` or `favouriteColor`. |
| `type` | string | Yes      | The property's value type.<br />Can be one of `string`, `number`, `boolean` or `date`. |

#### Examples (Python)

```python
client.create_contact_property("planName", "string")
```

#### Response

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

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

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

---

### getContactProperties()

Get a list of your account's contact properties.

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

#### Parameters

| Name   | Type   | Required | Notes                                                           |
| ------ | ------ | -------- | --------------------------------------------------------------- |
| `list` | string | No       | Use "custom" to retrieve only your account's custom properties. |

#### Example (Python)

```python
client.get_contact_properties()
client.get_contact_properties("custom")
```

#### Response

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

```json
[
  {
    "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"
  }
]
```

---

### getMailingLists()

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

None

#### Example (Python)

```python
client.get_mailing_lists()
```

#### Response

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.

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

---

### sendEvent()

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

| Name                | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`             | string | No       | The contact's email address. Required if `userId` is not present.                                                                                                                                                                                                                                                                                                                                                                                              |
| `userId`            | string | No       | The contact's unique user ID. If you use `userId` without `email`, this value must have already been added to your contact in Loops. Required if `email` is not present.                                                                                                                                                                                                                                                                                       |
| `eventName`         | string | Yes      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `contactProperties` | object | No       | An object containing contact properties, which will be updated or added to the contact when the event is received.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |
| `eventProperties`   | object | No       | An object containing event properties, which will be made available in emails that are triggered by this event.<br />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)).                                                                                                                                                         |
| `mailingLists`      | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                                                               |
| `headers`           | object | No       | Additional headers to send with the request.                                                                                                                                                                                                                                                                                                                                                                                                                   |

#### Examples (Python)

```python
# Minimal
client.send_event(email="hello@gmail.com", event_name="signup")

# With event properties and mailing lists
client.send_event(
    email="hello@gmail.com",
    event_name="signup",
    event_properties={"username": "user1234", "signupDate": "2024-03-21T10:09:23Z"},
    mailing_lists={"cm06f5v0e45nf0ml5754o9cix": True, "cm16k73gq014h0mmj5b6jdi9r": False},
)

# With both email and userId and contact properties
client.send_event(
    user_id="1234567890",
    email="hello@gmail.com",
    event_name="signup",
    contact_properties={"firstName": "Bob", "plan": "pro"},
)

# With Idempotency-Key header
client.send_event(
    email="hello@gmail.com",
    event_name="signup",
    headers={"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000"},
)
```

#### Response

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

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

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

---

### sendTransactionalEmail()

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

| Name                        | Type     | Required | Notes                                                                                                                                                                                            |
| --------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `transactionalId`           | string   | Yes      | The ID of the transactional email to send.                                                                                                                                                       |
| `email`                     | string   | Yes      | The email address of the recipient.                                                                                                                                                              |
| `addToAudience`             | boolean  | No       | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn’t already exist).                                                                     |
| `dataVariables`             | object   | No       | An object containing data as defined by the data variables added to the transactional email template.<br />Values can be of type `string` or `number`.                                           |
| `attachments`               | object[] | No       | A list of attachments objects.<br />**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`    | string   | No       | The name of the file, shown in email clients.                                                                                                                                                    |
| `attachments[].contentType` | string   | No       | The MIME type of the file.                                                                                                                                                                       |
| `attachments[].data`        | string   | No       | The base64-encoded content of the file.                                                                                                                                                          |
| `headers`                   | object   | No       | Additional headers to send with the request.                                                                                                                                                     |

#### Examples (Python)

```python
# Minimal
client.send_transactional_email(
    transactional_id="clfq6dinn000yl70fgwwyp82l",
    email="hello@gmail.com",
    data_variables={"loginUrl": "https://myapp.com/login/"},
)

# With Idempotency-Key header
client.send_transactional_email(
    transactional_id="clfq6dinn000yl70fgwwyp82l",
    email="hello@gmail.com",
    data_variables={"loginUrl": "https://myapp.com/login/"},
    headers={"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000"},
)

# With attachments (requires attachments to be enabled on your account)
client.send_transactional_email(
    transactional_id="clfq6dinn000yl70fgwwyp82l",
    email="hello@gmail.com",
    data_variables={"loginUrl": "https://myapp.com/login/"},
    attachments=[
        {
            "filename": "presentation.pdf",
            "contentType": "application/pdf",
            "data": "JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...",
        }
    ],
)
```

#### Response

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

Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).

```json
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."
}
```

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

---

### getTransactionalEmails()

Get a list of published transactional emails.

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

#### Parameters

| Name      | Type    | Required | Notes                                                                                                                         |
| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `perPage` | integer | No       | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.                                    |
| `cursor`  | string  | No       | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |

#### Example (Python)

```python
client.get_transactional_emails()
client.get_transactional_emails(per_page=15)
```

#### Response

```json
{
  "pagination": {
    "totalResults": 23,
    "returnedResults": 20,
    "perPage": 20,
    "totalPages": 2,
    "nextCursor": "clyo0q4wo01p59fsecyxqsh38",
    "nextPage": "https://app.loops.so/api/v1/transactional?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20"
  },
  "data": [
    {
      "id": "clfn0k1yg001imo0fdeqg30i8",
      "lastUpdated": "2023-11-06T17:48:07.249Z",
      "dataVariables": []
    },
    {
      "id": "cll42l54f20i1la0lfooe3z12",
      "lastUpdated": "2025-02-02T02:56:28.845Z",
      "dataVariables": [
        "confirmationUrl"
      ]
    },
    {
      "id": "clw6rbuwp01rmeiyndm80155l",
      "lastUpdated": "2024-05-14T19:02:52.000Z",
      "dataVariables": [
        "firstName",
        "lastName",
        "inviteLink"
      ]
    },
    ...
  ]
}
```

---

## Version history

- `v5.0.1` (May 13, 2025) - Added a `headers` parameter for [`sendEvent()`](#sendevent) and [`sendTransactionalEmail()`](#sendtransactionalemail), enabling support for the `Idempotency-Key` header.
- `v5.0.0` (Apr 29, 2025)
  - Types are now exported so you can use them in your application.
  - `ValidationError` is now thrown when parameters are not added correctly.
  - `Error` is now returned if the API key is missing.
  - Added tests.
- `v4.1.0` (Feb 27, 2025) - Support for new [List transactional emails](#gettransactionalemails) endpoint.
- `v4.0.0` (Jan 16, 2025)
  - Added `APIError` to more easily understand API errors. [See usage example](#usage).
  - Added support for two new contact property endpoints: [List contact properties](#listcontactproperties) and [Create contact property](#createcontactproperty).
  - Deprecated and removed the `getCustomFields()` method (you can now use [`listContactProperties()`](#listcontactproperties) instead).
- `v3.4.1` (Dec 18, 2024) - Support for a new `description` attribute in [`getMailingLists()`](#getmailinglists).
- `v3.4.0` (Oct 29, 2024) - Added rate limit handling with [`RateLimitExceededError`](#handling-rate-limits).
- `v3.3.0` (Sep 9, 2024) - Added [`testApiKey()`](#testapikey) method.
- `v3.2.0` (Aug 23, 2024) - Added support for a new `mailingLists` attribute in [`findContact()`](#findcontact).
- `v3.1.1` (Aug 16, 2024) - Support for a new `isPublic` attribute in [`getMailingLists()`](#getmailinglists).
- `v3.1.0` (Aug 12, 2024) - The SDK now accepts `null` as a value for contact properties in `createContact()`, `updateContact()` and `sendEvent()`, which allows you to reset/empty properties.
- `v3.0.0` (Jul 2, 2024) - [`sendTransactionalEmail()`](#sendtransactionalemail) now accepts an object instead of separate parameters (breaking change).
- `v2.2.0` (Jul 2, 2024) - Deprecated. Added new `addToAudience` option to [`sendTransactionalEmail()`](#sendtransactionalemail).
- `v2.1.1` (Jun 20, 2024) - Added support for mailing lists in [`createContact()`](#createcontact), [`updateContact()`](#updatecontact) and [`sendEvent()`](#sendevent).
- `v2.1.0` (Jun 19, 2024) - Added support for new [List mailing lists](#getmailinglists) endpoint.
- `v2.0.0` (Apr 19, 2024)
  - Added `userId` as a parameter to [`findContact()`](#findcontact). This includes a breaking change for the `findContact()` parameters.
  - `userId` values must now be strings (could have also been numbers previously).
- `v1.0.1` (Apr 1, 2024) - Fixed types for `sendEvent()`.
- `v1.0.0` (Mar 28, 2024) - Fix for ESM types. Switched to named export.
- `v0.4.0` (Mar 22, 2024) - Support for new `eventProperties` in [`sendEvent()`](#sendevent). This includes a breaking change for the `sendEvent()` parameters.
- `v0.3.0` (Feb 22, 2024) - Updated minimum Node version to 18.0.0.
- `v0.2.1` (Feb 6, 2024) - Fix for ESM imports.
- `v0.2.0` (Feb 1, 2024) - CommonJS support.
- `v0.1.5` (Jan 25, 2024) - `getCustomFields()` now returns `type` values for each contact property.
- `v0.1.4` (Jan 25, 2024) - Added support for `userId` in [`sendEvent()`](#sendevent) request. Added missing error response type for `sendEvent()` requests.
- `v0.1.3` (Dec 8, 2023) - Added support for transactional attachments.
- `v0.1.2` (Dec 6, 2023) - Improved transactional error types.
- `v0.1.1` (Nov 1, 2023) - Initial release.

---

## Development

This project uses `uv`.

```bash
uv sync
uv run pytest
uv run ruff check
uv run mypy .
```

## Publishing

Use GitHub Actions with PyPI Trusted Publishing.

Workflow:

- Tag a release locally: `git tag v0.2.0 && git push --tags`.
- The workflow `.github/workflows/release.yml` builds and publishes to PyPI on tag push using [PyPA’s official action](https://github.com/marketplace/actions/pypi-publish).
- To publish to TestPyPI manually, trigger the workflow (Run workflow) and choose `testpypi`.

### Installing from TestPyPI

If you published to TestPyPI, install with an explicit index URL:

```bash
pip install --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple \
            loops-python-unofficial
```

### Releasing

1. Update version in `pyproject.toml`.
2. Create a tag: `git tag vX.Y.Z && git push --tags`.
3. Workflow builds with `uv build` and publishes via trusted publishing.
4. Alternatively dispatch the workflow and select `testpypi` to publish to TestPyPI.

---

## Contributing

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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "loops-python-unofficial",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "email, loops, marketing, sdk, transactional",
    "author": null,
    "author_email": "VersionLens <support@versionlens.com>",
    "download_url": "https://files.pythonhosted.org/packages/c5/eb/ef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742/loops_python_unofficial-0.1.0.tar.gz",
    "platform": null,
    "description": "# Unofficial Loops Python SDK\n\n## Introduction\n\nPython SDK for [Loops](https://loops.so), with both sync and async clients. Mirrors the public JS SDK where it makes sense.\n\n## Installation\n\n```bash\npip install loops-python-unofficial\n```\n\nYou will need a Loops API key from your account settings.\n\n## Quick start\n\n### Sync\n\n```python\nfrom loops_unofficial import LoopsClient, APIError, RateLimitExceededError\n\nclient = LoopsClient(\"<YOUR_API_KEY>\")\n\ntry:\n    resp = client.create_contact(\"email@provider.com\")\nexcept RateLimitExceededError as e:\n    print(f\"Rate limit exceeded ({e.limit}/s), remaining={e.remaining}\")\nexcept APIError as e:\n    print(e.status_code, e.json)\n```\n\n## Handling rate limits\n\nIf you import `RateLimitExceededError` you can check for rate limit issues with your requests.\n\nYou can access details about the rate limits from the `limit` and `remaining` attributes.\n\n### Async\n\n```python\nimport asyncio\nfrom loops_unofficial import AsyncLoopsClient\n\nasync def main():\n    client = AsyncLoopsClient(\"<YOUR_API_KEY>\")\n    try:\n        resp = await client.create_contact(\"email@provider.com\")\n    finally:\n        await client.aclose()\n\nasyncio.run(main())\n```\n\n## Default contact properties\n\nEach contact in Loops has a set of default properties. These will always be returned in API results.\n\n- `id`\n- `email`\n- `firstName`\n- `lastName`\n- `source`\n- `subscribed`\n- `userGroup`\n- `userId`\n\n## Custom contact properties\n\nYou 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.\n\n## Methods\n\n- test_api_key()\n- create_contact(email, properties=None, mailing_lists=None)\n- update_contact(email, properties, mailing_lists=None)\n- find_contact(email=None, user_id=None)\n- delete_contact(email=None, user_id=None)\n- create*contact_property(name, type*)\n- get*contact_properties(list*=None)\n- get_mailing_lists()\n- send_event(email=None, user_id=None, event_name=..., contact_properties=None, event_properties=None, mailing_lists=None, headers=None)\n- send_transactional_email(transactional_id, email, add_to_audience=None, data_variables=None, attachments=None, headers=None)\n- get_transactional_emails(per_page=None, cursor=None)\n\n---\n\n### testApiKey()\n\nTest that an API key is valid.\n\n[API Reference](https://loops.so/docs/api-reference/api-key)\n\n#### Parameters\n\nNone\n\n#### Example (Python)\n\n```python\nfrom loops_unofficial import LoopsClient\n\nclient = LoopsClient(\"<API_KEY>\")\nresp = client.test_api_key()\n```\n\n#### Response\n\n```json\n{\n  \"success\": true,\n  \"teamName\": \"My team\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 401 Unauthorized\n{\n  \"error\": \"Invalid API key\"\n}\n```\n\n---\n\n### createContact()\n\nCreate a new contact.\n\n[API Reference](https://loops.so/docs/api-reference/create-contact)\n\n#### Parameters\n\n| Name           | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                |\n| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email`        | string | Yes      | If a contact already exists with this email address, an error response will be returned.                                                                                                                                                                                                                                                                                                                             |\n| `properties`   | object | No       | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |\n| `mailingLists` | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                     |\n\n#### Examples (Python)\n\n```python\nfrom loops_unofficial import LoopsClient\n\nclient = LoopsClient(\"<API_KEY>\")\n\n# Minimal\nresp = client.create_contact(\"hello@gmail.com\")\n\n# With properties and mailing lists\ncontact_properties = {\"firstName\": \"Bob\", \"favoriteColor\": \"Red\"}\nmailing_lists = {\"cm06f5v0e45nf0ml5754o9cix\": True, \"cm16k73gq014h0mmj5b6jdi9r\": False}\nresp = client.create_contact(\"hello@gmail.com\", contact_properties, mailing_lists)\n```\n\n#### Response\n\n```json\n{\n  \"success\": true,\n  \"id\": \"id_of_contact\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### updateContact()\n\nUpdate a contact.\n\nNote: To update a contact's email address, the contact requires a `userId` value. Then you can make a request with their `userId` and an updated email address.\n\n[API Reference](https://loops.so/docs/api-reference/update-contact)\n\n#### Parameters\n\n| Name           | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                |\n| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email`        | string | Yes      | The 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.                                                                                                                                                                                                                                            |\n| `properties`   | object | No       | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |\n| `mailingLists` | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                     |\n\n#### Example (Python)\n\n```python\nclient.update_contact(\"hello@gmail.com\", {\"firstName\": \"Bob\", \"favoriteColor\": \"Blue\"})\n\n# Updating a contact's email address using userId\nclient.update_contact(\"newemail@gmail.com\", {\"userId\": \"1234\"})\n```\n\n#### Response\n\n```json\n{\n  \"success\": true,\n  \"id\": \"id_of_contact\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### findContact()\n\nFind a contact.\n\n[API Reference](https://loops.so/docs/api-reference/find-contact)\n\n#### Parameters\n\nYou must use one parameter in the request.\n\n| Name     | Type   | Required | Notes |\n| -------- | ------ | -------- | ----- |\n| `email`  | string | No       |       |\n| `userId` | string | No       |       |\n\n#### Examples (Python)\n\n```python\nclient.find_contact(email=\"hello@gmail.com\")\nclient.find_contact(user_id=\"12345\")\n```\n\n#### Response\n\nThis method will return a list containing a single contact object, which will include all default properties and any custom properties.\n\nIf no contact is found, an empty list will be returned.\n\n```json\n[\n  {\n    \"id\": \"cll6b3i8901a9jx0oyktl2m4u\",\n    \"email\": \"hello@gmail.com\",\n    \"firstName\": \"Bob\",\n    \"lastName\": null,\n    \"source\": \"API\",\n    \"subscribed\": true,\n    \"userGroup\": \"\",\n    \"userId\": \"12345\",\n    \"mailingLists\": {\n      \"cm06f5v0e45nf0ml5754o9cix\": true\n    },\n    \"favoriteColor\": \"Blue\" /* Custom property */\n  }\n]\n```\n\n---\n\n### deleteContact()\n\nDelete a contact, either by email address or `userId`.\n\n[API Reference](https://loops.so/docs/api-reference/delete-contact)\n\n#### Parameters\n\nYou must use one parameter in the request.\n\n| Name     | Type   | Required | Notes |\n| -------- | ------ | -------- | ----- |\n| `email`  | string | No       |       |\n| `userId` | string | No       |       |\n\n#### Example (Python)\n\n```python\nclient.delete_contact(email=\"hello@gmail.com\")\nclient.delete_contact(user_id=\"12345\")\n```\n\n#### Response\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Contact deleted.\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n```json\nHTTP 404 Not Found\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### createContactProperty()\n\nCreate a new contact property.\n\n[API Reference](https://loops.so/docs/api-reference/create-contact-property)\n\n#### Parameters\n\n| Name   | Type   | Required | Notes                                                                                  |\n| ------ | ------ | -------- | -------------------------------------------------------------------------------------- |\n| `name` | string | Yes      | The name of the property. Should be in camelCase, like `planName` or `favouriteColor`. |\n| `type` | string | Yes      | The property's value type.<br />Can be one of `string`, `number`, `boolean` or `date`. |\n\n#### Examples (Python)\n\n```python\nclient.create_contact_property(\"planName\", \"string\")\n```\n\n#### Response\n\n```json\n{\n  \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### getContactProperties()\n\nGet a list of your account's contact properties.\n\n[API Reference](https://loops.so/docs/api-reference/list-contact-properties)\n\n#### Parameters\n\n| Name   | Type   | Required | Notes                                                           |\n| ------ | ------ | -------- | --------------------------------------------------------------- |\n| `list` | string | No       | Use \"custom\" to retrieve only your account's custom properties. |\n\n#### Example (Python)\n\n```python\nclient.get_contact_properties()\nclient.get_contact_properties(\"custom\")\n```\n\n#### Response\n\nThis method will return a list of contact property objects containing `key`, `label` and `type` attributes.\n\n```json\n[\n  {\n    \"key\": \"firstName\",\n    \"label\": \"First Name\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"lastName\",\n    \"label\": \"Last Name\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"email\",\n    \"label\": \"Email\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"notes\",\n    \"label\": \"Notes\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"source\",\n    \"label\": \"Source\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"userGroup\",\n    \"label\": \"User Group\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"userId\",\n    \"label\": \"User Id\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"subscribed\",\n    \"label\": \"Subscribed\",\n    \"type\": \"boolean\"\n  },\n  {\n    \"key\": \"createdAt\",\n    \"label\": \"Created At\",\n    \"type\": \"date\"\n  },\n  {\n    \"key\": \"favoriteColor\",\n    \"label\": \"Favorite Color\",\n    \"type\": \"string\"\n  },\n  {\n    \"key\": \"plan\",\n    \"label\": \"Plan\",\n    \"type\": \"string\"\n  }\n]\n```\n\n---\n\n### getMailingLists()\n\nGet a list of your account's mailing lists. [Read more about mailing lists](https://loops.so/docs/contacts/mailing-lists)\n\n[API Reference](https://loops.so/docs/api-reference/list-mailing-lists)\n\n#### Parameters\n\nNone\n\n#### Example (Python)\n\n```python\nclient.get_mailing_lists()\n```\n\n#### Response\n\nThis method will return a list of mailing list objects containing `id`, `name`, `description` and `isPublic` attributes.\n\nIf your account has no mailing lists, an empty list will be returned.\n\n```json\n[\n  {\n    \"id\": \"cm06f5v0e45nf0ml5754o9cix\",\n    \"name\": \"Main list\",\n    \"description\": \"All customers.\",\n    \"isPublic\": true\n  },\n  {\n    \"id\": \"cm16k73gq014h0mmj5b6jdi9r\",\n    \"name\": \"Investors\",\n    \"description\": null,\n    \"isPublic\": false\n  }\n]\n```\n\n---\n\n### sendEvent()\n\nSend an event to trigger an email in Loops. [Read more about events](https://loops.so/docs/events)\n\n[API Reference](https://loops.so/docs/api-reference/send-event)\n\n#### Parameters\n\n| Name                | Type   | Required | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email`             | string | No       | The contact's email address. Required if `userId` is not present.                                                                                                                                                                                                                                                                                                                                                                                              |\n| `userId`            | string | No       | The contact's unique user ID. If you use `userId` without `email`, this value must have already been added to your contact in Loops. Required if `email` is not present.                                                                                                                                                                                                                                                                                       |\n| `eventName`         | string | Yes      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| `contactProperties` | object | No       | An object containing contact properties, which will be updated or added to the contact when the event is received.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />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)). |\n| `eventProperties`   | object | No       | An object containing event properties, which will be made available in emails that are triggered by this event.<br />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)).                                                                                                                                                         |\n| `mailingLists`      | object | No       | An object of mailing list IDs and boolean subscription statuses.                                                                                                                                                                                                                                                                                                                                                                                               |\n| `headers`           | object | No       | Additional headers to send with the request.                                                                                                                                                                                                                                                                                                                                                                                                                   |\n\n#### Examples (Python)\n\n```python\n# Minimal\nclient.send_event(email=\"hello@gmail.com\", event_name=\"signup\")\n\n# With event properties and mailing lists\nclient.send_event(\n    email=\"hello@gmail.com\",\n    event_name=\"signup\",\n    event_properties={\"username\": \"user1234\", \"signupDate\": \"2024-03-21T10:09:23Z\"},\n    mailing_lists={\"cm06f5v0e45nf0ml5754o9cix\": True, \"cm16k73gq014h0mmj5b6jdi9r\": False},\n)\n\n# With both email and userId and contact properties\nclient.send_event(\n    user_id=\"1234567890\",\n    email=\"hello@gmail.com\",\n    event_name=\"signup\",\n    contact_properties={\"firstName\": \"Bob\", \"plan\": \"pro\"},\n)\n\n# With Idempotency-Key header\nclient.send_event(\n    email=\"hello@gmail.com\",\n    event_name=\"signup\",\n    headers={\"Idempotency-Key\": \"550e8400-e29b-41d4-a716-446655440000\"},\n)\n```\n\n#### Response\n\n```json\n{\n  \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### sendTransactionalEmail()\n\nSend a transactional email to a contact. [Learn about sending transactional email](https://loops.so/docs/transactional/guide)\n\n[API Reference](https://loops.so/docs/api-reference/send-transactional-email)\n\n#### Parameters\n\n| Name                        | Type     | Required | Notes                                                                                                                                                                                            |\n| --------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `transactionalId`           | string   | Yes      | The ID of the transactional email to send.                                                                                                                                                       |\n| `email`                     | string   | Yes      | The email address of the recipient.                                                                                                                                                              |\n| `addToAudience`             | boolean  | No       | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn\u2019t already exist).                                                                     |\n| `dataVariables`             | object   | No       | An object containing data as defined by the data variables added to the transactional email template.<br />Values can be of type `string` or `number`.                                           |\n| `attachments`               | object[] | No       | A list of attachments objects.<br />**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](https://loops.so/docs/transactional/attachments) |\n| `attachments[].filename`    | string   | No       | The name of the file, shown in email clients.                                                                                                                                                    |\n| `attachments[].contentType` | string   | No       | The MIME type of the file.                                                                                                                                                                       |\n| `attachments[].data`        | string   | No       | The base64-encoded content of the file.                                                                                                                                                          |\n| `headers`                   | object   | No       | Additional headers to send with the request.                                                                                                                                                     |\n\n#### Examples (Python)\n\n```python\n# Minimal\nclient.send_transactional_email(\n    transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n    email=\"hello@gmail.com\",\n    data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n)\n\n# With Idempotency-Key header\nclient.send_transactional_email(\n    transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n    email=\"hello@gmail.com\",\n    data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n    headers={\"Idempotency-Key\": \"550e8400-e29b-41d4-a716-446655440000\"},\n)\n\n# With attachments (requires attachments to be enabled on your account)\nclient.send_transactional_email(\n    transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n    email=\"hello@gmail.com\",\n    data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n    attachments=[\n        {\n            \"filename\": \"presentation.pdf\",\n            \"contentType\": \"application/pdf\",\n            \"data\": \"JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...\",\n        }\n    ],\n)\n```\n\n#### Response\n\n```json\n{\n  \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"path\": \"dataVariables\",\n  \"message\": \"There are required fields for this email. You need to include a 'dataVariables' object with the required fields.\"\n}\n```\n\n```json\nHTTP 400 Bad Request\n{\n  \"success\": false,\n  \"error\": {\n    \"path\": \"dataVariables\",\n    \"message\": \"Missing required fields: login_url\"\n  },\n  \"transactionalId\": \"clfq6dinn000yl70fgwwyp82l\"\n}\n```\n\n---\n\n### getTransactionalEmails()\n\nGet a list of published transactional emails.\n\n[API Reference](https://loops.so/docs/api-reference/list-transactional-emails)\n\n#### Parameters\n\n| Name      | Type    | Required | Notes                                                                                                                         |\n| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `perPage` | integer | No       | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted.                                    |\n| `cursor`  | string  | No       | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |\n\n#### Example (Python)\n\n```python\nclient.get_transactional_emails()\nclient.get_transactional_emails(per_page=15)\n```\n\n#### Response\n\n```json\n{\n  \"pagination\": {\n    \"totalResults\": 23,\n    \"returnedResults\": 20,\n    \"perPage\": 20,\n    \"totalPages\": 2,\n    \"nextCursor\": \"clyo0q4wo01p59fsecyxqsh38\",\n    \"nextPage\": \"https://app.loops.so/api/v1/transactional?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20\"\n  },\n  \"data\": [\n    {\n      \"id\": \"clfn0k1yg001imo0fdeqg30i8\",\n      \"lastUpdated\": \"2023-11-06T17:48:07.249Z\",\n      \"dataVariables\": []\n    },\n    {\n      \"id\": \"cll42l54f20i1la0lfooe3z12\",\n      \"lastUpdated\": \"2025-02-02T02:56:28.845Z\",\n      \"dataVariables\": [\n        \"confirmationUrl\"\n      ]\n    },\n    {\n      \"id\": \"clw6rbuwp01rmeiyndm80155l\",\n      \"lastUpdated\": \"2024-05-14T19:02:52.000Z\",\n      \"dataVariables\": [\n        \"firstName\",\n        \"lastName\",\n        \"inviteLink\"\n      ]\n    },\n    ...\n  ]\n}\n```\n\n---\n\n## Version history\n\n- `v5.0.1` (May 13, 2025) - Added a `headers` parameter for [`sendEvent()`](#sendevent) and [`sendTransactionalEmail()`](#sendtransactionalemail), enabling support for the `Idempotency-Key` header.\n- `v5.0.0` (Apr 29, 2025)\n  - Types are now exported so you can use them in your application.\n  - `ValidationError` is now thrown when parameters are not added correctly.\n  - `Error` is now returned if the API key is missing.\n  - Added tests.\n- `v4.1.0` (Feb 27, 2025) - Support for new [List transactional emails](#gettransactionalemails) endpoint.\n- `v4.0.0` (Jan 16, 2025)\n  - Added `APIError` to more easily understand API errors. [See usage example](#usage).\n  - Added support for two new contact property endpoints: [List contact properties](#listcontactproperties) and [Create contact property](#createcontactproperty).\n  - Deprecated and removed the `getCustomFields()` method (you can now use [`listContactProperties()`](#listcontactproperties) instead).\n- `v3.4.1` (Dec 18, 2024) - Support for a new `description` attribute in [`getMailingLists()`](#getmailinglists).\n- `v3.4.0` (Oct 29, 2024) - Added rate limit handling with [`RateLimitExceededError`](#handling-rate-limits).\n- `v3.3.0` (Sep 9, 2024) - Added [`testApiKey()`](#testapikey) method.\n- `v3.2.0` (Aug 23, 2024) - Added support for a new `mailingLists` attribute in [`findContact()`](#findcontact).\n- `v3.1.1` (Aug 16, 2024) - Support for a new `isPublic` attribute in [`getMailingLists()`](#getmailinglists).\n- `v3.1.0` (Aug 12, 2024) - The SDK now accepts `null` as a value for contact properties in `createContact()`, `updateContact()` and `sendEvent()`, which allows you to reset/empty properties.\n- `v3.0.0` (Jul 2, 2024) - [`sendTransactionalEmail()`](#sendtransactionalemail) now accepts an object instead of separate parameters (breaking change).\n- `v2.2.0` (Jul 2, 2024) - Deprecated. Added new `addToAudience` option to [`sendTransactionalEmail()`](#sendtransactionalemail).\n- `v2.1.1` (Jun 20, 2024) - Added support for mailing lists in [`createContact()`](#createcontact), [`updateContact()`](#updatecontact) and [`sendEvent()`](#sendevent).\n- `v2.1.0` (Jun 19, 2024) - Added support for new [List mailing lists](#getmailinglists) endpoint.\n- `v2.0.0` (Apr 19, 2024)\n  - Added `userId` as a parameter to [`findContact()`](#findcontact). This includes a breaking change for the `findContact()` parameters.\n  - `userId` values must now be strings (could have also been numbers previously).\n- `v1.0.1` (Apr 1, 2024) - Fixed types for `sendEvent()`.\n- `v1.0.0` (Mar 28, 2024) - Fix for ESM types. Switched to named export.\n- `v0.4.0` (Mar 22, 2024) - Support for new `eventProperties` in [`sendEvent()`](#sendevent). This includes a breaking change for the `sendEvent()` parameters.\n- `v0.3.0` (Feb 22, 2024) - Updated minimum Node version to 18.0.0.\n- `v0.2.1` (Feb 6, 2024) - Fix for ESM imports.\n- `v0.2.0` (Feb 1, 2024) - CommonJS support.\n- `v0.1.5` (Jan 25, 2024) - `getCustomFields()` now returns `type` values for each contact property.\n- `v0.1.4` (Jan 25, 2024) - Added support for `userId` in [`sendEvent()`](#sendevent) request. Added missing error response type for `sendEvent()` requests.\n- `v0.1.3` (Dec 8, 2023) - Added support for transactional attachments.\n- `v0.1.2` (Dec 6, 2023) - Improved transactional error types.\n- `v0.1.1` (Nov 1, 2023) - Initial release.\n\n---\n\n## Development\n\nThis project uses `uv`.\n\n```bash\nuv sync\nuv run pytest\nuv run ruff check\nuv run mypy .\n```\n\n## Publishing\n\nUse GitHub Actions with PyPI Trusted Publishing.\n\nWorkflow:\n\n- Tag a release locally: `git tag v0.2.0 && git push --tags`.\n- The workflow `.github/workflows/release.yml` builds and publishes to PyPI on tag push using [PyPA\u2019s official action](https://github.com/marketplace/actions/pypi-publish).\n- To publish to TestPyPI manually, trigger the workflow (Run workflow) and choose `testpypi`.\n\n### Installing from TestPyPI\n\nIf you published to TestPyPI, install with an explicit index URL:\n\n```bash\npip install --index-url https://test.pypi.org/simple/ \\\n            --extra-index-url https://pypi.org/simple \\\n            loops-python-unofficial\n```\n\n### Releasing\n\n1. Update version in `pyproject.toml`.\n2. Create a tag: `git tag vX.Y.Z && git push --tags`.\n3. Workflow builds with `uv build` and publishes via trusted publishing.\n4. Alternatively dispatch the workflow and select `testpypi` to publish to TestPyPI.\n\n---\n\n## Contributing\n\nBug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Unofficial Python SDK for Loops (loops.so) with sync and async clients",
    "version": "0.1.0",
    "project_urls": {
        "Documentation": "https://loops.so/docs",
        "Homepage": "https://loops.so",
        "Repository": "https://github.com/VersionLens/loops-python-unofficial"
    },
    "split_keywords": [
        "email",
        " loops",
        " marketing",
        " sdk",
        " transactional"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6e860822f91202abb93848b143e9888b75603e6e2add5efcba88bb7a71a1eda0",
                "md5": "8f8093025b04868ba1918c9dcf1026d0",
                "sha256": "f128f03849eb592dacaa4265c83aff3b3cd47fb339aa4498c8dd436e57497269"
            },
            "downloads": -1,
            "filename": "loops_python_unofficial-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "8f8093025b04868ba1918c9dcf1026d0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 13495,
            "upload_time": "2025-08-09T14:44:18",
            "upload_time_iso_8601": "2025-08-09T14:44:18.847569Z",
            "url": "https://files.pythonhosted.org/packages/6e/86/0822f91202abb93848b143e9888b75603e6e2add5efcba88bb7a71a1eda0/loops_python_unofficial-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c5ebef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742",
                "md5": "58f320162c1b5e1ba2d5a4dcabd6ac46",
                "sha256": "7231ce0debd7ff9d4dd51ffce7273b49e839cde9ce9611ee9a76adcb23820bc1"
            },
            "downloads": -1,
            "filename": "loops_python_unofficial-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "58f320162c1b5e1ba2d5a4dcabd6ac46",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 35374,
            "upload_time": "2025-08-09T14:44:20",
            "upload_time_iso_8601": "2025-08-09T14:44:20.430906Z",
            "url": "https://files.pythonhosted.org/packages/c5/eb/ef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742/loops_python_unofficial-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-09 14:44:20",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "VersionLens",
    "github_project": "loops-python-unofficial",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "loops-python-unofficial"
}
        
Elapsed time: 1.68690s