# Team members How to add and manage team members in your Loops account. It is free to add additional team members to your account. Here's how to add team members to your team: 1. Visit [https://app.loops.so/settings?page=team](https://app.loops.so/settings?page=team) 2. You'll be able to manage invites and members of your team. 3. When you remove someone from your team, they will be established with a new Loops team of their own. That's it! 🎉 # Change your login email How to update the email address you use to log in to Loops. You will need the account owner role to change your email address. 1. Ensure you have access to your new email address. 2. Visit [https://app.loops.so/settings?page=team](https://app.loops.so/settings?page=team) 3. Invite your new email address to your team. 4. Accept the invite from an incognito window or a different browser. 5. Once the new email address has joined the team, promote it to an owner role. 6. Remove your old email address from the team. # Free plan Learn about our free plan. **The Loops Free Plan is available to everyone who stores under 1,000 contacts in Loops.** You can also send up to 2,000 emails every 30 days (marketing and transactional emails are both counted in this total). After you've exceeded the 1,000 contact limit, you'll be prompted to upgrade to a paid plan. You can upgrade to a paid plan at any time by clicking **Start subscription** at the top of your [Billing page](https://app.loops.so/settings?page=billing). On the free plan, light branding will be placed at the bottom of your emails. ### Key Benefits of the Free Plan * **Start free**: Store up to 1,000 contacts and send up to 2,000 emails every 30 days. * **Full functionality**: Access all features, including marketing and transactional emails, without restrictions. * **Easy upgrade**: Seamlessly transition to a paid plan when your contact list grows. For more information or any questions, please contact [chris@loops.so](mailto:chris@loops.so). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/free-plan.png) # Notifications Learn how to receive notifications about new Loops contacts in external tools. You will receive a notification when a contact is added to your audience through the API, integrations or form submission. You will not receive notifications for manually added users via CSV or other methods. You can manage your notifications in the [Notifications](https://app.loops.so/settings?page=notifications) page. ## Email notifications By default, you will receive an email notification from Loopsbot when a contact is added to your audience. You can disable this notification by toggling the **Notifications enabled** switch in the [Notifications section](https://app.loops.so/settings?page=notifications) of your Settings. If you'd like to receive email notifications again, you can toggle the switch back on. This setting is individual to your user account, and won't propagate to the rest of your team. ## Slack notifications You can receive notifications in Slack for new contacts. Please note, this is a global setting and will apply to all additional seats in your account. ### Set up Slack notifications 1. Go to [Settings -> Notifications](https://app.loops.so/settings?page=notifications). 2. Click the link to be redirected to Slack for authorization. 3. Select the channel you want to add the bot to. The bot must be invited to private channels before they become visible in the dropdown. 4. Send a test message to verify the integration works. That's it! All new contacts added to your audience will now send a notification to the selected Slack channel. ### Other options Here are a few other ways to get Slack notifications for new contacts: 1. Install an email add-on to send individual emails from [Gmail](https://slack.com/apps/AEFLFJR9Q-slack-for-gmail) or [Outlook](https://slack.com/apps/AFS3736H3-slack-for-outlook) to channels or DMs (free). 2. Create a forwarding address to send individual emails to your DM with Slackbot (free). [View the full documentation from Slack](https://slack.com/help/articles/206819278-Send-emails-to-Slack) for more information. # CSV Upload Easily add contacts to Loops by uploading a CSV file. ## Add new contacts via CSV 1. Select **Import** in the top right of the [Audience](https://app.loops.so/audience) page. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/select-import-users.png) 2. Hover on **CSV** and click **Upload CSV**. Download the [example formatted CSV](https://app.loops.so/loops_sample.csv) to get an idea of the columns we use. By default, we recommend using at least `Email`, `First Name`, `Last Name`, `User Group` and `Source` columns. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/import-users.png) 3. After uploading the CSV you'll have a chance to review any duplicates or missing information before finishing. 4. When the upload is finished, all the uploaded contacts can be viewed in the Audience page. That's it! 🎊 ## Update contacts via CSV You can also upload a CSV file to update your existing contacts in bulk. You can either download a CSV from your Audience page in Loops, edit the data and re-upload it, or start with a new CSV file and just include the contacts and columns you want to update. When updating contacts, Loops will first look for a matching contact using the value in the `User ID` column, then the `Email` column. If a contact is found, Loops will update the contact using the data provided in the CSV file. If a contact is not found with either `User ID` or `Email` values, a new contact will be created using the data provided. Note: If a contact is found using `User ID` and its email address does not match the provided `Email` value, the contact will not be updated and an error will be recorded. ## View previous CSV uploads After uploading CSV files you can view a history of your uploads plus details for each file's rows. On the [Audience page](https://app.loops.so/audience) click **Import contacts**, hover on **CSV** and click **Upload CSV**. On the next page click on **View imports**, which will show you a list of all of your past uploads. Clicking on one of your uploads will let you view lists of all new, updated or duplicated contacts plus a list of any errors from the import. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/csv-history.png) ## Important notes * All contacts uploaded will be automatically marked as subscribed. If you want to mark imported contacts as unsubscribed add a "Subscribed" column into your CSV file and use `false` as the column value. * The importer does not re-subscribe contacts who are marked as unsubscribed in your Audience. * The default `Source` value for each uploaded contact will be "CSV Upload". You can change this by adding a `Source` column to your CSV file and specifying a custom value. * Cells that are empty in the CSV file will not overwrite existing data in Loops. If you want to clear a field, you will need to provide a value of `null` in the CSV file (4 characters, all lowercase). # Integrations Connect Loops to external platforms to automatically add contacts to your audience. We have a range of integrations with other platforms, which allow syncing of data between services you use. For example, you can add users from thousands of apps through [Segment](/integrations/segment) or [Zapier](/integrations/zapier), or use forms on platforms like [Framer](/integrations/framer) or [Webflow](/integrations/webflow) to let contacts sign up to your mailing list. [Go to the Integrations page](/integrations) to view the full list. # API Loops provides a REST API to manage your contacts. With [the Loops API](/api-reference), you can easily manage contacts directly from your application or service. For example, creating a new contact is as easy as sending a `POST` request to `https://app.loops.so/api/v1/contacts/create`. ```json { "email": "adam@loops.so", "firstName": "Adam", "lastName": "Kaczmarek", "favoriteColor": "blue", "userGroup": "Founders", "source": "Signup form Service" } ``` We also offer endpoints for finding, updating and deleting contacts (plus some other features like sending transactional email and sending events). [Go to our API reference](/api-reference) for a full guide. # API key GET /v1/api-key Test that an API key is valid. ## Request No parameters. ## Response ### Success The name of the team the API key belongs to. ### Error An error message. ```json Response { "success": true, "teamName": "My team" } ``` ```json 401 response { "error": "Invalid API key" } ``` # API Changelog Stay up-to-date with changes to our API. Improvement: the maximum payload size for the transactional endpoint has been increased from 1MB to 4MB, allowing for more or larger attachments. New: our new [Nuxt module](/sdks/nuxt) is now out! New: our new [Ruby SDK](/sdks/ruby) is available! New: we added an `isPublic` attribute to mailing list objects in the [List mailing lists](/api-reference/list-mailing-lists) endpoint. New: there's a new `addToAudience` parameter in the [Send transactional email](/api-reference/send-transactional-email) endpoint can add contacts to your audience. New: support for our new [mailing lists](/contacts/mailing-lists) feature. You can now add contacts to and remove contacts from mailing lists in the [Create contact](/api-reference/create-contact), [Update contact](/api-reference/update-contact) and [Send event](/api-reference/send-event) endpoints. There is also a [new endpoint](/api-reference/list-mailing-lists) for retrieving your mailing lists. New: you can now [Find contacts](/api-reference/find-contact) by `userId`. New: a new endpoint for testing your integration and/or API key: [API key](/api-reference/api-key). New: you can now include [event properties](/events/properties) in requests to the [Send event](/api-reference/send-event) endpoint. Improvement: we removed behavior that returned a `400 Bad Request` response if an unrecognized property was added to Contact endpoints. Improvement: sending in a payload that contains an unrecognized property will now return a `400 Bad Request` response. Clarification in the API docs that `userId` can be used in a [Send event](/api-reference/send-event) request. New: we added a new endpoint for [querying custom contact properties](/api-reference/list-custom-fields). Our [official JavaScript/TypeScript SDK](/sdks/javascript) is now available! New: we now accept dates as a custom [contact property](/contacts/properties) type. [View the available formats](/contacts/properties#dates). # Create contact POST /v1/contacts/create Create a new contact with an email address and any other contact properties. ## Request ### Body The contact's email address. The contact's first name. The contact's last name. A custom source value to replace the default "API". [Read more](/contacts/properties#source) Whether the contact will receive campaign and loops emails. [Read more](/contacts/properties#subscribed) You can use groups to segment users when sending emails. Currently, a contact can only be in one user group. [Read more](/contacts/properties#user-group) A unique user ID (for example, from an external application). [Read more](/contacts/properties#user-id) Key-value pairs of mailing list IDs and a `boolean` denoting if the contact should be added (`true`) or removed (`false`) from the list. [Read more](/contacts/mailing-lists#api) ```json "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": false } ``` ### Custom properties You can also include [custom contact properties](/contacts/properties) in your request body. These should be added as top-level attributes in the request. Custom properties can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). ```json { "email": "hello@gmail.com", "plan": "pro", /* Custom property */ "dateJoined": 1704711066 /* Custom property */ } ``` There are a few [reserved names](/contacts/properties#reserved-names) that you cannot use for custom properties. To empty or reset the value of a contact property, send a `null` value. ## Response ### Success The ID of the new contact. ### Error An error message. ```json Response { "success": true, "id": "id_of_contact" } ``` ```json Error response { "success": false, "message": "An error message." } ``` # Delete contact POST /v1/contacts/delete Delete a contact by email address or user ID. ## Request ### Body You can delete a contact by using either their `email` or `userId` value. The contact's email address. The contact's `userId` value. ## Response ### Success "Contact deleted." ### Error An error message. ```json Response { "success": true, "message": "Contact deleted." } ``` ```json Error response { "success": false, "message": "An error message." } ``` # Find contact GET /v1/contacts/find Find a contact by email address or user ID. ## Request ### Query parameters Search by email or user ID. Only one parameter is allowed. The contact's email address. Make sure it is [URI-encoded](https://en.wikipedia.org/wiki/Percent-encoding). The contact's unique user ID. ## Response This endpoint will return a list of contact objects containing all default properties and any [custom properties](/contacts/properties). If no contact is found, an empty list will be returned. The contact's Loops-assigned ID. The contact's email address. The contact's first name. The contact's last name. The source the contact was created from. Whether the contact will receive campaign and loops emails. The contact's user group. The contact's unique user ID. Mailing lists the contact is subscribed to, represented by key-value pairs of mailing list IDs and `true`. ```json Response [ { "id": "cll6b3i8901a9jx0oyktl2m4u", "email": "hello@gmail.com", "firstName": "Bob", "lastName": null, "source": "API", "subscribed": true, "userGroup": "", "userId": null, "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true } } ] ``` ```json No contact found response [] ``` # API Introduction The Loops REST API lets you manage your contacts, send events and send transactional email. ## Authentication Your Loops API key should never be used client side or exposed to your end users. Start here if you want to use the Loops API to add contacts to your Loops audience, update their attributes, and send events to Loops. To get started, you'll need an API key. Visit the Loops settings page and click “Generate new key” ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/authentication-1.png) This creates an API key. You can assign it a human-readable name: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/authentication-2.png) We suggest using a different API key for different purposes. You can revoke an API key at any time with the trash icon. When making an API call, add an Authorization header and set the API key as a Bearer token: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/authentication-3.png) You can test your API key by making a `GET` request to [https://app.loops.so/api/v1/api-key](https://app.loops.so/api/v1/api-key). If successful, you will receive `{ "success": true }` As a Curl request (replace `d2d561f5ff80136f69b4b5a31b9fb3c9` with your own API key): ```bash curl https://app.loops.so/api/v1/api-key -H "Accept: application/json" -H "Authorization: Bearer d2d561f5ff80136f69b4b5a31b9fb3c9" ``` ## Rate Limiting To ensure the quality of service for all users, our API is rate limited. This means there's a limit to the number of requests your application can make to our API in a certain time frame. The baseline rate limit is **10 requests per second per team**. To see your current usage, we provide the following response headers in every API response: * `x-ratelimit-limit`: The maximum number of requests you can make per second. * `x-ratelimit-remaining`: The number of requests remaining in the current rate limit window. Here is an example of a successful response with rate limit headers: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/api-successful-response.png) If you exceed this limit, you'll receive a response with HTTP status `429 Too Many Requests`. Here is an example of a failed response with rate limit headers: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/api-rate-limited.png) It's important to handle these 429 responses in your application. We recommend implementing retries with an exponential backoff strategy. If your use case requires a higher limit, please [contact us](mailto:help@loops.so) and we'll be happy to discuss your needs. ## Debugging Sometimes things go wrong. Here are some tips to help you debug your API requests. If you are having trouble with the API, we recommend using a tool like [Postman](https://www.postman.com/) to test your requests. 1. Handling CORS Errors: * The Loops API does not support cross-origin requests made from client-side JavaScript. To avoid CORS errors, make sure to issue your requests from a server-side application. 2. Dealing with "Not Authenticated" Codes: * Make sure you have generated an API key from the Loops settings page and that you are including it in your requests. * Your API key should be included in the "Authorization" header of your request, following the format "Authorization: Bearer \{YOUR\_API\_KEY}". 3. Handling Rate Limiting (429 Response): * The Loops API allows a maximum of 10 requests per second per team. If you receive a "429 Too Many Requests" response, this means you have exceeded this limit. * The "x-ratelimit-limit" and "x-ratelimit-remaining" headers in the response can provide information about your current rate limit usage. 4. Authorization Header: * Ensure that you are correctly including the "Authorization" header in your requests. The value of this header should follow the format "Bearer \{YOUR\_API\_KEY}". 5. Handling 400-level Responses: * 400-level responses typically indicate that there was a problem with the request. The response body will contain more information about what went wrong, so be sure to check it for details. * Check on your request type (GET, POST, PUT, DELETE) and ensure that you are using the correct one for the endpoint you are trying to access. 6. Handling Issues with Contacts API: * Make sure to include at least the "email" field in your requests when adding or updating contacts. * For searching contacts, replace `URI_ENCODED_EMAIL` with the URL-encoded email you are looking for in the GET request. 7. Handling Issues with Events API: * When sending events, ensure that both the "email" and "eventName" fields are included in your requests. If you have followed these steps and are still experiencing issues, don't hesitate to [reach out to the Loops team](https://app.loops.so/settings?page=support) for further assistance. ## OpenAPI spec Get started quickly with the Loops API using our OpenAPI documents. You can import these documents into an API client like Postman or Insomnia to see and use all of our endpoints, with example requests and expected responses. * **YAML:** [app.loops.so/openapi.yaml](https://app.loops.so/openapi.yaml) * **JSON:** [app.loops.so/openapi.json](https://app.loops.so/openapi.json) ## SDKs SDKs are software packages built on top of the API, making it easier to integrate into your project. The official JavaScript/TypeScript SDK for Loops. } href="/sdks/ruby"> The official Ruby SDK for Loops. ### Unofficial SDKs The following SDKs are community-submitted and have not been officially reviewed or endorsed by Loops. We recommend thoroughly testing and reviewing the code before integrating it into your project. * [Laravel](https://github.com/plutolinks/laravel-loops) by PlutoLinks * [PHP](https://github.com/plutolinks/loops-php) by PlutoLinks * [Ruby on Rails](https://github.com/danielfriis/loops_rails) by Daniel Friis [Submit an SDK](mailto:dan@loops.so) ## API Reference The base URL for the API is `https://app.loops.so/api` ### Contacts Manage contacts. ### Mailing lists View your mailing lists. ### Events Send events to trigger emails in loops. ### Transactional email Send a transactional email. ### Custom fields View your account's custom contact properties. ### API key Test your API key. # List custom fields GET /v1/contacts/customFields Retrieve a list of your account's custom contact properties. ## Request No parameters. ## Response This endpoint will return a list of custom field objects. If your account has no custom fields, an empty list will be returned. The property's name key. The human-friendly label for this property. The type of property (one of `string`, `number`, `boolean` or `date`). ```json Response [ { "key": "favoriteColor", "label": "Favorite Color", "type": "string" }, { "key": "plan", "label": "Plan", "type": "string" } ] ``` ```json No custom fields response [] ``` # List mailing lists GET /v1/lists Retrieve a list of your account's mailing lists. ## Request No parameters. ## Response This endpoint will return a list of mailing list objects. If your account has no mailing lists, an empty list will be returned. The ID of the list. The name of the list. Whether the list is public (`true`) or private (`false`). [Read more](/contacts/mailing-lists#list-visibility) ```json Response [ { "id": "clxf1nxlb000t0ml79ajwcsj0", "name": "Mailing List Beta", "isPublic": true }, { "id": "clxf2q43u00010mlh12q9ggx1", "name": "Product B Launch", "isPublic": true } ] ``` # Send event POST https://app.loops.so/api/v1/events/send Send events to trigger emails in Loops. ## Request ### Body Provide either an `email` or `userId` value or both to identify the contact. If both are provided, the system will look for a contact with either a matching `email` or `userId` value. If a contact is found for one of the values (e.g. `email`), the other value (e.g. `userId`) will be updated. If a contact is not found, a new contact will be created using both `email` and `userId` values. The contact's email address. The contact's unique user ID. This must already have been added to your contact in Loops. The name of the event. An object containing event property data for the event. Values can be of type `string`, `number`, `boolean` or `date`. [Read more](/events/properties) Key-value pairs of mailing list IDs and a `boolean` denoting if the contact should be added (`true`) or removed (`false`) from the list. [Read more](/contacts/mailing-lists#api) ```json { "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": false } } ``` ### Contact properties You can also include default and custom [contact properties](/contacts/properties) in your request body, which will update the contact in Loops. These should be added as top-level attributes in the request. Contact properties can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). ```json { "email": "hello@gmail.com", "eventName": "signup", "firstName": "Bob", /* Contact property */ "plan": "pro" /* Custom contact property */ } ``` There are a few [reserved names](/contacts/properties#reserved-names) that you cannot use for custom properties. To empty or reset the value of a contact property, send a `null` value. ## Response ```json Response { "success": true } ``` ```json Error response { "success": false, "message": "An error message." } ``` # Send transactional email POST /v1/transactional Send a transactional email to a contact. ## Request ### Body The email address of the recipient. The ID of the transactional email to send. If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn't already exist). An object containing data as defined by the data variables added to the transactional email template. Values can be of type `string` or `number`. Please [email us](mailto:help@loops.so) to enable attachments on your account before using them with the API. An object containing file(s) sent along with an email message. The name of the file, shown in email clients. The MIME type of the file. The base64-encoded content of the file. ## Response ### Success ### Error ```json Response { "success": true } ``` ```json Error response { "success": false, "path": "dataVariables", "message": "There are required fields for this email. You need to include a 'dataVariables' object with the required fields." } ``` ```json Error response { "success": false, "error": { "path": "dataVariables", "message": "Missing required fields: login_url" }, "transactionalId": "clfq6dinn000yl70fgwwyp82l" } ``` # Update contact PUT /v1/contacts/update Update or create a contact. Update an existing contact by sending a request containing contact properties. This endpoint will create a contact if a matching contact does not already exist in your audience. If you want to update a contact's email address, the contact will first need a `userId` value. You can then make a request containing the `userId` field along with an updated email address. ## Request ### Body The contact's email address. If there is no contact with this email, one will be created. The contact's first name. The contact's last name. A custom source value to replace the default "API". [Read more](/contacts/properties#source) Whether the contact will receive campaign and loops emails. [Read more](/contacts/properties#subscribed) You can use groups to segment users when sending emails. Currently, a contact can only be in one user group. [Read more](/contacts/properties#user-group) A unique user ID (for example, from an external application). [Read more](/contacts/properties#user-id) Key-value pairs of mailing list IDs and a `boolean` denoting if the contact should be added (`true`) or removed (`false`) from the list. [Read more](/contacts/mailing-lists#api) ```json "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": false } ``` ### Custom properties You can also include [custom contact properties](/contacts/properties) in your request body. These should be added as top-level attributes in the request. Custom properties can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). ```json { "email": "hello@gmail.com", "plan": "pro", /* Custom property */ "favoriteColor": "Blue" /* Custom property */ } ``` There are a few [reserved names](/contacts/properties#reserved-names) that you cannot use for custom properties. To empty or reset the value of a contact property, send a `null` value. ## Response ### Success The ID of the contact. ### Error An error message. ```json Response { "success": true, "id": "id_of_contact" } ``` ```json Error response { "success": false, "message": "An error message." } ``` # Contact activity timeline The contact activity timeline is a great way to see all the activity for a specific contact. ![contact property timeline](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/contact-activity-timeline.png) The contact activity timeline provides a chronological view of all the activity for a specific contact. To access the contact activity timeline, click on a contact from inside an audience. Email properties that can show on the timeline include: * Sent * Opened * Clicked * Soft bounced * Hard bounced * Marked as spam * Unsubscribed Additionally, we also show the following properties: * Added to audience * Event fired # Filters and Segments How to send emails to specific groups of contacts and save segments for future use. After you have added contacts to your audience in Loops, you may want to send loops or campaigns to certain groups of contacts. You can do this with filters and segments. ![Loop](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/audience-filters.png) ## Audience filters When sending [campaigns](/types-of-emails#campaigns) or emails inside [loops](/loop-builder), you can send to specific groups of contacts in your audience. Choosing these contacts is done using filters, which are based on [contact properties](/contacts/properties) (either default properties or custom properties you've added to your audience). ![Audience filter form](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/filter-form.png) You can add multiple filters at once, and you can also choose to apply *all filters* ("All of these are true") or *any filter* ("Any of these are true"). Audience filters are automatically updated as contact properties are changed. This means that your emails will only send to contacts that match the filter at the time the email is sent. ## Audience segments Segments are just filters that are saved in your Loops account for the future. This makes them easily re-usable between different loops and campaigns. You can create segments while sending campaigns or loops, or directly on the [Audience page](https://app.loops.so/audience). To create a segment, first filter the audience table and then click **Save segment**. ![Create a segment](https://mintlify.s3-us-west-1.amazonaws.com/loops/contacts/images/create-save-segments.png) You will be prompted to add a name for your segment, then click **Finish**. Your new segment will then be available in a dropdown whenever you filter your audience for campaigns or loops. ![List of segments](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segments.png) Just like filters, segments are automatically updated when contact data is changed, meaning emails always send to the correct contacts at time the email is sent. # List management Organise contacts and offer a subscriber preference center with mailing lists. Lists are the preferred way of managing your contacts in Loops. In addition to providing fine-grained control over your audience, using Lists will automatically generate a branded Preference Center for contacts in your audience, allowing them to easily manage their subscription preferences. Lists can be public or private and contacts can belong to many, one, or no lists. Lists are an optional feature. You can use Loops without lists but if you'd like finer-grained control over your contacts and the types of communication they receive, lists are available on any plan tier. ## List visibility Each list you create can be Public or Private. By default lists are private, meaning they are only shown to their subscribed contacts (non-subscribers won't be able to see or subscribe to private lists in the Preference Center). If you want to allow general opt-in to a list, you can set the list visibility to `Public`. Public lists will be shown to all contacts in the Preference Center. You can also sign up new subscribers to public lists with [Forms](/forms). Both private and public lists are visible within your Loops admin and can be used for filtering contacts when sending campaigns and loops. ## Preference Center The Preference Center allows your contacts to manage their own subscription preferences. ![Preference Center](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/preference-center.png) A link to the contact-specific Preference Center is automatically added to each marketing email sent from Loops. You can link to the Preference Center in MJML emails by using the `{unsubscribe_link}` [dynamic tag](/creating-emails/personalizing-emails#dynamic-tag-syntax). You can upload a company icon to brand your Preference Center. This option is shown just below your mailing lists in the [Lists settings page](https://app.loops.so/settings?page=lists). You can brand your unsubscribe page with a company icon even if you do not use the lists features. Within the Preference Center, contacts will see: * your company icon (if uploaded) * the names and descriptions of all public lists * the names and descriptions of all private lists they are subscribed to * the option to unsubscribe from each list they are subscribed to ## Create a list Go to [Settings -> Lists](https://app.loops.so/settings?page=lists). Click on the **Create a list** button. ![Create a new list](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/create-list.png) A new mailing list will appear. Enter a name for your list and optionally, a description. You can also choose a color to easily identify the list inside your Loops account. ![Visibility toggle](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/lists-toggle.png) Choose between `Private` or `Public` ([see above](#list-visibility)). Click **Save changes** to finalize the creation of the list. ## Edit a list To edit an existing list, go to [Settings -> Lists](https://app.loops.so/settings?page=lists). Edit the name, description, visibility and color. Click **Save changes** to apply the changes. After saving your changes, the updated list data will be instantly available to your contacts in their Preference Centers. ## Utilizing lists Here are a few ways you can use lists to send emails and organise contacts. ### Trigger a loop when a contact is added to a list This example is a typical use case of sending an email sequence to new contacts when they are added to a specific list. [Create a loop](/loop-builder) or edit an existing one. Set the Loop trigger to "Contact added to list". ![Select trigger](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/set-trigger.png) Select the list you want to trigger the loop. ![Select the list](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/select-list.png) Start the loop. When a contact is added to the selected list, the loop will be triggered. ### Send campaigns to a list Instead of sending campaigns to your whole audience, you can send emails to a specific list. [Create a campaign](https://app.loops.so/campaigns) or edit an existing one. On the [Audience page](https://app.loops.so/audience), select the list you want to send to. ![Select a mailing list](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/campaign-list.png) Users not subscribed to the selected list will not receive the campaign. Optionally, you can apply additional [filters or segments](/contacts/filters-segments) to further refine your audience. ### Manually add contacts to lists How to add existing contacts to your different mailing lists within Loops. You cannot edit the list subscriptions of contacts who have been unsubscribed from your audience. Likewise, if a contact unsubscribes from a list via the Preference Center, they cannot be resubscribed by your team. #### Individual contacts Go to your [Audience page](https://app.loops.so/audience). Click on the contact you want to manage. In the contact details page, click on **Subscribed** to reveal the mailing list dropdown. ![Manage subscriptions](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/manage-contact-subscriptions.png) Toggle each list on or off as needed. Click **Save changes** in the top right to apply the changes. #### Bulk contacts You can easily add any filtered group of contacts to a specific list on the Audience page. Go to your [Audience page](https://app.loops.so/audience). Add filters to segment your audience into the group of contacts you'd like to add to a list. Click the `•••` menu icon on the far right-hand side of the audience filters, select **Add to mailing list** and then select the list(s) you want to add the contacts to. ![Add to list](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/bulk-assign-to-lists.png) ### Upload a CSV to a list If you want to import contacts to a list in bulk you can use our [CSV importer](/add-users/csv-upload). In the final stage of the form you can select a list, which will add all contacts (new or existing) in the CSV file to that list. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/csv-upload.png) ### Add contacts to lists with the API Utilizing the [Loops API](/api-reference) you can programmatically add and remove contacts to and from Lists. When creating a contact, updating a contact, or sending in an event with the API, you can include a `mailingLists` object in the payload. This `mailingLists` object is a key-value pair of list IDs and a subscription status. The subscription status can be `true` or `false`. ```json { "email": "loopy-loop@example.com", "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": false } } ``` In this example, the contact would be subscribed to `cm06f5v0e45nf0ml5754o9cix` and unsubscribed from `cm16k73gq014h0mmj5b6jdi9r`. Mailing list IDs can be found [in the app](https://app.loops.so/settings?page=lists) (click the ID to add it to your clipboard) or by using the [API](/api-reference/list-mailing-lists). ![Visibility toggle](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/lists-toggle.png) ### Add contacts to lists with forms If you use a [form](/forms/simple-form) on your website you can subscribe contacts to specific lists. When exporting HTML from the [Forms page](https://app.loops.so/forms) in Loops, choose a list from the **Settings** tab. ![Select a list from the form settings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mailing-lists/form-settings.png) Adding contacts to a list via a form only works with public lists. The option to select a list will only appear in the form settings if you have at least one public list. If you already have a form in place or are using a [custom form](/forms/custom-form) you can add a `mailingLists` parameter to the form body with the value a comma-separated list of mailing list IDs. HTML example: ```html
``` JavaScript example: ```javascript fetch(formEndpointUrl, { method: "POST", body: "mailingLists=" + encodeURIComponent("cm06f5v0e45nf0ml5754o9cix,cm16k73gq014h0mmj5b6jdi9r") + "&email=" + encodeURIComponent(emailInput.value), headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); ``` # Contact properties How to add, edit and delete contact properties. ## Default contact properties These are the default properties for every contact on Loops. They cannot be deleted. | Contact Property | Example | Email Tag\* | API Name\*\* | | ---------------- | --------------------------------------- | ------------- | ------------ | | Email | *[hi@example.co](mailto:hi@example.co)* | `{email}` | `email` | | First Name | *Chris* | `{firstName}` | `firstName` | | Last Name | *Frantz* | `{lastName}` | `lastName` | | Notes | *Favorite color is blue.* | `{notes}` | N/A | | Source | *API* | `{source}` | `source` | | Subscribed | `true` | N/A | `subscribed` | | User Group | *Investors* | `{userGroup}` | `userGroup` | | User Id | *ask523236* | `{userId}` | `userId` | \* Used to [add personalization to emails](/creating-emails/personalizing-emails#dynamic-tag-syntax).\ \*\* Used in [API requests](/api-reference). ### Source "Source" describes where the contact originated from. By default, this value will be "Form" for contacts added [via a form](/forms/simple-form), or "API" for contacts added [via the API](/api-reference). You can specifiy custom "Source" values when adding contacts via forms and the API. ### Subscribed The "Subscribed" value determines whether a contact is able to receive **loops and campaigns**. Unsubscribed contacts *will receive all transactional emails*. Contacts can unsubscribe from your emails using an Unsubscribe link automatically added to your campaigns and loops. Some important notes: * We do not charge for unsubscribed contacts. * We suggest you keep unsubscribed contacts in your audience. If you delete and then re-add them in the future somehow, they may end up being "subscribed" even though they have been unsubscribed. * You cannot re-subscribe contacts via a [CSV upload](/add-users/csv-upload) or from the Audience page in Loops. You can re-subscribe contacts [with the API](/api-reference/update-contact) and with some of [our integrations](/integrations#manage-contacts). ### User Group "User Group" is a useful optional property that you can use to segment contacts. It is a free text field that allows you to easily divide contacts into groups like "Users", "VIPs", "Investors" or "Customers". Contacts can currently only have one user group value. ### User Id "User Id" is a unique external ID you can assign to each contact in your audience. For example, this could be a customer ID from your store or a user ID from your SaaS. This field is optional but is very useful if you are working with our API. For example, you need a user ID to be able to [change a contact's email address](/api-reference/update-contact). ## Custom contact properties Custom contact properties are additional fields that you can create to store information about contacts. ### Types of property Custom contact properties can be one of four different types: * String * Number * Boolean * Date (see below) You can specify a property type when creating new properties in Loops. #### Dates When sending dates with the [API](/api-reference) or via one of our [integrations](/integrations), you can use either a Unix timestamp (*in milliseconds*) or an [ECMA-262 date-time string](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format) Timestamps must be in milliseconds and can be sent as either an integer or string. * `1705486871000` (if the Unix timestamp is `1705486871`) Supported date formats are shown below. These must be sent as a string. Adding a time offset at the end (e.g. `+02:00` or `-07:00`) is optional (if omitted, the date will default to UTC). * `YYYY-MM-DDTHH:MM:SS.sss` * `YYYY-MM-DDTHH:MM:SS` * `YYYY-MM-DDTHH:MM` * `YYYY-MM-DD HH:MM:SS.sss` * `YYYY-MM-DD HH:MM:SS` * `YYYY-MM-DD HH:MM` * `YYYY-MM-DD` ### Reserved names Note that Loops does not allow the creation of properties with the following reserved names: * `id` * `listId` * `softDeleteAt` * `teamId` * `updatedAt` ### Add a property One way to create custom contact properties is to go to [your Audience](https://app.loops.so/audience) and click on any of the column headers, then select **Add property**. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/option-1.png) Alternatively you can scroll to the end of the Audience table and click the `+` button at the end of the column headers. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/option-2.png) A third way is to go your [API Settings](http://app.loops.so/settings?page=api) page, scroll down to the Contact properties section and click **Add property**. In each of these cases, you'll see a form asking you for a property name and type. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-property-form.png) ### Deleting contact properties You can delete properties on the Audience page by clicking on column headers. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/delete-property-dropdown.png) You can also delete properties from your [API Settings](http://app.loops.so/settings?page=api) page by clicking the trashcan icons. It is not possible to delete default contact properties. Once a contact property is deleted, all associated data will also be deleted and cannot be recovered. It's important to be sure that you won't need the information stored in a property before deleting it. #### Property in use warning If you receive a “Property in use” warning modal while deleting a contact property, there are a few things you can check before you're able to delete the property. * If the listed email is a Campaign: * Check if the property is in use as [dynamic content](/creating-emails/personalizing-emails) inside the email editor * Ensure this property is not being actively used in the Audience filter * If the listed email inside a Loop: * Make sure a draft or running Loop is not using it as part of the Audience filter or as a Trigger ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/delete-property.png) # Components Create re-usable elements for your emails. Components are re-usable elements for emails. They help you create frequently-repeated sections of emails once, which you can then easily drop into new emails. ![Components in an email](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/components-example.png) Changes made to components can be synced to all other instances. You can also choose to make local edits to a single component instance without updating others. Components can be created from and added to all emails created in Loops (campaign, loops and transactional emails). They also work in both Plain and Styled emails. ## Example components Some useful examples of components are logos and social icons. These elements are typically the same across multiple emails; using components will make sure they are the same everywhere. Most of the time you will want your logo to have the same alignment, spacing and size in your emails. Similarly, you will want the same set of social icons readily available to drop into every new email you create. ![Example components](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/example-components.png) ## Create a component To create a component, click on the block settings icon on the left side of an element in the editor. Then click **Create component**. ![Create a component](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-component.png) A modal will appear where you can name your component. Use a descriptive name so you can easily find your component in the future. Click **Create**. You will see your new component appear in the Components list on the left of the editor. All components you create are available in all of your emails, i.e. a component created in a campaign email is also available to insert into transactional emails. ## Insert a component Click on **Components** at the top of the left-hand menu to reveal your components list, then simply click on a component to insert it. ![Add a component](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/insert-component.png) If you want to change the location of a component in your email, you can drag and drop it within the editor just like other blocks. If you already have a component in your email, clicking **Duplicate** in the block settings menu will add a copy of that component into the email, including any local edits made. ## Edit a component You can tell if an element in your email is a component by looking for a purple outline around the block in the editor. You will also see a purple label containing the component's name just above the formatting toolbar. To edit a component, edit its content as you would any other part of your email. This will create local edits to that single instance; these edits are not synced to other components in use elsewhere. This means you can make tweaks and changes to a single instance of a component without updating all other instances. If a component has local edits, you will see a purple dot in the components icon in the formatting toolbar. ![Edited component](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/edited-component.png) If you want to save your changes to all instances of the component, click on the block settings icon and then click **Push changes**. Note that any local edits made to the component in other emails will be preserved. ![Push changes to other instances](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/push-changes-1.png) Alternatively, you can click on the component, click on the components icon on the far right of the formatting toolbar (you'll notice a purple dot denoting that the component has edits), then click **Push changes to main component**. ![Push changes to other instances](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/push-changes-2.png) If you make local edits to a component that you want to revert, you will find a **Reset all changes** option in both the block settings menu and formatting toolbar. ## Rename a component In the Components list, find the component you want to rename, then click the `•••` menu icon. Click on **Rename** to show the rename modal. Enter a new name and click **Rename**. ## Delete a component Deleting a component only deletes the component; it does not remove the component from emails.\ A deleted component's contents will be retained by any email it was added to. In the Components list, find the component you want to rename, then click the `•••` menu icon. Click on **Delete** to show the confirmation modal. Click **Delete** to delete the component. # Duplicating emails Re-use basic elements of an email or create email templates. Duplicating emails is a great way to save time crafting similar emails You can easily create emails that share many of the same elements (investor updates, product updates, waitlist invites, etc). To duplicate an email: 1. Go into Campaigns, Loops, or Transactional (whichever email you want to duplicate). 2. Click the `•••` menu icon and select **Duplicate**. 3. Edit the new email's subject line, preview text and content as needed. ![Duplicating emails](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/duplicating-emails.png) # Adding content Learn how to edit emails in the Loops editor. ## Formatting text Once you have written content in your email, you can apply styles by highlighting the text. This will bring up a formatting toolbar with options that give you a lot of control over your written content. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/formatting-toolbar.png) You can choose between different headings, style text **bold**, *italic*, underline as well as add links. You can also change text size and color. Text can also be easily changed into lists, quotes and inline code blocks. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/editor-text.png) ## Adding elements Emails can be more than just text. You can add the following elements to your emails: * images * buttons * dividers * social icons * columns ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/editor-elements.png) You can add elements by typing `/` in the editor. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/slash-menu.png) If you continue typing, you can narrow down the selections. You can use your arrow keys to select items and pressing Enter will confirm your selection. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/slash-typed.png) You can alternatively add elements from the toolbar above the editor, or by hovering over an existing element in your email and clicking the `+` plus icon on the left. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/editor-adding.png) ### Images As well as adding from the toolbar or the `+` icon, you can drag or paste images into the editor. Add a link to an image by selecting the element and then clicking the link icon in the toolbar. #### Dynamic images Uploaded images can become dynamic by selecting them and then clicking the `⚡️` icon in the formatting toolbar. Paste in an image URL or add dynamic content. For example, you could insert an `imageUrl` data variable in a transactional email, or use an `avatarUrl` custom contact property in a campaign. ![Adding a dynamic image](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/dynamic-image.png) You can also create dynamic URLs by combining text and dynamic content inside the image source field. ![Adding a dynamic image URL](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/dynamic-image-url.png) Keep in mind that the image must be publicly accessible for this to work and the URL must end with a supported image file extension for email (like `.jpg` or `.png`). Additionally, these images should be hosted for a period of time that is relevant to the email's lifecycle as they will not be stored in Loops. Note that, just as with other dynamic content, if the contact or event property used for your dynamic image is missing, the email will not be sent. ### Buttons Buttons let you add call-to-action elements inside your emails. Add links to buttons by selecting the element and then clicking the link icon in the toolbar. ### Dividers Dividers help separate or group content in your emails. Change spacing around dividers by clicking the drag-and-drop icon on the left and changing the padding values ([more about block settings below](#block-settings)). ### Social icons You can add a row of social icons by clicking the star icon in the editor toolbar. ![Social icons](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-icons.png) You have options for icon width and color (black, grey or white) plus padding. You can add more icons by clicking the plus icon. Drag icons to rearrange them. To change the alignment of the icons, click on the block settings icon (more info below). ### Columns Columns allow you to arrange content side by side, creating dynamic layouts for your emails. You can use columns to place elements like images, text, buttons, and more next to each other. Currently, we support a maximum of two columns. Nesting columns within columns is not yet supported. #### Adding columns You can add columns to your email in several ways: 1. **Using the `/` slash menu in the editor**: Type `/` to open the slash menu then start typing "columns" or click on the **Columns** option. 2. **Using the editor toolbar**: Click on the columns icon to insert a column layout.\ ![Adding columns from toolbar](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/columns-toolbar.png) 3. **Dragging two blocks together**: Click and hold the block settings icon (six dots) to the left of a block. Drag the block next to another one until you see a vertical gray line on the right, which indicates a new column will be created. ![Adding columns by dragging blocks together](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/columns-dragging.png) #### Customizing columns Once you've added columns, you can customize their appearance and behavior to suit your design needs. **Width and spacing** You can adjust the width of a column by clicking and dragging the gray bar between the columns. ![Adjusting column width](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/column-width.png) You can also adjust how much space appears between columns by dragging the green bar that appears on hover. ![Adjusting column spacing](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/column-spacing.png) **Stacking columns on mobile** When emails are viewed on a mobile device (when there is less horizontal space available) you can choose if your columns should stack vertically for better readability or stay as columns. In [block settings](#block-settings) (click on the drag-and-drop icon to the left) you'll find **Stack columns** (the default) and **Preserve columns** options. If **Stack columns** is selected, columns will be stacked when the viewport is 479px or smaller. ![Mobile stacking option](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/mobile-stacking.png) **Adding and moving content** You can drag elements like text blocks, images, and buttons in and out of your columns. To do this, hold down the `Cmd` key (Mac) or `Ctrl` key (Windows) while hovering over the element you want to move. Then click and drag the block settings icon (six dots) to move the block to its new position. ![Drag an element into a column](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/drag-into-column.png) #### Editing column content and settings Columns consist of multiple layers—the outer container and the inner column blocks. You can style both layers individually. **Outer container settings** Click on the drag-and-drop icon of the column block without holding any keys to access the outer container settings. Here, you can set the background color, padding, border radius, and alignment for the entire columns section. **Inner column settings** To edit the settings of an individual column or the elements inside it, hold down the `Cmd` key (Mac) or `Ctrl` key (Windows) while clicking on the element. This allows you to "drill through" the layers and select the block settings for the inner element. #### Tips * **No nested columns**: Keep in mind that nesting columns within columns is not supported. If you need a complex layout, consider adjusting your design to fit within the two-column limit. * **Consistent styling**: Use the block settings to ensure that padding and alignment are consistent across your columns for a polished look. ## Block settings Block settings let you edit properties of different element "blocks" in your email. If you hover over a block in your email, you'll see a drag-and-drop icon appear on the left. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/block-icons.png) You can drag this icon to re-order elements in your email. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/block-drag.png) If you click this icon you will open the block settings menu. Here you'll see styling options for padding, radius, background color and horizontal alignment. You can also duplicate and delete blocks from this menu. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/block-menu.png) ## Footer content We add an automatic footer into campaign and loops emails, which includes your company name, address and an unsubscribe link. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/footer.png) Unsubscribe links help prevent your messages from being marked as spam and ensure that your messages are willingly being received by an engaged audience, helping your deliverability. The unsubscribe link leads to a [Preference Center](/contacts/mailing-lists#preference-center), where your contacts can manage their own subscriptions. You can edit your company address from [Settings > Domain](https://app.loops.so/settings?page=domain). # Font support Font support via Google Fonts is now available in Loops. ## Web font support Custom font support via Google Fonts is now available in Loops. You can use any font from Google Fonts in your emails. To use a font, simply select it from the font dropdown in the email editor. Please note that not all fonts are supported by all email clients. See the table below for a list of supported email clients. | Support | Platform | | ------- | ---------- | | No | Gmail | | No | Outlook | | Yes | Apple Mail | | Yes | Samsung | | Yes | Comcast | | No | AOL | | No | Yahoo | ## General list of email-safe fonts The following fonts are supported by all modern email clients. They're considered "email safe" fonts because they're available on most devices and email clients. * Arial * Courier New * Georgia * Lucida Sans Unicode * Tahoma * Times New Roman * Trebuchet MS * Verdana # Personalizing emails Add context and personalization to emails. Every email you send from Loops can contain dynamic content connected to the contact you're sending to or the event that triggered the email. ## Types of dynamic content The three [email types](/types-of-emails) in Loops have different dynamic content available to them: * **Campaign emails** can contain **contact properties**. * **Emails within Loops** can contain **contact properties** and **event properties**. * **Transactional emails** can contain **data variables**. The three types of dynamic content are: * **Contact properties** are pieces of data related to each [contact](/contacts/properties) in your audience. There are a set of default properties like name and source, but you can also add any number of custom contact properties. If you sync contact data to Loops with the API, an integration or with CSV uploads, you can include that data in your email. * **Event properties** are pieces of data that can be sent along with every [event](/events) (which are used to trigger loop emails) via integrations or API calls. * **Data variables** are pieces of data included in [transactional emails](/transactional), which are populated in the API call. ## Add dynamic content to emails If you want to add dynamic content to a [custom MJML email](/creating-emails/uploading-custom-email), check the [Dynamic tag syntax](#dynamic-tag-syntax) section below. To add dynamic content to emails, simply select the corresponding icon in the toolbar of the email editor. ![Adding a contact property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/basic-merge.png) Depending on whether you're editing a campaign, loop or transactional email, you will have different options (explained above). When the email is sent, the dynamic content will be replaced with actual values from the contact, event or data variable. If you send an email and the dynamic content is missing a value, the email will not be sent. Make sure to add fallback values to avoid missed sends. A common example of using dynamic content is to personalize an email greeting by using a contact's first name. Once you've clicked on the icon in the toolbar, select the contact property you want to add. In this case, **First Name**. ![Selecting the First Name property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/editor-appear.png) When the email is sent, the contact's data will be added to your email, like this: `Hey Chris,` (if the contact doesn't have a First name value, it will read `Hey there,`). ## Custom properties Any contact attributes you sync with Loops either through the [API](/api-reference) or through one of our [integrations](/integrations) can be added to an email to personalize it. Likewise, any data variables you add to your transactional emails, or event properties you add to your events, will also appear as options in the editor. For example, say you wanted to create a [Scheduled digest](/guides/scheduled-digest-email) email with metrics about a contact over the last week. You could sync those metrics to Loops as [event properties](/events/properties). These properties will automatically be available to insert into any emails triggered by that event. ![Inserting an event property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/merge-tag-menu.png) ## Fallback values It is important to set a fallback variable for any dynamic content you add to your campaign and loop emails. If a contact does not have a value for the related contact property, or you don't remember to include an event property for an event-triggered email, the email will fail to send. Fallback values can be used to make sure default content is added to the email and ensure that it is sent. For example, if you set a fallback variable for “First Name” as “there”, if a contact does not have this contact property, the email will include “there” in the place of the contact's name. This is a great way to make sure that your emails still feel personalized to contacts even if there is no property available. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/fallback-variable.png) ## Dynamic tag syntax As well as adding dynamic content using the toolbar icons, you write "tags" directly in the email body. This is especially useful when [uploading custom emails](/creating-emails/uploading-custom-email). These tags are the only way to add dynamic content in [custom MJML emails](/creating-emails/uploading-custom-email). ### Contact properties If you have a custom contact property named `teamName` that you want to add to a campaign, you can write it surrounded by curly brackets in the email: ``` {teamName} ``` When the email is sent, the `teamName` value for each contact will be added to the email. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/merge-tag-0.png) For a list of all of your contact properties, visit the [API Settings](https://app.loops.so/settings?page=api) page. The **API Name** is the name you use within the brackets in your email, for example `{firstName}`, `{lastName}`, `{email}`, etc. ![Contact properties table](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/merge-tag.png) ### Event properties To add dynamic data to emails within loops using event properties, the tag requires an `EVENT_PROPERTY:` prefix: ``` {EVENT_PROPERTY:firstName} ``` ### Data variables To add data variables in transactional emails, the tag requires a `DATA_VARIABLE:` prefix: ``` {DATA_VARIABLE:firstName} ``` It's important to use camelCase format to type your tags. If you have any questions about how to format your tags, reach out to us! # Managing styles Learn how to edit emails in the Loops editor. ## Styled vs Plain emails Loops offers two different email styling options, Styled and Plain. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/styled-plain.png) By default new emails are created as "Styled" emails in the editor. This option offers a range of design options that can be used to design the base theme for your email, like text and background colors, padding and borders. A Styled email layout is a responsive central column with a maximum width of 600 pixels. Plain emails are full-width emails with no base design options (though you can always style individual content as described at the top of this page). Plain emails better mimic emails sent from clients like Gmail and Apple Mail. ## Style panel If the Styled option is enabled (see above), you will see a Style panel when clicking the pencil icon. This panel shows options for changing base styles for your email. This includes all text, headings, links, buttons and dividers in your email, as well as design options for the main email body (padding, background and border). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/message-visual.png) Any changes you make in the style panel will be applied to all matching elements in your email. If you want, you can always edit specific elements individually from their own formatting toolbar. (However, if you make changes in the email's style panel afterwards, individual styling will be overwritten.) ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/edit-button.png) There are helpful "Reset to default" icons if you ever want to return styling back to Loops' default. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/reset-style.png) ### Language selector We've included a helpful language selector in the style panel. Using this will tell email clients which language your emails are written in. This increases the accessibility of your emails, plus some email clients may use this language information to auto-translate email content or offer translation options. The language selector does not have a default value. We recommend selecting the correct language for each of your emails. ![Language selection menu](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/language-picker.png) If you send campaigns and loops in different languages, we recommend using a [custom contact property](/contacts/properties#custom-contact-properties) to store language information for each contact. Then, when it comes to sending emails, create emails in different languages (using the language switcher to specify the language), and [filter your audience](/contacts/filters-segments#audience-filters) for each email to match the language stored for each contact. For transactional emails, create a new email in each required language. You will need to store language information for each recipient in your application and map transactional IDs to their respective language groups. ## Saved styles Saved styles let you create repeatable styles that can be easily applied to new emails. Once applied, Saved styles can be tweaked on each email and changes will also be synced back to the main style set. This makes the same change on any other email that the styles are applied to. If your email is in Plain mode (see above), styles cannot be applied. ### Create a Saved style To create a Saved style, click the `+` plus icon in the style panel. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-style.png) Give your style a name and click the `✓` check icon or press Enter. This will create a Saved style based on the current styling options selected in the style panel and apply it to the current email. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/save-style.png) You can now add this Saved style to other emails. ### Apply a Saved style to an email To add an existing style to an email, open the style panel and select a Saved style from the dropdown. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/select-style.png) ### Edit a Saved style To edit a Saved style, first open an email with it applied, or apply it to an email. Then change individual style options to your liking. When individual styles are changed, you'll notice they are highlighted in purple, which denotes styles that have deviated from the main Saved style. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/edited-style.png) A purple "Update" button also appears. Clicking this button updates the Saved style and also *applies the changes to all emails that have the Saved style applied*. This makes it really easy to change styles across many emails at once. Alternatively you can create a new Saved style based on your changes by clicking the `+` plus icon. This new Saved style will be applied to the current email. (Emails using the previous Saved style will retain that Saved style's settings.) ### Make one-off changes to a Saved style You are free to make one-off custom design changes on an individual email. Just avoid clicking the "Update" button shown above as this will sync the changes to all other emails using the Saved style. Any subsequent updates made to the underlying Saved style will be synced to your email but your individual style changes made directly on the email will override any synced changes. ### Reset to a Saved style's defaults After making design changes to an email with a Saved style applied, you can get back to the Saved style's defaults by clicking the reset button next to the "Saved" title. This will re-apply the Saved style's design to your email and remove *all* custom edits. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/reset-saved-style.png) ### Remove a Saved style from an email To remove a Saved style from an email click on the `–` minus icon in the panel. This will revert back to any styles directly applied to the email before the Saved style was applied. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/remove-style.png) ### Duplicate a Saved style You can easily duplicate a Saved style if you want to create a similar style for different emails. Open an email with a Saved style applied, click the `•••` menu icon and select **Duplicate**. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/duplicate-style.png) Give your new Saved style a new name then click the `✓` check icon or press Enter to save. This will create a new Saved style using the current styles and apply it to your email. ### Delete a Saved style To delete a Saved style from your account, make sure the style is applied to the email you have open and then click the `•••` menu icon and select **Delete**. To make sure there are no design changes to emails in your account, the settings from the deleted Saved style will get applied individually to each email it was active on. However, please note that any overrides made to the saved style in the affected emails will still be retained even after the style is deleted. # Uploading a custom email Use Loops with Emailify, Email Love or MJML. While we do support importing MJML, we recommend using our editor to create your emails. It's the easiest way to create beautiful emails that work across all email clients. ## Overview We allow uploading of custom-coded emails created using a markup language called MJML. You can upload custom MJML for [all three types of emails](/types-of-emails) in Loops (campaigns, loop emails and transactional emails). **MJML** MJML is a markup language that helps you create responsive emails. It's an established framework that helps you create beautiful emails that work across all email clients. You can read more about it at [mjml.io](https://mjml.io/) **Figma** (via the Emailify and Email Love plugins) Sometimes you just want that special touch to an email that only Figma can give you. We get it! That's why we support plugins [Emailify](https://www.figma.com/community/plugin/910671699871076601/emailify-html-email-builder) and [Email Love](https://www.figma.com/community/plugin/1387891288648822744/email-love-html-email-builder), which help you create beautiful emails in Figma then export into Loops. ## MJML If you have your MJML ready, there is one step you need to complete before you can upload it into Loops... ### Add an unsubscribe URL In your email code, you have to insert an unsubscribe link. This keeps you compliant with email sending restrictions. All you need to do is add a `{unsubscribe_link}` tag into your MJML. When the email is sent, we will insert a contact-specific URL into this tag, which the contact can click to unsubscribe. ```HTML Unsubscribe ``` ### Add dynamic content You may want to add dynamic content (for example, a contact property or event property) into your email. You can do this using dynamic content tags. When the email is sent, Loops will replace this tag with the actual value. Remember to use camelCase format when typing your event property tags. For example, adding a first name [contact property](/contacts/properties) (in campaigns and loops) can be added like this: ``` Hello {firstName}, ``` In loops triggered by events you can add [event properties](/events/properties) with an `EVENT_PROPERTY:` prefix: ``` Hello {EVENT_PROPERTY:firstName}, ``` In transactional emails you can add [data variables](transactional/guide#add-data-variables) with a `DATA_VARIABLES:` prefix: ``` Hello {DATA_VARIABLE:firstName}, ``` [Read more about dynamic content tags](/creating-emails/personalizing-emails#dynamic-tag-syntax) ## Emailify Using Emailify, you can create well-designed emails inside Figma, then easily export them ready for Loops. Download the free [Emailify plugin for Figma](https://www.figma.com/community/plugin/910671699871076601/emailify-html-email-builder) and launch it. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emailify-plugin.png) ### Add blocks to your email To build your email you can drag and drop pre-made blocks provided by Emailify. Once added you can customize each block. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emailify-blocks.png) Loops will automatically download and host all of your email images so they can be reliably displayed to your audience. This feature results in an odd quirk that you should be aware of: Any text in your email that matches the path to one of your images will be replaced with the new path provided by loops. **For example:** `img/myImage.jpg` will be replaced with something like: `https://something.com/lkjn98s08hbAF/img/lkwekHBlhk78kasj.jpg` For most situations this won't be an issue, only text that exactly matches the path to one of your images will be replaced. ### Add dynamic content You may want to add dynamic content (for example, a contact property) into your email. You can do this using dynamic content tags. For example, adding a first name value can be added like this: ``` Hello {firstName}, ``` In loops you can add event properties with an `EVENT_PROPERTY:` prefix: ``` Hello {EVENT_PROPERTY:firstName}, ``` In transactional emails you can add data variables with a `DATA_VARIABLES:` prefix: ``` Hello {DATA_VARIABLE:firstName}, ``` [Read more about dynamic content tags](/creating-emails/personalizing-emails#dynamic-tag-syntax) ### Add the Loops footer While creating your email, you need to include an unsubscribe link. To do this manually, add a link with the URL `{unsubscribe_link}`. Alternatively, Emailify contains a pre-made Loops footer. Click **Footer**, scroll down until you see the Loops logo and click. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emailify-footer.png) You are then free to edit the text and design of the footer (just leave the link URL value as-is). ### Export your email When you're ready to export, click on **Export HTML** in the top right. Then in the dropdown select **Loops**, which will generate Loops-friendly MJML. You can add a Subject and Preview text in this step, too. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emailify-export.png) When you're ready, click the **Export for Loops** button and wait for the code to be generated. To download your files click on the **Download your .zip file** button. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emailify-download.png) ## Email Love Email Love is a Figma Plugin that enables you to design and export responsive and accessible HTML emails directly from Figma. Email Love includes an “Export for Loops” option that downloads an MJML file that can easily be imported into Loops. [Download the Email Love Figma Plugin](https://www.figma.com/community/plugin/1387891288648822744/email-love-html-email-builder) ![Email Love Figma plugin](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emaillove-plugin.png) To run the plugin, click the **Actions** menu in the Figma toolbar, then under the **Plugins & Widgets** tab select **Email Love -> HTML Email Builder**. ### Add components to your email You can now start designing your email template using Email Love’s pre-built components. Select the frame you created in the previous section (where you want components to be added). From the plugin pane select one of the component types and then the component you want to add. ![Adding components](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/emaillove-components.png) ### Customize components You can customize every component you add to your design. Each component features layers and frames that replicate the structure of MJML. For example, selecting **mj-section** in a header component enables you to update the background color and selecting **mj-image** in the header enables you to update the logo. Go through each component and update the text, images, and styles as you wish. ### Add an unsubscribe link For campaign and loops emails you need to include an unsubscribe link in your email. If you add a **Footer** component Email Love will automatically add a Loops unsubscribe link at the time of export. Alternatively you can code an unsubscribe link manually by adding a link element with the URL `{unsubscribe_link}`. ```HTML Unsubscribe ``` ### Add dynamic content You may want to add dynamic content (for example, a contact property) into your email. You can do this using dynamic content tags. For example, adding a first name value can be added like this: ``` Hello {firstName}, ``` In loops you can add event properties with an `EVENT_PROPERTY:` prefix: ``` Hello {EVENT_PROPERTY:firstName}, ``` In transactional emails you can add data variables with a `DATA_VARIABLES:` prefix: ``` Hello {DATA_VARIABLE:firstName}, ``` [Read more about dynamic content tags](/creating-emails/personalizing-emails#dynamic-tag-syntax) ### Export for Loops When your email is finished, click on the frame you want to export. Click **Export** and select "Loops" from the **Export** dropdown. Then click **Export to Loops** to generate a ZIP containing MJML and any included images, which you can then upload into Loops. ## Upload into Loops Once you have MJML with an unsubscribe link included, you can upload it into Loops. Click the upload icon above the email editor on the **Compose** page. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/import-mjml.png) If you exported from Emailify, open the ZIP you downloaded. Drag and drop the `.zip` file found inside the **\_zips (For upload to Loops.so)** folder. Then click **Upload**. If you exported from Email Love, upload the generated `.zip` file then click **Upload**. If you have custom MJML, create a ZIP file and drag it into Loops. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/step5-2.png) Your email is now uploaded into Loops and can be sent out! ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/thats-it.png) # Add a sending avatar Set up your email sending avatar through third-party platforms like Gmail and Outlook. Adding an avatar to your emails can improve open rates and brand recall. ![example of email sending avatar](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-a-sending-avatar.png) ## Basic setup 1. Login to the Google Workspace account linked to your Loops sending domain 2. Click the Google logo in the top right corner 3. Click "Add a Profile Picture" 4. Save For full coverage, add a [Gravatar](https://en.gravatar.com) icon to the email address. ## Advanced setup ### Adding a sending avatar on a secondary domain To send email from a secondary domain (e.g., mail.loops.so) with a sending avatar: 1. Log into your Google Admin console with an administrator account 2. Go to "Manage domains" in the dropdown menu 3. Click "Add a domain" and enter the domain name 4. Select "secondary domain" or "User alias domain" as appropriate 5. Add and start verification Once verified, the secondary domain will use the same sending avatar as the primary domain. Note that avatars are only visible within the same email client. To display your avatar across all clients, create accounts and upload avatars in each one's settings. Alternatively, consider using BIMI (Brand Indicators for Message Identification), a new email standard for displaying authenticated logos. BIMI requires some setup and legal work but provides an added layer of trust. # Insights from Google Postmaster Gmail doesn't report spam reports to Loops, so setting up Google Postmaster is key to getting an idea of your deliverability performance in Gmail. Google Postmaster is a free tool that provides users with in-depth data and analytics to help them understand how their emails are performing in Gmail. This tool provides senders with data on various metrics such as spam rate, domain reputation, email authentication, and delivery errors. By analyzing these metrics, you can identify the issues that may be causing your emails to be marked as spam or not delivered. You can signup for Google Postmaster here: [https://gmail.com/postmaster/](https://gmail.com/postmaster/) # Improve your inbox placement If your emails are not landing in the inbox folder you expect, here are some tips to improve your inbox placement. Your email deliverability is an ever-evolving metric that can be influenced by a variety of factors. It's composed of, but not limited to, a combination of the following: * sending IP * your domain * your content * email clients' history of those * the content of your current message * blocklists * sending frequency * TLD (Top Level Domain) Follow these steps to resolve any issues you may be experiencing with your email deliverability. When these steps are followed, we typically see a significant improvement in inbox placement. ## 1. Confirm Domain Record Setup Ensure your domain records are properly set up is a huge piece of the puzzle. Utilize [Bounce Doctor](https://bounce.doctor) to verify your domain settings if you are not a Loops user. If you already use Loops, you can just visit the [Domain Records](https://app.loops.so/sending-domain) page to see if your records are set up correctly. ## 2. Recent Domain Record Changes Following on the previous point, if you've added or updated domain records within the last 24 hours, wait for changes to propagate. This is especially important if you've just set up your domain for email sending. ## 3. Don't Send Cold Emails Avoid sending cold emails from your primary domain or a subdomain linked to it. Use a separate domain dedicated to sales efforts. Any cold emails at all can have a negative impact on your deliverabilty. Talk to your sales team. Please do not send cold emails with Loops. ## 4. Historical Use of Primary Domain for Cold Email If you have previously used your primary domain for cold emailing, you may need a new domain for sending emails, especially if your domain reputation has had issues in the past. It's not impossible to fix your domain reputation, but it can be a long process. If you're a startup, a fresh domain might be the right choice. ## 5. Send a Welcome Email [Implement a welcome email with a Loop](/loop-builder) to welcome new users as soon as they join. When a user signs up, they expect a welcome email, so intent is clear and engagement is high. Keeping a steady cadence of emails to all new users will help balance out larger email sends to your entire user base. This is especially important if you're a new sender. ## 6. Content of Emails Review the content of your emails and refrain from sending emails about crypto, giveaways, financial products or a regulated industry which are typically flagged by email clients. If you need to send these types of emails, review the content and ensure it's compliant with email client policies. ## 7. Use of Popular Top-Level Domains (TLDs) Consider sending email from a domain with a popular top-level domain (TLD) such as .com or .org. These TLDs are less likely to be flagged as spam by email clients. A few less common TLD's like .io, .ai and .so seem to be fine as well. However brand new TLD's as well as specificially .xyz and .info are often flagged as spam. ## 8. Send Content Your Users Want Ensure your email content is desirable and relevant to your audience. Focus on valuable updates such as product information rather than unsolicited giveaways or promotions. Would you want to read the email if it came from a service you use? If not, consider changing the content. ## 9. User Consent Only send emails to users who have explicitly opted in to receive communication from you. Do not email without explicit consent. ## 10. Google Postmaster Tools Set up [Google Postmaster Tools](/deliverability/gaining-insights) to gain insights into your email performance and identify potential issues. ## Additional Checks: Blocklists and IP Reputation If you've checked all the above and are still having issues, you may want to check if your IP address is on a blocklist. You can use [MXToolbox](https://mxtoolbox.com/blacklists.aspx) to check if your IP address is on a blocklist. Please note that blocklists change frequently and usually resolve themselves within a few days. # Maintaining a clean list A clean list is a happy list. Here’s how to keep your list clean and your emails deliverable. One of the pillars of good email deliverability is **list hygiene**. Keeping your list clean and up-to-date is a the first step to ensure that your emails are being delivered to the right people. Most of the emails on your list will likely be legitimate emails, but if you have an in-demand service, users may create temporary email addresses or attempt other methods to gain access to your service and evade limitations you set. Temporary or fake email addresses are often used once and then abandoned, and can cause your emails to bounce. If you send to too many of these addresses, your deliverability will suffer as the invalid ones will bounce and the valid (but fake) ones will mark your emails as spam. A few things you can do to keep your list clean: ### 1. Protect the inputs The easiest way to limit pain is to prevent it from happening in the first place. Start with cutting off the source of the problem by protecting the inputs (your signup forms). * Use a captcha * Enable Cloudflare’s bot protection on your site * Use form validation to ensure that the email address is valid * Block disposable email addresses * Use a double opt-in ### 2. Use a service that handles deliverability best practices for you * Loops automatically handles bounces, unsubscribes, and spam complaints for you * Just use Loops ### 3. Look for warning signs * If your open rates are low, it’s likely that you’re sending to a lot of invalid addresses or sending content your subscribers * If your open rates are high but then drop off, it could be a sign that your emails are being marked as spam or that poor list hygiene is causing your emails to bounce # Deliverability optimization How we monitor sends to increase the deliverability of your emails. During the email sending process, you may see a notification indicating that your email is being monitored for deliverability. This is a proactive measure to ensure that your emails are being delivered to the inbox. A combination of factors are taken into account that may affect deliverability, such as: * Queuing of emails for delivery * Email content analysis * Email sending patterns * Email sending volume * Client reputation and email sending history indicators * Much more! We have guardrails in place to protect our user's email deliverability (along with yours) and if you see this message, you're seeing one of those guardrails in action. Emails will be monitored on send to ensure they're hitting the inbox. If you have any questions, contact us via any existing channel or send an email to [support@loops.so](mailto:support@loops.so) # Subdomains vs root domains Most email services (including Loops) prefer sending emails from subdomains (hey.company.com) over root domains (company.com). Subdomains can often be easier to configure and can sometimes be more effective at preventing email from being marked as spam. It is important to remember that emails sent from a subdomain will also appear in the “from” field of your emails, so make sure to use an address that is recognizable to your audience. We recommend sending from a subdomain like: * hey.company.com * mail.company.com * updates.company.com Whichever you choose, you can always update it later in your [Domain Settings](https://app.loops.so/settings?page=domain). # Building your sender reputation Understanding how to build your sender reputation. This is a high-level guide, mostly based on our experience as the makers of Loops. We're going to touch on some industry terms and best practices, but ultimately we're focusing on how successful SaaS companies have built their sender reputation using Loops. You might be reading this guide because you're trying to understand if migrating to Loops and changing your domain is a good idea, if you need to warm up your IP or domain, or if you're just curious about how it all works. Your sender reputation, as defined by us, is a calculated value based on your domain and your sending IP. This calculation also considers factors like engagement rates, sending volume, and email content quality. We reference this throughout our documentation, and in our support chats, so it's important to understand how it works. Email clients, such as Gmail, Outlook, and others, also judge sender reputation using largely opaque algorithms. Each factor, such as engagement rates and sending consistency, contributes to whether your email will be successfully delivered. ## Sending Domain Your domain is the url (e.g., example.com) that you use to send emails. For example, [chris@loops.so](mailto:chris@loops.so) is my personal email address, but we send from [chris@mail.loops.so](mailto:chris@mail.loops.so). Using a subdomain like 'mail.loops.so' is part of our best practice and is also what we would refer to as your sending domain. A key rule in email sending is that a domain with a strong sending history—consistent volume and high engagement—helps your sender reputation. Conversely, a domain with a weak history can harm it. This can be frustrating when trying to improve your reputation, but if your current sender reputation is good, there’s no need to change your domain. Changing domains or IP addresses can lead to a reset of your sender reputation, requiring you to rebuild your score from scratch. This process can be risky and time-consuming, often without significant benefit if your current sender reputation is strong. It’s generally advisable to stick with your existing domain and IP unless you're facing significant deliverability issues. Other factors can also play a role: * The volume of emails you send * The type of content you send * The users receiving your emails Beyond that, factors like your subdomain, your domain's age, and even the TLD of the domain matter because they contribute to the perceived trustworthiness of your emails. ## Sending IP Your IP reputation is largely based on its sending history. If you send from an IP that has a strong sending history, it will help your sender reputation. If you send from an IP that has a weak sending history, it will hurt your sender reputation. Additionally, factors like email frequency, volume, content quality, and recipient engagement play important roles. If you change either your domain or IP, your sender reputation will need to be rebuilt, which requires following best practices and potentially reducing email volume until a strong reputation is established. Keep in mind that your sender reputation is made up of both your domain and IP. ## Wrapping Up Follow the best practices listed in our docs, send relevant content to engaged users, and maintain a consistent sending volume to keep your reputation strong. If you're starting fresh, consider using a subdomain for sending emails and keep your sending volume conservative initially. For any questions, just ping [chris@loops.so](mailto:chris@loops.so). # Sending to large audiences Tips for sending emails to a large group of new contacts. Most users in this scenario face two key challenges: * You have a large audience that you haven't contacted before or rarely have in the past. * Your domain has little to no sending history. As a result: * Users are not expecting emails from you, as they haven't received any in the past. * Email clients (Gmail, Outlook, etc.) don't recognize you, so your sender reputation is nonexistent. It’s **really important** to handle this well, so follow these steps or contact [chris@loops.so](mailto:chris@loops.so) with any questions. ### "Warming Up" Your List We want to build your sender reputation **and** start emailing your best users at the same time. Thankfully, this is easier than it sounds. The easiest way to do this is *not* by contacting your existing list but by contacting users that are just joining your platform for the first time. Why? Because in our experience, as individuals and as the makers of Loops, the most likely time a user will engage with your emails is when they first join your platform. ### Setting Up an Onboarding Loop So how do we start emailing these new users? Start by [setting up a Loop](https://loops.so/docs/loop-builder#building-a-loop) that greets users as soon as they join your platform. These users are the most active and are most likely to respond favorably, positively impacting your business and your sender reputation. You should let this Loop run for a few days while your sender reputation is built up and you have a chance to understand how your users react to the type of emails you're sending. Ideally, open rates will now be >40% at this point for the first email in your Loop. ### Engaging Your Most Active Users After your onboarding Loop has run for a few days, you can begin to backfill your audience in Loops by importing a CSV of your existing users or by using our API. While you can (and probably should) import all existing users at this point, we recommend contacting users that signed up over the last seven days prior to the start of the Loop we set up. Depending on audience size, you could also import users from up to three months ago, but our goal here is to make sure we are contacting users that still engage with your platform. Alternatively, instead of importing users by the creation date of their account, you can do it by their last login date if you have that information stored. If you don't have the information stored, you could start now and import users in a week or two after you have an idea of who your most engaged users are. ### General Recommendation for Importing Contacts A general recommendation would be to import any contacts who have logged in within the last 90 days and then also set up a Loop to welcome all new users going forward. Then you get transactional sending to any contacts in your audience free of charge as well. ### Expanding to Older Users After your most engaged users have been contacted and your Loop has continued to send, you should have a good idea of how your users are responding to emails that you send them. You can then begin to expand your audience and import some older users. We recommend sending in batches to limit any potential issues with the domain reputation. If you notice your open rates dipping below average at any point, it’s a good indication that you’ve reached as far back into your audience as you’ll want to go. At that point, it’s a good idea to only contact users that were previously on your list if they reengage on your platform with a login. ### Wrapping Up By following these steps, you should be able to successfully send emails to your user base without damaging your domain reputation. Remember to always monitor your open rates and engagement levels and adjust your email strategy accordingly. For any questions, just ping [chris@loops.so](mailto:chris@loops.so). # Email open rates Understand how open rates can be affected by clients and networks. Have you ever sent out an email campaign and noticed that some of the recipients showed as "opened" nearly instantly upon sending, while others took a while or didn't show at all? This discrepancy can be confusing and make it difficult to interpret your email open rates accurately. If your contacts are using Apple Mail or are behind corporate firewalls, it's likely that your open rates are being affected by email privacy measures. Here's what you need to know. ## Apple Mail Apple Mail Privacy Protection: Apple Mail is a popular email client that is used by millions of people worldwide. In an effort to protect user privacy, Apple introduced Mail Privacy Protection with the release of iOS 15, iPadOS 15, and macOS Monterey. This feature blocks email tracking pixels, obscuring email open rates, and making it difficult to know exactly when an email has been opened. If your recipient is using Apple Mail to access their Gmail or other email provider's inbox, then their email will be proxied through Apple's servers, which will affect your open rates. ## Corporate firewalls Many companies use firewalls to protect their network and prevent unauthorized access to their data. These firewalls can also affect email open rates by blocking or caching tracking pixels, which are used to track when an email is opened. If your recipient is behind a corporate firewall, then their email client may not be able to download the tracking pixel, which will affect your open rates. If they cache the pixel, it may show as opened immediately. So, what can you do to get a better understanding of your email open rates? Here are a few tips: 1. **Use other metrics:** While open rates can be a useful metric for measuring the success of an email campaign, they're not the only metric you should be using. Click-through rates, conversion rates, and revenue generated are all important metrics that can give you a better understanding of how your email campaign is performing. 2. **Use a different tracking method:** If you want to track email opens more accurately, you can use a different tracking method that doesn't rely on tracking pixels. For example, you could use a unique link in your email that redirects to your website. By tracking the number of clicks on that link, you can get a better understanding of how many people opened your email. Open rates are just one metric among many that you should be using to measure the success of your email campaign. By using other metrics, segmenting your audience, and using different tracking methods, you can get a more accurate picture of how your email campaign is performing. # Events Events let you trigger emails when something happens in an external platform. ## About events Events represent a payload of data that can be used to trigger emails. They can be sent to Loops with [the API](/api-reference/send-event) or via [an integration](/integrations). Events are used to trigger emails in a [loop](/loop-builder) and can contain [personalized data](/events/properties) for each email. ## Creating events Events can be created automatically or manually in several ways. As well as specifying a name, you can also create [event properties](/events/properties) with each of these methods. ### Automatically by sending an event You can create an event by sending an event with a new event name. For example, `creditWarning` here is a new event. This request will create a new event in your account. ```json { "email": "chris@loops.so", "eventName": "creditWarning", "eventsProperties": { "creditsPurchased": 1000, "creditsUsed": 903, "accountName": "Loops" } } ``` ### Manually from the Events page Navigate to the [Events page](https://app.loops.so/settings?page=events) to create an event manually. ![Add an event from Settings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-event-settings.png) ### Manually from an Event trigger node When [building a loop](/loop-builder), use the **Event received** trigger node to create an event (see below). ## Specifying an event for a Loop You can use events to trigger email sending within loops. When creating or editing a loop, select **Event received** in the "Trigger Type" dropdown. Type in the name of a new event or start typing the name of an existing event, then select from the dropdown. ![Add an event from the Loop builder](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-event-loop.png) ## Sending events Now you have events set up in your Loops account, you can start sending events using the API or integrations. ### Sending events with the API Using the API, send events to the [Send event endpoint](/api-reference/send-event): ``` POST https://app.loops.so/api/v1/events/send ``` All that's required is an `email` or `userId` to identify the contact, plus the `eventName`. ```json { "email": "sam@loops.so", "eventName": "planUpgrade" } ``` The `eventName` can either be an existing event in your account or a new event name. If a contact is not found with the provided `email` or `userId`, a new contact will be created. You can include [contact properties](/contacts/properties) in the request, which will be saved onto the contact. These contact properties can be used in emails to your contacts or for [filtering and segmenting your audience](/contacts/filters-segments). ```json { "email": "sam@loops.so", "eventName": "planUpgrade", "firstName": "Sam", "favoriteColor": "red" } ``` To provide your emails with event-specific data, include event properties in the request. [Read more about event properties](/events/properties) ```json { "email": "sam@loops.so", "eventName": "planUpgrade", "firstName": "Sam", "favoriteColor": "red", "eventProperties": { "newPlan": "Pro", "oldPlan": "Basic", "planPrice": 29, "isLifetime": false, "updateDate": "2024-03-07T10:09:23Z" } } ``` ### Sending events via an integration Many of [our integrations](/integrations#send-email) also support sending events, giving you options to send events without building with our API. ## Viewing and editing events In your Loops Settings the [Events page](https://app.loops.so/settings?page=events) shows the list of your different events, including how many times they've been sent. Counts are updated hourly. ![Events page](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/events-page.png) To edit event properties, click on an event. (You can also edit events by selecting "Edit properties" in the Loop builder's event trigger popup.) ![Editing an event](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/edit-event.png) Events cannot be deleted. Below the list of events, you can see the event stream for your account. This includes events sent to us [via the API](/api-reference/send-event) and any of [our integrations](/integrations). ![Event stream](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/events-stream.png) You can sort your events either by email, period of time or event name. You can even create complex segments to view events through a period of time by a single user. ## Testing events You can test events by sending to email addresses with `@example.com` and `@test.com` domains (for example `user1@example.com` and `user2@example.com`). Events will fire as normal but emails will not be sent to `@example.com` or `@test.com` email addresses, making this a good way to test how events are working in your account without affecting your sending domain’s reputation. # Event properties Learn how to use event properties to personalize your emails. You can include additional data for each event with event properties, which can be used to include personalized information in emails triggered by your events. Once included in an event, event properties can be added to emails within your loops. ## Adding event properties You can add event properties for your events from two places. ### Events page Firstly, you can add them from the [Events page](https://app.loops.so/settings?page=events). Click on an event and then **+ Add event property**. ![Adding an event property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-event-property.png) ### Loop builder Secondly, you add event properties from an **Event received** node in the loop builder by clicking on the **Edit event properties** button. ![Adding an event property in the loop builder](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-event-property-loop.png) ### Important information about event properties * Event properties must have unique names within the same event. * Empty spaces are trimmed from the beginning and end of property names. * Event properties can be one of four types: * String * Number * Boolean * Date * Date values can be either a Unix timestamp (*in milliseconds*) or an [ECMA-262 date-time string](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format), similar to contact properties. [Read more](/contacts/properties#dates) ## Editing event properties You can add, rename, delete, and change data types for event properties in the event editor popup (either from the [Events page](https://app.loops.so/settings?page=events) or within the loop builder, as above). ![Editing an event](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/edit-event.png) ## Sending event properties in an event You can add event properties to your [Send event](/api-reference/send-event) API request using `eventProperties`: ```json { "email": "sam@loops.so", "eventName": "planChange", "firstName": "Sam", "favoriteColor": "red", "eventProperties": { "newPlan": "Pro", "oldPlan": "Basic", "planPrice": 29, "isLifetime": false, "updateDate": "2024-03-07T10:09:23Z" } } ``` Conversion attempts are made for mismatched types (e.g. if you send a string for a number property). New properties not previously defined for an existing event will not be available when sending emails. Make sure you add event properties in Loops before including them in your event requests. If you send a request with a new `eventName` we will create a new event in your account and include the properties listed in `eventProperties`. ## Using event properties in emails Event properties you create become available in emails once Loops has received events containing them. Click on the Event properties icon `⚡️` in the editor toolbar (only available in emails within your loops) and select the property you want to add. ![Add an event property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/event-property-icon.png) {/* Unlike contact properties, fallbacks for event properties are optional. Missing a fallback prints that the property doesn't have one. */} # Custom form Integrate with Loops via a form endpoint, which will work with any type of custom form solution you have set up. Our form endpoint lets you add new contacts to your audience from an HTML form or using JavaScript. ## Find the form endpoint To submit data to Loops you will need to retreive the form endpoint URL that's linked to your Loops account. 1. Go to the [Forms page](https://app.loops.so/forms) in your Loops account. 2. Click on the **Settings** tab. 3. Copy the URL shown in the **Form Endpoint** field. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/form-endpoint.png) ## Create a form In a form, add `input` elements for each of the contact properties you want to collect. You can add fields for any of the [default contact properties](/contacts/properties#default-contact-properties) plus any of your custom properties. For each form field use the "API Name" value found from your [API settings page](https://app.loops.so/settings?page=api) as the `name` attribute. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/contact-properties-table.png) Here's a simple example form that collects name, email address and assigns a custom user group: ```HTML
``` To add subscribers to mailing lists, include a hidden field called `mailingLists`. You can use a single mailing list ID or to add subscribers to multiple lists, use a comma-separated list of mailing list IDs. Make sure that any mailing list you add to a form is [Public](/contacts/mailing-lists#list-visibility). You cannot subscribe contacts to private mailing lists from the form endpoint. ```HTML
``` If you need a hand integrating with your custom form, just shoot [adam@loops.so](mailto:adam@loops.so) an email and we'll help you integrate with anything your specific setup ✌️ ## Submit with JavaScript If you would rather submit a form using JavaScript, you can make a `POST` request to your form endpoint. Make sure to set the `Content-Type` header to `application/x-www-form-urlencoded`. ```javascript function handleSubmit() { const formBody = `firstName=${encodeURIComponent(firstName)}&email=${encodeURIComponent(email)}&mailingLists=${encodeURIComponent(mailingListIds)}`; // Change this URL to your own endpoint URL fetch("https://app.loops.so/api/newsletter-form/YOUR_FORM_ENDPOINT", { method: "POST", body: formBody, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); } ``` Responses from this form endpoint will be one of the following: ```json HTTP 200 { success: true } ``` ```json HTTP 400 or 500 { success: false, message: "A descriptive error message." } ``` This endpoint is rate limited. If you go over the limit, will see a `HTTP 429` error just like with the API. [Read more about how to handle 429 responses](/api-reference/intro#rate-limiting) ## FAQ We rate limit requests from the same IP to once per minute and also do not allow duplicates of the same email address. It's likely the error is related to one of these cases. # Simple form Collect signups from any web page with a customizable form. Check out our specific documentation for adding forms to [Framer](/integrations/framer) and [Webflow](/integrations/webflow). You may want to collect signups from your website directly into Loops. ## Generate the form Easily generate an HTML or JSX form for collecting new signups for your mailing list from inside Loops. Go to the [Forms page](https://app.loops.so/forms), customize your form (such as mailing list, layout, button color and success message) and copy the HTML into your website. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/simple-form.png) ## Add more fields to the form It's possible to add other fields to your form if you want to collect more than just email addresses. When adding new fields, use the "API Name" value found from your [API settings page](https://app.loops.so/settings?page=api) as the `name` attribute for each field. For example, this field would collect a First Name: ```html ``` If you want more flexibility when creating signup forms, [read about creating custom forms](/forms/custom-form). # Sending emails to Apple “Hide my email” addresses Apple has recently begun offering their iCloud+ users more and more privacy options when it comes to how they move and appear on the internet. What does this mean for email? It turns out… quite a bit. When it comes to email deliverability, [Hide My Email](https://support.apple.com/guide/icloud/set-up-hide-my-email-mm9d9012c9e8/icloud) provides Apple users the opportunity to create unique, random email addresses that forward to their actual email address. This allows them to keep their real email address hidden from online services and email marketers (likely, you). This can pose challenges for businesses and marketers that hope to track user engagement or maintain an updated and engaged email list. ## Sending emails to customers using Hide My Email Sending emails to customers that are using Apple’s Hide My Email feature can result in hard bounces — meaning your email will not be delivered. If you’re using [Loops](https://loops.so/) as your ESP (email service provider) to send your marketing and transactional emails, an additional step is needed to ensure these members of your audience remain in the loop (pun intended). It’s simple. You will need to register your sending domain with Apple through these quick steps. 1. Login to the Apple Developer Console 2. Click **Services** under the category labeled Certificates, Identifiers & Profiles ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/ePMohygJlAarcRmFAv6GtcE9w.webp) 3. Click **Configure** ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/Gr2ItNdb8mCINSZHpBbHOoPhqs.webp) 4. Click the `+` icon under Email Sources ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/WGNTKDV9jsOJ1euiPgzoptQsfk.webp) 5. Verify SPF and Send Test ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/du5rsZ3IXp1Tec0HRJc5vsxL8.webp) You'll want to register the email address that we provide for you in-app as well as the usual domain. For example: `envelope.yourdomain.com` (envelope is the part we are generating for you) and `yourdomain.com`. That’s it! Goodbye hard bounces and hello inbox. ## What if I don’t do this? If you don’t register your sending domain with Apple prior to sending emails it may very likely result in a hard bounce (email will not be delivered to the intended recipient). If you are using Loops and notice hard bounces to these email addresses in your campaign metrics please follow the above steps to register your domain. Once your domain is registered, reach out to us and we can manually remove the emails from our block list. # Bubble API Connector Send data to Loops from Bubble using the API Connector plugin. This guide helps you set up an API connection to Loops from your Bubble app. In this example, we will create an integration that allows you to sync Bubble users to your Loops audience. We have created an [official plugin for Bubble](/integrations/bubble), which contains actions for all Loops API calls. However, it is limited to what it can do by how Bubble works.\ THis tutorial helps if you need more control or flexibility over the data that is sent to Loops. ## Install the API Connector plugin In Bubble, you need to first install the API Connector Plugin. This plugin is what makes the API calls to Loops. Go to Plugins and click **+ Add plugins**. Search for "API Connector" and click **Install**. ![Search for API Connector](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-search-plugin.png) ## Add a Loops API connection Now you have the plugin installed, you can add an API connection. Click **Add another API**. In the form that appears, enter "Loops" into the **API Name** field (Bubble uses this field to group API calls together in the interface). Make sure the **Key name** value is "Authorization" (this is the default). In the **Authentication** field, select "Private key in header". In Loops, go to your [API Settings page](https://app.loops.so/settings?page=api) and copy an API key (you can create a new key if you like). Back in Bubble, in the **Key value** field, write the word `Bearer`, then a space character, then paste your API key, so it looks something like `Bearer YourApiKey`. ![Add the Loops API](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-connect-api.png) ## Add an API call to sync users to Loops Now you can create an API call using the connection you just created. Click **Add another call** at the bottom of the gray box. In the **Name** field, write something like "Sync users". In the **Use as** field, select "Action". The **Data type** field should be "JSON" (this is the default). In the dropdown where it says "GET", select "POST", and in the subsequent field, paste the following endpoint URL: ``` https://app.loops.so/api/v1/contacts/update ``` ![Add an API call](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-add-api-call.png) Now all that's left is to set up the request body, which is the data that's sent to Loops. This will depend on what kind of data points you want to send. In this example, we will add a [custom contact property](/contacts/properties) called "Plan name" (which we [already added in Loops](/contacts/properties#add-a-property)). Due to how Bubble handles empty values, make sure to only add properties to this request body that will always contain a value. Otherwise, Bubble will send `"null"` as the value instead of an empty value, which will mean, for example, your contact's first name will become `null`. To make the request, we have to include an email address (which is required to create new contacts in Loops). Then we'll also add a "User ID" value (which will maintain a distinct connection between a user in Bubble to their contact record in Loops) plus the "Plan name" property. Due to the data in my example application, I know all three of these fields will always have a value. In the **Body** field, paste this: ```json { "email": "", "userId": "", "planName": "" } ``` If you then click anywhere outside the **Body** field, you'll see two sets of fields appear below it, one for each of the three custom variables (`Email`, `User_ID` and `Plan_name`) we added to the body data. Uncheck the **Private** checkbox for each of these variables, otherwise they won't show up in the Bubble interface when you come to use this API call. ![Adding body data](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-body-data.png) The final step is to initialize the call, which will show Bubble that the API call works. To do this, enter some example values in each of the **Value** fields, then click **Initialize call** (this will send real data to Loops). If it works, you'll see a success message and a confirmation of the returned values from Loops. Click **Save** to complete setting up the API call. ![Request success](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-success.png) If the request is not successful, you should see a message telling you what went wrong. When you're done, remember to delete the test data you just added in the **Value** fields. ## Add the action to your Workflow The final step is using your API call inside your application. To do this, go to **Workflows** in your Bubble account and create a workflow for when you want to send a user to Loops. In this example, I want to sync users to Loops whenever they log in (they could also be synced after other events, like if their email address changes). To do this, click **Click here to add an event...** and select "General > User is logged in". Click **Click here to add an action...**, hover over **Plugins** and select the action you created by the name you gave it (naming is organized by "API name - API call name"). ![Select the API call](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-select-call.png) This will show a popover containing the fields you added in the previous step, allowing you to insert your data in Bubble into the API call. Click into a field and then **Insert dynamic data**, then select the data you want to send to Loops. (In my example I searched for "Current User" and then selected `'s email`, `'s unique ID` and `'s plan` to fill out the three fields.) ![Add data to API call](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-add-data.png) Now when the selected event happens, Bubble will make an API call to Loops! To verify everything is set up correctly, log in to your Bubble app (to trigger the event) and then check the [Audience page](https://app.loops.so/audience) in Loops to see if your user account appears. ## Other examples There are lots of different ways you can use Bubble's API Connector to send data to Loops: * Create a newsletter subscription form and send all submitted email addresses to Loops. * Sync contacts whenever a user's email address is changed in Bubble. * [Trigger an event](/events) in Loops for all new sign ups using the [Send event endpoint](/api-reference/send-event), to enter new users into a welcome email sequence. * Periodically sync [contact properties](/contacts/properties) to Loops so you can send personalized emails. ## Learn more } href="/integrations/bubble" > Manage contacts and send emails with our Bubble plugin. View all of our API endpoints. Learn more about custom contact properties. # Set up a welcome email sequence for new Ghost members Send automated email sequences to new Ghost subscribers using Zapier and Loops. This guide helps you set up email sequences that get sent to all new subscribers to your Ghost site. With Loops, you can set up sophisticated sequences called "[loops](/loop-builder)", allowing you to send a range of welcome emails to your members over a period of time. Using [Zapier](https://zapier.com), we can automatically add every new member who signs up on your Ghost site to your Loops audience. ## Set up the Zapier Trigger The first step is to connect your Ghost site to Loops using Zapier. Sign up to Zapier and create a new Zap using Ghost's **Member Created** Trigger. ![Add Ghost trigger](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-ghost-trigger.png) This creates an automation that will trigger every time a new member is created in Ghost. Zapier will then send the contact's information over to Loops. Now connect Ghost from the Trigger by clicking **Account** then **Sign in** and then pasting in your Ghost API key and API URL. To find these, go to Settings in your Ghost admin, search for "Integrations" and click on **Zapier**. ![Sign in to Ghost](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-sign-in-ghost.png) ## Set up the Zapier Action Next you need to link up Loops as the Action. Click the **Action** node, search for Loops and select the **Send Event** option in the Event dropdown. Click **Continue**, then **Sign in** and paste in your Loops API key (which you can find from your [API Settings page](https://app.loops.so/settings?page=api)). ![Sign in to Loops](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-sign-in-loops.png) Instead of using an event for the loop trigger, you could also choose to send the loop to every new contact created in your audience.\ We choose the event trigger in this example as you may add other contacts to your audience from other sources than Ghost. Now click **Continue** to move to the **Action** tab. This is where you can configure which Ghost data is sent to Loops. As you click each field, you can select the different Ghost-provided data. In **Email**, select the Email field. In **User ID**, select the ID field. In the **Event Name** field, write something like `newMember`. This is the name we'll use in Loops to trigger the email sequence. You can use any name, but make it descriptive. You'll need this name in the next step inside Loops. ![Adding an event name](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-ghost-email.png) If you want to include contact-related information in your emails you can use [event properties](/events/properties). To do this, add more Ghost data in the **Event Properties** field. For example, if you want to include the user's name, subscribed status, member status or your newsletter's name, you can add these properties here. Click the `+` button to add new properties each time. ![Adding event properties](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-ghost-properties.png) Click **Continue** to see an overview of the event and its data that you'll send to Loops. Here you can test the action works by clicking **Test Step**. This will send actual data to Loops, which you will see in the Event Log section on the [Events page](https://app.loops.so/settings?page=events). When you're happy everything is set up properly, click **Publish**. If you ever change the event properties sent from Zapier, you need to update the event data in Loops to match. You can do this from [Settings -> Events](https://app.loops.so/settings?page=events). Click on the event and edit the listed properties. [More info](/events/properties#editing-event-properties) ## Create an email sequence Now that the connections are set up, you can create the email sequence "loop" in Loops. Go to Loops and click on **Loops** in the sidebar. Click **New**, which will create a new loop and show the [loop builder](/loop-builder). Select the **Event is fired** trigger option. Click on the **Event received** trigger in the loop builder and enter the name you entered in Zapier in the previous step (in this example, `newMember`). ![Select the event trigger](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/member-trigger.png) You can edit the **Timer** node if you want to add a delay between the event being received by Loops and the email being sent to your Ghost members. ![Set a timer delay](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/timer-3-days.png) You can also add a filter to the audience for this loop, if you want to limit sending to a certain sub-group of your members. If you want the loop to send to all new Ghost members, just leave it as "All contacts". Lastly, click the **Send email** node and then **Create email**. You'll see the [email editor](/creating-emails/editor) open, where you can create your email. If you opted to send event properties from Zapier, you can add them to your email by clicking the `⚡️` button in the editor toolbar. ![Insert event property](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/add-event-properties.png) Once your first email is complete, you can add more Timer and Email nodes to your loop to complete your email sequence. Just click on the `+` icon between nodes to add new ones. ## Learn more Read more about triggering emails with events. Learn how to create stylized emails and add personalization. } href="/integrations/zapier" > Manage contacts and send emails from thousands of other platforms. # Guides ## Deliverability Improve your email deliverability with these guides. ## Integrations How-to guides for connecting Loops to other platforms. } href="/guides/bubble-api-connector" /> } href="nextjs" /> ## Creating emails How to create different kinds of emails in Loops. ## Email basics Learn more about email. ## Docs blog * [How we create our documentation](/guides/how-we-work-documentation) # How to set up customer lifecycle emails in Loops How to send onboarding, dunning and churn emails to your customers with Loops. For SaaS companies, there are a few important events in a customer lifecycle that email can help with. * **Acquisition emails** are emails sent after a user signs up for a waitlist, platform or newsletter. * **Onboarding emails** help brand new users get familiar with—and get the most out of—your product. * **Retention emails** keep your users engaged long-term. * **Re-engagement emails** attempt to get users active again if they haven't used your platform recently. * **Dunning emails** help reduce churn by prompting the user to re-activate a subscription or fix payment issues. * **Re-activation emails** attempt to bring users back after a cancelation. With Loops, you can easily set up "loops" (email workflows) plus our API (or an integration) to help with each of these use cases. ## How it works To get this set up in Loops, the idea is to create a [loop](/loop-builder) for each of the types of email you want to send. So there would be a loop for activation emails and a loop for dunning emails and so on. A loop is like an email sequence containing emails, time delays, audience filters and an initial trigger. We will use a [custom contact property](/contacts/properties) called `subscriptionStatus` to enter users into each of the loops at different times in their subscriptions. This property is used as the [loop trigger](/loop-builder/loop-triggers); if the property is ever changed—using [an integration](/integrations) or the [Loops API](/api-reference)—we trigger a loop, which will send emails to the contact. ## Add a contact property First, let's add the contact property to the audience in Loops. Go to your [Audience page](https://app.loops.so/audience). Click on a table header to show the dropdown then select **Add property**. Enter "Subscription status" into the **Name** field and make sure **String** is selected in the "Type" field. (You'll notice that the "stored name" of your property is `subscriptionStatus`. This is the name we'll use in the API and integrations). Click **Add Property** when you're done. Your new property was added to the far right of the Audience table. ## Update contacts Now you can click on contacts and update the value manually one-by-one, or use the API or an integration to update contacts programmatically. To update contacts in bulk you can download your Audience as a CSV, update values and [re-upload the file](/add-users/csv-upload). ## Create loops The next step is to create the loops, one for each type of email. Repeat the following step for each of the different subscription statuses you want to send emails for. Go to the [Loops page](https://app.loops.so/loops) in your account and click **New**. Select the **Contact update** option, which will create a new loop using that trigger. ![Select trigger](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/triggers.png) You will enter the [loop builder](/loop-builder). Click on the **Contact updated** node to set up the loop trigger. ### Set up the trigger 1. Select the "Subscription Status" property from the **When** dropdown. 2. The **Changes from** dropdown should have "Any value" selected. 3. Netx you need to specify wheat value Loops should look out for to enter users into the loop. For example, for onboarding emails, you can choose "is empty" from the **To** dropdown. For re-activation emails select "Equals" from the **To** dropdown and enter a relevant value like "Canceled". 4. **Trigger time** should be set to "Every time"; this will make sure users are entered into the loop every time that the Subscription Status value matches. ### Create emails Now create the email(s) you want to send from each loop. You can add as many emails as you like into each loop, separated with timers. If you want to send emails to certain groups of contacts, you can use Audience filters in your loop. We have a range of [email templates](https://app.loops.so/templates) available, which you can use as a base for all of your subscription emails. ![Templates](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/getting-started.png) If you want to use cohesive branding in your emails, make use of [Saved styles](/creating-emails/editor#saved-styles) in the editor. ![Saved styles](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/message-visual.png) ## Update the Subscription status value The final step is to make sure that your contacts have the correct "Subscription status" value assigned to them in Loops. This will make sure they are in the correct loops at the correct moment within their subscription. Using the API you can make a simple `PUT` call to the [Update contact endpoint](/api-reference/update-contact), with a request containing the contact's email address and subscription status. (You can also add more contact properties in this call if you want to add more data to the contact in Loops.) Using the Update contact endpoint will either create or update a contact in Loops using the provided email address. ```json { "email": "canceleduser@gmail.com", "subscriptionStatus": "Canceled" } ``` Many of [our integrations](/integrations) allow syncing of contact data. Just make sure you create or update contacts and include the current `subscriptionStatus` value. Now you have set up different loops to trigger when the "Subscription status" value changes, your users will be automatically entered into each loop and receive the correct emails at the correct times during their subscription lifecycle. ## Example loops Here are some example loops you can create along with a suggested trigger and loop contents. ### Onboarding Sent to brand new users. * **Trigger**: Contact added or Contact updated (e.g. `subscriptionStatus` is empty). * **Audience filter**: Match the trigger (e.g. `subscriptionStatus` is empty). * **Loop contents**: 1–5 welcome and onboarding emails over the first 30 days. ### New subscribers Sent to users who just started paying. * **Trigger**: Contact updated (e.g. `subscriptionStatus` changes to "Paying"). * **Audience filter**: Match the trigger (e.g. `subscriptionStatus` equals "Paying"). * **Loop contents**: 1–3 emails about paid-only features over the next 3 days. ### Dunning Sent to customers who had a first failed payment. * **Trigger**: Contact updated (e.g. `subscriptionStatus` changes to "Failed"). * **Audience filter**: Match the trigger (e.g. `subscriptionStatus` equals "Failed"). * **Loop contents**: 1–3 emails during your dunning period. ### Churn Sent to customers who have just cancelled their payments. * **Trigger**: Contact updated (e.g. `subscriptionStatus` changes to "Canceled") * **Audience filter**: Match the trigger (e.g. `subscriptionStatus` equals "Canceled"). * **Loop contents**: 1 email saying goodbye and asking for feedback. # Open rates are a vanity metric And why that’s okay. When it comes to email marketing, there are countless metrics that you can track. Some of these metrics may be fun to track and look great on paper (your computer screen) but unfortunately don’t actually lead to improved business results. These are vanity metrics. While you might think that vanity metrics help paint a picture of success or improvements to unknowing stakeholders, they are ultimately a distraction. Facebook likes, Twitter followers, blog pageviews… these are all vanity metrics because they don’t materially impact the success of your business in a tangible way. But so are email open rates. Yes, you read that right. The main metric you’ve been tracking (and sharing) to gauge the success or failure of your email marketing campaigns is (mostly) just for show. ## What is an email open rate? An email open rate is the number of unique opens your email campaign receives. Calculating open rate is simple: Number of unique email opens / Number of delivered emails x 100 = Open Rate Delivered emails are important here because hard bounced emails do not count towards this calculation. Note: a hard bounced email is an email that cannot be delivered due to an unchanging and permanent reason. Unfortunately, there is nothing that you can do to reverse this to force your email through. Some common reasons for a hard bounce are recipient email address doesn’t actually exist, recipient mail server doesn’t exist, and invalid domain name. While a hard bounce will prevent your email from arriving in its intended inbox it is possible that a hard bounce could be caused by something as simple as your recipient having a typo in their email address. To keep things simple, let’s assume you sent and delivered an email to 100 different users in your campaign. 23 of your users opened this email giving you an open rate of 23%. 23 / 100 = .23 x 100 Easy enough, right? Up until recently, open rates were a universally loved way to gauge campaign performance. So what changed? ## Your open rate is (now) a vanity metric Apple changed — mostly. With a dominating [59.8% email client market share](https://www.litmus.com/blog/email-client-market-share-february-2022/), any change they make is a huge deal. New privacy initiatives from many of the largest email clients are quietly removing email senders ability to successfully track open rates. Apple is leading the charge with their new “Mail Privacy Protection” (first introduced in 2021 with iOS 15) that allows users to opt-in to having Apple pre-load their email upon receipt. By doing so, the email’s tracking pixel will immediately trigger. Note: an email tracking pixel is a 1px by 1px square image that is inserted into an email and is transparent in color and invisible to the recipient. These tracking pixels are what allow marketers to measure open rate, click rate, traffic sources and more. This change artificially inflates open rates as delivered emails will now automatically show as opened whether they truly have or have not been opened by the intended recipient. These changes to email open tracking are completely out of the sender’s control. And to top it all off, these changes affect not only users receiving emails on the Apple Mail app itself but also those who use an Apple client in any way (r[egardless of email server](https://www.litmus.com/blog/apple-mail-privacy-protection-for-marketers/)). This means that using GMail, Yahoo, Outlook, etc. on iOS 15 may also contribute to your now-inflated open rate. What was once a key metric to track and share with key stakeholders should now be viewed with a bit more skepticism. ## What’s next? It is highly unlikely that these recent privacy changes will be reversed anytime soon (ever). So what can you do about it? The answer may surprise you. We suggest continuing to run your campaigns as you have been. Continue to send consistent emails (both in frequency and content), create intriguing and accurate subject lines and body content, segment your list so that only the most relevant audience is receiving your emails, and pay close attention to your unsubscribe rate. However, now you should put a greater emphasis on tracking specific product goals in relation to your email campaigns. Have actionable metrics in mind – metrics around business goals such as generating free trials, converting trials to paid, increased usage of your app or inviting team members. And actually, this should have been the goal all along. High open rates don’t necessarily lead to increased sales or profits for your business, the end result of those opens is what really makes the difference. Now that your focus has transitioned to more tangible KPI’s that lead to specific product goals being met it will be much easier to accurately measure the success of your email marketing campaigns. Looking back on it, maybe your email open rate becoming a vanity metric overnight will ultimately be a blessing in disguise. Take this as an opportunity to refocus your attention on the KPI’s that ultimately matter for your business. High open rates may have been impressive in the past but now it’s time to craft and measure your email campaigns against the things that truly move the needle. # Product updates Our updated, definitive guide for sending product updates. ## Introduction A product update should be sent once a month with updates about what you shipped recently. This typically includes new features, improvements, and bug fixes. Things to keep in mind: * Brevity is key. Users don't want to read a novel. * If you send valuable content, users will come to expect (and open) it. * It's okay to send multiple emails in a month if that's your shipping cadence. Learn more tips for crafting effective emails and improving open rates. ## How to craft a product update email: Example of a product update email sent from Loops 1. [Create a new email Campaign](/creating-emails/editor) 2. Add your logo at the top of the email, [save it as a component](/creating-emails/components) if you haven't already. 3. Add a simple subject line, like "The latest updates from Loops" 4. Add a simple intro paragraph, with a link to the full changelog, potentially socials and a highlight of the most important changes along with a call to action to read the full email. 5. Try to limit the number of updates to 2-3, and make sure they are relevant to the content. It's better to have a single or a couple impactful, relevant updates than a long list of updates that are not relevant to the user. 6. In the footer, add a link to the full changelog, socials, and a link to unsubscribe. 7. [Send the email!](/creating-emails/sending-first-email) Be careful not to overwhelm your users with too many updates. Focus on the most impactful changes. ## Choosing your audience ### For new senders If you're just starting out, send to all users and maintain a steady cadence over time. ### For established senders If you have an existing list but haven't sent product updates before: * Send to active users who have engaged with your product in the last 30 days * Include users who have created an account in the last 30 days * If the audience size is less than 5,000 users, send to all users Learn how to create targeted segments for your product updates. Learn how to create and send your first email in Loops. Add dynamic content to make your product updates more relevant. Create reusable elements for consistent product update emails. Explore different types of emails you can send with Loops. # How to create a scheduled email with Loops How to send a daily, weekly or monthly email with a summary of what's happened in your app. This email type may also be referred to as a "rollup" email or a "summary" email. The idea is to send a single email that summarizes what's happened in your app over a period of time. These kinds of digest emails are a great way to keep your users engaged with your app. The best way to do it today is a Loop with an [event trigger](https://loops.so/docs/loop-builder/loop-triggers) set to fire “every time” with an event payload containing the updated property you’d like to send. Then at the end of the day, week or month you can trigger a digest email with a summary of the events that happened. ## Create the loop and event Go to the [Loops](https://app.loops.so/loops) page and create a new loop. Choose the **Event is fired** trigger. You will enter the loop editor. Click on the **Event received** node and type the name of your event. You can re-use an existing event or create a new one from this input. For example, you can use a name like `sendDigest` and then click **+ Create new event**. ![Creating a trigger node](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/scheduled-trigger.png) Next, click on the **Edit event properties** button to add [properties](/events/properties) to your event. Properties are extra pieces of data that you can attach to each event. This data is then made available in every email you send. In the event editor overlay, click **+ Add event property** and add any properties you want to include in your digest email. ![Adding event properties](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/scheduled-event-properties.png) Make sure the "Trigger frequency" dropdown in the **Event received** node has **One time** selected, as we only want to email each user once per event. ## Create your email The next step is to create the email you send to each contact. Click on the **Send email** node and then **Create email**. This will open the email editor, where you can [create your email](/creating-emails/editor). ![Adding event properties in the editor](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/scheduled-editor.png) When you want to add the event properties you created in the previous step, click the `⚡️` button above the editor. You will see a list of each event property, which you can then [insert into your email](/creating-emails/personalizing-emails). Make sure to [add fallback values](/creating-emails/personalizing-emails#fallback-values) for every property; if an event doesn't have a value for a property in your email, the email will not send. Fallback values make sure that emails are sent to every contact. When you've finished creating your email, click **Start** in the top right. This will make your loop active and you can start triggering it by sending events. ## Trigger events To send events to Loops you can use an [integration](/integrations), an [SDK](/sdks) or [our API](/api-reference). With the API, it's just a case of making a request to the [Send event endpoint](https://loops.so/docs/api-reference/send-event) containing the contact's details, the event name and your event properties. ```json { "eventName": "sendDigest", "email": "user1@gmail.com", "eventProperties": { ... } } ``` ## Learn more Learn about creating email sequences in our loop builder. Read more about triggering emails with events. Learn how to add dynamic data to your emails. Find out how to send events using our API. # How to resend a campaign to new subscribers A quick guide for sending a campaign to subscribers who signed up since you sent it out. If you're regularly adding new subscribers via [a form](/forms), [integration](/integrations) or [the API](/api-reference), it's likely that you will want to send a campaign to new contacts who signed up after you sent it initially. In Loops, you can do this in two easy steps: duplicating the campaign and filtering the audience. ## Duplicate the campaign The first step is to duplicate the campaign so that you can send it out again. To do this, go to the Campaigns page and click on the `•••` menu icon, then select **Duplicate**. ![Duplicating emails](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/duplicating-emails.png) ## Send to your new contacts When you're ready to send the email, it's important that it only gets sent to contacts that haven't been sent it already. To do this, on the **Audience** page of the sending flow, select "Not Sent" from the first dropdown and then the *original* campaign from the third dropdown. ![Filter audience by sent campaign](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/campaign-filter-resend.png) This will update the total contacts and the table below, which will show contacts that were not sent the original campaign. Check that everything looks OK and continue sending the email. # What is BIMI? And why does it matter? As an email marketer, ensuring that your emails actually get seen and read is priority #1. Adding an avatar to your emails can increase your open rates by up to 21%. The familiarity and trust that this little avatar brings has a great impact on your email campaigns. Outside of manually adding your brand’s logo as an avatar to each individual email client, there may actually be another way to ensure your emails have the same visual aid with additional layer of trust built in. How? BIMI. ## What is BIMI? BIMI, or Brand Indicators for Message Identification is a relatively new email standard that allows you to add your brand’s logo to your authenticated emails. Adopting BIMI will allow your brand’s logo to appear next to your emails in your recipient’s inbox without manually needing to add and maintain it on a provider by provider level. As long as the email client supports BIMI, your logo will appear. ## Which email providers support BIMI? A growing number of email provider’s are currently supporting BIMI with many more currently considering it. The current list of supported providers are: * Gmail * Google Workspace * Apple Mail ([macOS Ventura 13, iOS 16, and iPadOS 16, or later](https://developer.apple.com/support/bimi/)) * AOL * Netscape * Fastmail * Yahoo (Yahoo Japan is not currently supported but is under considering for future adoption) * Pobox Here is the full breakdown of who does and doesn’t support BIMI as of right now: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/R3P9DzvgnutHbbSomXep6ZOEOQ.webp) Image via [BIMI Group](https://bimigroup.org/bimi-infographic/) ## How does BIMI actually work? Setting up BIMI will take a bit more work than simply uploading your brand’s logo and hitting save. Technically speaking, BIMI is a text file that lives on your sending servers and works hand in hand with DKIM, SPF, and DMARC protocols. At a broad level, after you send an email, your recipient’s email provider will look up your BIMI text file and attempt to verify the legitimacy of it. Once verified, your BIMI text file points the email provider to where they can find your logo and they will then attach it to your email. To start, the sender (you) will need to ensure that you are DMARC compliant. DMARC (domain-based Message Authentication, Reporting, & Conformance) is an [email authentication policy and reporting protocol](https://bimigroup.org/faqs-for-senders-esps/) that defends against unauthorized use of domains. Basically, DMARC helps protect your brand by detecting emails that aren’t coming from your domain – preventing spoofing and phishing attempts. The email provider will also run through the Sender Policy Framework (SPF) and DomainKeys Identified Mail (DKIM) protocols to ensure that the sender’s email address was sent from the correct domain. Next, you will need to create the logo that will actually be used. The current recommendation is a [square SVG file](https://bimigroup.org/creating-bimi-svg-logo-files/). The naming convention of this file should be: `https://yourservername.com/logo.svg` Next is an optional but recommended step. To fully embrace BIMI, your brand should acquire a VMC, or a Verified Mark Certificate. This will help validate the true ownership of the logo being used. More on this in the section below. Finally, you will need to create a TXT record for BIMI in your DNS records. This record will contain a URL to your logo for the email provider to grab and display. The full implementation guide from BIMI can be located [here](https://bimigroup.org/implementation-guide/). ## How much does BIMI cost? Along with taking some technical chops to get BIMI fully set up, it’s also not free. Getting the Verified Mark Certificate mentioned above currently [costs \$1,500](https://www.digicert.com/tls-ssl/verified-mark-certificates). On top of this cost, to qualify for the certificate your brand logo also needs to be a registered trademark, which will also come with additional costs and possible legal fees. ## Checking your BIMI record Now that you’ve gone through the work of setting up your BIMI records, it’s time to confirm that everything is working as expected. The BIMI group provides a [BIMI inspector](https://bimigroup.org/bimi-generator/) where you can enter your domain to ensure everything is set. ## Implement BIMI, build trust As you can see, fully embracing BIMI is a lengthy and potentially expensive process that may test your patience. However, leaning into this new email standard positions your brand to gain your reader’s trust while limiting the manual labor and upkeep on your end to ensure that your brand’s logo is always at the forefront of their inboxes. As the inbox becomes a more and more competitive landscape with each passing day, anything that you can do to stand out should be done. BIMI just might give your brand the edge it needs to capture those sought after eyeballs. # What is DNS? A Beginner's Guide Have you ever wondered how the internet seems to know precisely where to go when you type a website's name into your browser's address bar? How does your computer know to show you the right page when you type in "[www.loops.so](http://www.loops.so)"? The answer to these questions lies in the Domain Name System or DNS. DNS is an essential part of the Internet's inner workings, serving as the Internet's equivalent of your iPhone’s address book. But what exactly is DNS, and why is it so important? To understand DNS, we first need to understand how the Internet works. Let’s dive in! ## What is DNS? Every device connected to the Internet, including computers, phones, and servers, has a unique IP address. An IP address is a string of numbers separated by periods that identifies each computer using the Internet Protocol to communicate over a network. For example, an IP address might look like this: "192.168.0.1". However, humans generally find it much easier to remember names rather than strings of numbers. This is where the **Domain Name System (DNS)** comes in. The DNS translates the domain names that we humans easily understand and remember into the IP addresses that computers use to identify each other on the network! For instance, when you type in "[www.loops.so](http://www.loops.so)" into your web browser, your computer sends a request to a DNS server asking for the IP address associated with that domain name. The DNS server responds with the IP address, and your computer then sends a request to that IP address to fetch and display the website. The process of converting domain names into IP addresses is known as **DNS resolution**, and it usually takes only milliseconds. DNS servers are strategically located around the world and work together to ensure that these requests are processed quickly and accurately. ## Why is DNS important Now, why is DNS crucial? Firstly, it makes the internet user-friendly. Without DNS, we would have to memorize complex IP addresses for each website we wanted to visit. Secondly, it ensures the smooth operation of the internet. Every time you send an email, browse a website, or use an app, DNS is working behind the scenes to route your request to the right destination. And if that wasn’t enough, DNS also provides a level of security. DNS servers can filter and block access to certain websites that might be harmful or inappropriate, providing an essential layer of protection for internet users. It helps you verify the authenticity of a sender via email as we’ll cover in the next section. ## DNS and email DNS is vital for the functioning of email. ### Sending email When you send an email, your email client needs to know where to send it. Let's say you're sending an email to [someone@gmail.com](mailto:someone@gmail.com). The client doesn't inherently know where "gmail.com" is, just like your web browser wouldn't know where to go if you entered that into the address bar. DNS steps in to resolve "gmail.com" into an IP address that represents the actual server your email needs to reach. ### Routing email to the right location DNS is used for email routing, particularly through a type of DNS record called the MX (Mail Exchanger) record. An MX record is a type of record that specifies a mail server responsible for accepting email messages on behalf of a recipient's domain. A priority value (a number like 10 is typical) is used to prioritize mail delivery if multiple mail servers are available. Without the MX records, your email wouldn't know which server to go to. ### Security Most importantly, DNS plays a critical role in email security. For instance, Sender Policy Framework (SPF) and DomainKeys Identified Mail (DKIM) are two methods used to prevent email spoofing, a technique used in phishing and spam campaigns where the sender masquerades as another by forging the header data. SPF and DKIM utilize DNS to hold text records that a recipient's server can check to verify the sender's identity. In essence, DNS is a cornerstone for email operations and security. It helps your email find its way to the correct server, ensures the recipient's server can accept the mail, and provides mechanisms to verify the sender's identity to combat fraudulent activities. Without DNS, our email system would be far less reliable and secure. ## Send better email with Loops In summary, the Domain Name System, or DNS, is a critical component of both the internet and email as a whole, transforming the web from a complicated network of numeric addresses into an accessible and secure environment for users worldwide. It's the silent hero that allows us to navigate the digital world with ease, translating memorable website names into computer-friendly IP addresses. So, the next time you browse the internet or send that time-sensitive email, take a moment to appreciate the extraordinary system that makes it all possible: **DNS**. # Integrations Loops integrates with thousands of other platforms so you can sync contacts and trigger emails from around the internet. ## Featured integrations Sync customers to your audience and send automated emails. } href="/integrations/supabase" > Send Supabase authentication emails with Loops. ## Manage contacts } href="/integrations/bubble" > Manage contacts and send emails from your Bubble app. } href="/integrations/census" > Add contacts and contact properties from Census. } href="/integrations/framer" > Add a Loops signup form to your Framer site. Sync outgoing mail contacts to HubSpot. } href="/integrations/integrately" > Manage contacts and send emails from over a thousand other platforms. } href="/integrations/make" > Manage contacts and send emails from thousands of other platforms. Sync outgoing mail contacts to Salesforce. } href="/integrations/segment" > Manage contacts and trigger loops from thousands of other platforms. Sync customers to your audience and send automated emails. } href="/integrations/webflow" > Add a Loops signup form to your Webflow site. } href="/integrations/zapier" > Manage contacts and send emails from thousands of other platforms. ## Send email } href="/integrations/auth0" > Send Auth0 authentication emails with Loops. } href="/integrations/bubble" > Manage contacts and send emails from your Bubble app. } href="/integrations/integrately" > Manage contacts and send emails from over a thousand other platforms. } href="/integrations/make" > Manage contacts and send emails from thousands of other platforms. } href="/integrations/segment" > Manage contacts and trigger loops from thousands of other platforms. Sync customers to your audience and send automated emails. } href="/integrations/supabase" > Send Supabase authentication emails with Loops. } href="/integrations/zapier" > Manage contacts and send emails from thousands of other platforms. ## Create templates Import custom MJML email templates from Emailify. Import custom MJML email templates from Email Love. # Auth0 Send Auth0 authentication emails with Loops. Set up an SMTP connection to send all of your Auth0 emails with Loops. There are two big benefits to using Loops for sending your Auth0 emails: You can use [Loops' design editor](/creating-emails/editor) to create (and then easily edit) beautiful transactional emails instead of having to code them with HTML. You get full visibility on which emails are being sent, when, and to whom in your Loops account. Auth0 doesn't offer this view. ## Set up Loops SMTP in Auth0 Go to **Branding -> Email Provider** in your Auth0 dashboard. Scroll down and click on **SMTP Provider**. In the SMTP Provider Settings section below, enter a value into the "From" field. This value will *always be overwritten by the values set in your Loops templates* from the next step, so it can be anything. In the **SMTP Provider Settings** section enter the following data: | Field | Value | | ----------- | ------------------------------------------------------------------------------------------ | | Host | `smtp.loops.so` | | Port number | `587` | | Username | `loops` | | Password | An API key copied from your [API settings](http://app.loops.so/settings?page=api) in Loops | ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/auth0-smtp-settings.png) The **Send Test Email** button here will not work due to how the Loops SMTP system works. You can test your connection in a later step. ## Create Transactional emails in Loops Next, create new transactional emails for the emails you are sending from Auth0. Go to **Branding -> Email Templates** to view the full list. [Read our guide for creating transactional emails](/transactional/guide#compose-your-email) In Loops, go to the [Transactional page](https://app.loops.so/transactional) and click **New**. Alternatively, you can select one of our many ready-made templates from the [Templates page](https://app.loops.so/templates). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/auth0-template.png) You can then use [the Loops editor](/creating-emails/editor) to create nicely-designed templates or make them as simple as you like. You can even [save styles](/creating-emails/styles#saved-styles) so you can easily apply consistent branding to all of your emails. For each Loops template you create, you need to [add data variables](/creating-emails/personalizing-emails#add-dynamic-content-to-emails), which allow data from Auth0 to be inserted into each email. You can check the list of [Common variables](https://auth0.com/docs/customize/email/email-templates#common-variables) supported in each email from the Auth0 documentation. Once you're done creating the email and adding the data variables, click **Next**. On the next page, click the **Show payload** button to view the API payload for your template. You will need this for the next step. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/auth0-payload.png) Make sure to also publish your email! It won't send unless it's published. ## Configure email templates in Auth0 The final step is to make sure your emails in Auth0 are configured to send the correct data to Loops. Make sure you set up at least the **Verification Email (using Link)** or **Verification Email (using code)** templates in Auth0. Enable other emails based on your user flows. Loops SMTP integrations work a bit differently than most. Instead of sending a text or HTML email body, you set them up to send API-like data. In Auth0, go to **Branding -> Email Templates**, then edit each template to contain the payload as shown in the previous step (you can click the clipboard icon in Loops to copy the full payload). Make sure that each template you want to use in Auth0 has the **Status** field enabled. Once pasted into the **Message** body, you need to add the Auth0 message variables into the payload. You can do this using double curly brackets like `{{ url }}`. Here is an example "Verification Email (using Link)" email template. This payload was copied from the template's Publish page in Loops, then the `{{ user.email }}` and `{{ url }}` Auth0 variables were added. ```json { "transactionalId": "clvmzp39u035tl50pw7wrl0ri", "email": "{{ user.email }}", "dataVariables": { "productName": "{{ application.name }}", "url": "{{ url }}" } } ``` Here's how it looks in the Auth0 editor: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/auth0-email.png) To test that everything works, click the **Try** button beneath the editor. Insert your email address in the modal that appears, then click **Try** to send the email. You will also be able to see activity for your email sends in **Monitoring -> Logs**. The best way to view your Auth0 email history is in Loops. Go to your [Transactional](https://app.loops.so/transactional) page then click on one of your emails. Click on **Metrics** in the left menu to view a page containing a table showing all sends and some statistics. ## Important notes * The subject in Auth0 templates is always overwritten by the subject added to the corresponding template in Loops. * The sender email configured in your Auth0 SMTP settings is always overwritten by the "From" address added to your templates in Loops. * Any enabled Auth0 template not set up with the correct API-like payload will fail to send. # Auto BCC Import emails and contacts to your CRM by adding a BCC address to your marketing emails. Our Auto BCC feature works with campaigns and loops, but is not offered on transactional emails. By adding a BCC email address in your settings, your emails will be BCCd to this address. Do not use a personal email address, only one generated by your CRM. Auto BCC is a feature that imports emails and contacts into your CRM platform. This works by adding a BCC email address to all outgoing campaigns and loops emails sent from Loops. You can add a BCC email address in your [Tracking settings page](https://app.loops.so/settings?page=tracking). Be aware that BCCing might not be enough by itself. Some platforms may require you to add the sender email address to an allowlist or for the sender to be a user on your account. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/auto-bcc.png) ## Syncing with Salesforce You can record all outgoing email and add contacts into Salesforce using a pre-generated Email to Salesforce address. [How to find your Salesforce email address](https://support.cloudhq.net/where-do-i-find-my-bcc-address-in-salesforce/)\ [Read how Email to Salesforce works](https://help.salesforce.com/s/articleView?id=sf.email_my_email_2_sfdc.htm\&type=5) ## Syncing with HubSpot You can log all outgoing emails and add new contacts to HubSpot using your pre-generated BCC address. [How to find your HubSpot email address](https://knowledge.hubspot.com/settings/log-email-in-your-crm-with-the-bcc-or-forwarding-address) Make sure to read the "Requirements to log emails to the CRM using the BCC address" section on the page above to make sure the sync works correctly. ## Syncing with Attio With Attio, your Attio log in email has to match the sending email in Loops. This is a limitation within Attio. For example, if your sending domain is `mail.mydomain.com` and the ["From" address](/sending-first-email#sending-settings) for your email is `bob@mail.mydomain.com`, your Attio account email has to be `bob@mail.mydomain.com` for this integration to work. ## Syncing with other CRMs Most CRMs have a feature that lets you import email and contacts. Look for a BCC email address and add it to your Loops settings. # Bubble Integrate the Loops API into your Bubble app with our plugin. Our Bubble plugin lets you: * Add, find, update and delete contacts * Send events to trigger loops * Send transactional email Our Bubble plugin is unfortunately limited to what it can do because of how Bubble works. If you want more flexibility—for example, syncing more contact data to Loops—check out our [tutorial for using Bubble's API Connector](/guides/bubble-api-connector). ## Install the plugin Go to the [Loops plugin](https://bubble.io/plugin/loops-1704117562175x705056703666192400) and select your app from the "Install in an application..." dropdown. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-install.png) Alternatively, go to the Plugins page in your Bubble admin, click "Add plugins" in the top right, and search for "Loops". ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-search.png) ## Set up the plugin To use the plugin you will need to add a Loops API key into Bubble. First, go to your [Loops API settings page](https://app.loops.so/settings?page=api) and copy an API key. You may need to create a key first. Then go to the the Plugins page in Bubble, select the Loops plugin and paste your API key into the "API key" field, prepended with the word "Bearer" and a space. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-api-key.png) ## Using the plugin actions To use the plugin actions in a workflow, select "Plugins" in the menu and you will see the options show up (prefaced with "Loops - "). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-action.png) Here's a simple example of using the Bubble plugin to add all new users to your Loops audience. After your sign up action, add a new "Loops - Create a contact" action. In the form, add your user email into the "Email" field and user ID into the "User ID" field. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-form.png) And that's it! ## Actions The plugin contains actions that replicate what's possible with the [Loops API](/api-reference). ### Create a contact The **Create a contact** action will create new contacts in Loops using the email address and user ID that you send from Bubble. If you want to "create or update" users, use the [Update a contact](#update-a-contact) action instead. You need to provide both email and user ID values, otherwise the underlying API request from Bubble will fail. The **Create a contact with name** action lets you also send a first and last name to Loops. [API documentation](/api-reference/create-contact) ### Find a contact The **Find contact by email** action will find a Loops contact based on the provided email address. This can be used to see if one of your user already exists in your Loops audience. Similarly the **Find contact by user ID** action will find a contact by their ID. [API documentation](/api-reference/find-contact) ### Update a contact This action will update the email address of a Loops contact who has the provided user ID. This action can also be used to "update or create" contacts. If a contact doesn't already exist with the provided email or user ID, a contact will be created. [API documentation](/api-reference/update-contact) ### Delete a contact The actions **Delete a contact by email** and **Delete a contact bu user ID** will delete a contact from Loops. This is useful for when a user closes their account in your application and therefore you no longer want to email them from Loops. [API documentation](/api-reference/delete-contact) ### Send an event The send event actions can be used to [trigger Loops](/loop-builder) from your application. For example, you may have a welcome loop which sends an email drip campaign to new users. For the Send an event action, you need to provide an "Event name" value and the user's ID or email address. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-form.png) [API documentation](/api-reference/send-event) ### Send transactional email Transactional emails are one-time emails like password resets sent by apps to users based on an action. To send transactional emails, you will first need to [create the email in Loops](/transactional/guide#compose-your-email). Once you have written the email and added data variables, you can click **Next** to see the example payload for the API. Note the names of the `dataVariables` (which you added in the email) because you need these in Bubble. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/bubble-transactional.png) Once you create the action in Bubble, you will see the three fields in the form. Here is an example API payload for the Loops API: ```json { "transactionalId": "clomzp89u635xl30px7wrl0ri", "email": "name@email.com", "dataVariables": { "lastLoggedIn": "20240214T10:01:29Z", "plan": "Pro", } } ``` To get the same effect in Bubble, we need to enter the following into the "Data variables" field: `"lastLoggedIn": "20240214T10:01:29Z", "plan": "Pro"` To add user data into this field, place your cursor and select **Insert dynamic data** (see image above). [API documentation](/api-reference/send-transactional-email) # Carrd Enable sign ups to Loops using a native Carrd form. ### Add a form to your Carrd site ![carrd image](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/carrd.png) 1. Add a form to your site and select the **Custom** option. 2. Select **Send to URL** and paste in your form endpoint from the [Forms page in Loops](https://app.loops.so/forms). ![form endpoint](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/form-endpoint.png) 3. Paste the form endpoint you copied from into the **URL** input in Carrd. 4. Change the **Method** to "AJAX" and the **Format** to "JSON". Our form submission endpoint has rate limiting, so you will see an error in testing if you submit more than once per minute or submit the same email twice. ### Customizing the form In addition to collecting the email address, you can also collect any other contact property you want. 1. Follow Carrd's documentation to [add a new field to your form](https://carrd.co/docs/forms/setting-up-a-custom-form) 2. Determine your preference: * A hidden field that is set to a static value * A value that your user can set 3. Assign the field an "ID" matching the Loops API property name that you want to set. You can check the full list of your available properties from your [API Settings](https://app.loops.so/settings?page=api) page. For example, if you want to set the User Group property, you would add a hidden field and set the ID to `userGroup`. To add subscribers to specific [mailing lists](/contacts/mailing-lists), add a field with an ID `mailingLists`. The **Value** can be a single mailing list ID or if you want to add subscribers to multiple lists, a comma-separated list of IDs. # Census Send user data from your data warehouse to Loops. Our Census integration lets you: * Create and update contacts * Trigger loops when contacts are created or updated Loops is a partnered destination for Census. We support syncing data via `userId` or `email`. New contacts cannot be created if no email is provided. Within the Census app, go to **Destinations** and click **+ New Destination**. Select the **Loops** destination. ![Add Loops destination](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/census-loops-destination.png) You will be prompted to enter your Loops API Token, which you can find on your [API settings page](https://app.loops.so/settings?page=api), and to decide if you want to trigger loops when contacts are created or updated. ![Create a destination](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/census-destination-modal.png) Click **Connect** and test out your destination. Loops will now be available as a destination in Census. ![Use the Loops destination](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/census-destination-loops.png) When choosing a sync behavior, you can choose either **Update or Create** or **Mirror**. ![Choose sync behavior](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/census-sync-behavior.png) # Clerk Sync contacts and send emails triggered by events in Clerk. Our Clerk integration lets you: * Create and update contacts * Send events to trigger loops Our Clerk integration is built on top of our [Incoming webhooks](/integrations/incoming-webhooks) feature. This system lets you send webhooks from supported platforms directly to Loops so you can easily sync users and customers as well as send automated emails. [Please read our guide about incoming webhooks](/integrations/incoming-webhooks) With Clerk, you can keep your user data synced to Loops so you can easily send emails to your userbase. ## Synced data We sync the following Clerk data to your Loops contacts for every incoming event: * User ID * Email address * First name (optional) * Last name (optional) We use the IDs of Clerk users to match contacts in your Loops audience. If the user ID is not found in Loops, we will create a new contact. ## Supported events We accept the following events: * `user.created` * `user.updated` [Clerk webhook docs](https://clerk.com/docs/integrations/webhooks/overview) If you send other events, they will be ignored. If you would like to see more events supported, please let us know by sending an email to [help@loops.so](mailto:help@loops.so). Please keep in mind only events that contain an email address are able to be processed. ## Create a webhook endpoint in Loops [Follow the instructions here](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops) to create a new webhook endpoint, which will allow you to send webhook events directly to Loops. ![Endpoint form](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-endpoint-clerk.png) ## Create a webhook in Clerk Next, you need to set up webhooks in Clerk. Go to **Webhooks** and click **+ Add Endpoint**. Paste in the endpoint URL from Loops, then select the event(s) you want to send (see our [supported events](#supported-events) above). ![Adding a webhook in Clerk](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/clerk-webhook.png) Click **Create** to finish. The last step is to copy the signing secret into Loops. On the webhook page in Clerk, click the eye icon on the right to show the secret in the page. Copy the secret and paste it into the **Signing Secret** field in Loops. ![Reveal Clerk secret](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/clerk-secret.png) Now you're all set up. ## Testing Clerk webhooks Clerk offers a nice way to test webhooks. Click through to the webhook you created and then the **Testing** tab. Select `user.created` or `user.updated` from the **Send event** dropdown, tweak the example data that's shown, then click **Send Example**. This will send real data to your Loops account. You can also test your webhook by creating and editing users in the **Users** page in Clerk, or by signing up in your application. You can see all sent webhooks by going to **Webhooks**, clicking on the webhook and scrolling down to the **Message Attempts** section. On Loops' end, you will see new contacts appear in your [Audience](https://app.loops.so/audience) page, and triggered events in the [Events](https://app.loops.so/settings?page=events) page. ## Examples Here are some examples of how you can send data from Stripe to Loops to sync contacts and trigger useful emails to your customers. ### Syncing users to Loops Create or update contacts in your Loops audience when a user is created or updated in Clerk. 1. Create a new Clerk webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 2. In Clerk, create a new webhook ([instructions above](#create-a-webhook-in-clerk)) for the `user.created` and `user.updated` events and paste in your endpoint's URL. 3. In Loops, make sure `user.created` and `user.updated` are checked on the Clerk settings page. ### Send an email to all new Clerk users Send an email from Loops when a new user is created in Clerk. 1. Create a new loop in Loops using our **Onboarding drip** template. 2. For the loop trigger, select **Event received** and enter `newClerkUser` (or something similar). 3. Set up your Clerk webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 4. In Clerk, create a new webhook ([instructions above](#create-a-webhook-in-clerk)) for the `user.created` event and paste in your endpoint's URL. 5. In Loops, make sure `user.created` is checked, and select the event name you chose in Step 2 from the **Trigger an event** field. ### Enter all new Clerk users into an onboarding email sequence Send an email from Loops when a new customer is created in Clerk. 1. Complete Steps 1–5 from "Send an email to all new Clerk users" above. 2. Add more emails and timers into the loop to complete your email sequence. # Framer Enable signups from your Framer site using an in-built or custom Loops component. Collect new subscribers from your Framer site. There are a few ways to set this up. ## Framer Form component Use [Framer Forms](https://www.framer.com/features/forms/) to easily create a form on your site. ### Insert the Form Component From **Insert -> Forms** drag the **Form builder** component into your page. This will add an example form. ![Framer Form component](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-form-component.png) ### Edit the form fields Edit the fields in the form to match the data you want to collect from new subscribers. An email field is required. Make sure to toggle the **Required** option to **Yes** for your email field. You need to edit the **Name** value of each field to match the [contact properties' "API name"](/contacts/properties) in Loops. For example, the **Name** value must be "email" for the email address field. ![Email form field](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-email-field.png) You can add hidden fields to populate data like `mailingLists`, `userGroup` and `source` in Loops yet ensure they don't show up in the form. You may have to click the `+` button to add the **Value** and **Hidden** options. To add subscribers to specific [mailing lists](/contacts/mailing-lists), add a field with **Name** `mailingLists`. The **Value** can be a single mailing list ID or a comma-separated list of IDs. ![Hidden form field](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-form-hidden.png) ### Configure the form The final step is to set the endpoint to your Loops form URL. 1. Go to the [Forms page](https://app.loops.so/forms) in your Loops account. 2. Click on the **Settings** tab. 3. Copy the **Form Endpoint**. ![Form endpoint](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/form-endpoint.png) 4. Back in Framer, select your form. In the **Send To** option, select "Webhook". Then paste the URL from Loops into the **API** field. ![Framer form webhook](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-form-webhook.png) Now your form is all set up and can start receiving new subscribers. ### Set up a confirmation message By default a "Thank you" message is shown inside the form button when the form is submitted successfully. You can opt to use a redirect instead. You can add a confirmation message on another web page and use the **Redirect** option in the form settings. ## Framer Loops component Framer has a built-in Loops option for creating simple signup forms with an email address field. ### Insert the Loops Component From **Insert -> Forms** drag the **Loops** component into your page. This will add an example form. ![Framer Loops component](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-loops-component.png) ### Configure the form Next, you need to add your Loops form ID to the **ID** field. 1. Go to the [Forms page](https://app.loops.so/forms) in your Loops account. 2. Click on the **Settings** tab. 3. Copy the **Form ID**. ![Form ID](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/form-id.png) 4. Paste this ID into the **ID** field in the Framer component. ![Loops form ID](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-property-panel.png) 5. Framer offers a **User Group** option in the component, which will populate the contact's `userGroup` value in Loops. ### Set up a confirmation message Make sure to also set up a confirmation message by clicking on the **Success** dropdown. You can choose to show a message in an overlay or redirect the user to another web page. ## Advanced integration This option adds a custom component into your Framer site [using form code](/forms/custom-form) generated by Loops ### Generate the form code 1. Go to the [Forms page](https://app.loops.so/forms) in your Loops account. 2. Click on the **Settings** tab. 3. Select “JSX” from the **Generate Form Code** dropdown (1), then copy the code snippet (2). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/jsx-form.png) ### Embed the component in Framer 1. Create a new component. Toggle over to **Assets** in the Framer side panel then click the `+` button. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-1.png) 2. Give your New Component a title. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-2.png) 3. Finally, paste the code copied in from Loops into the code editor. You should see the Preview on the right fill in with a preview of your component. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/framer-3.png) 4. Drag and drop your new asset anywhere on your page to use it :) Our form submission endpoint has rate limiting, so you will see an error in testing if you submit more than once per minute or submit the same email twice. # Incoming webhooks Send data to Loops from supported platforms using webhooks. Incoming webhooks allow you to: * Create and update contacts * Send events to trigger loops This feature lets external platforms send webhook events directly to Loops, making it straightforward to create or update contacts in Loops automatically when changes happen in other platforms. You can also trigger events when webhooks arrive in Loops so you can send automated email after something happens in your other accounts. ## How it works First, you create webhook endpoints in your Loops account. These allow other platforms to send data automatically and directly to Loops. You then create webhooks in the external platforms, which send event data to your Loops endpoint URLs. Note: we only process webhook events listed below for each provider (and which contain an email address). We return helpful messages in responses if there is an issue processing a webhook event. Check the webhook logs in your external platforms. ### Syncing contacts When data arrives in Loops, we grab the email address to create and update contacts in your Loops audience. For each endpoint you create you can choose to assign a user group value to each new contact, allowing you to create segments from webhook-created contacts. Any new contact created via a webhook will have a source like "Stripe webhook" so you know where it originated from. ### Sending emails We also support triggering events for each incoming webhook event. This can be useful to automatically send emails when a webhook event arrives in Loops (e.g. a successful payment or a new customer). ## Create webhook endpoints in Loops To start sending webhook events to Loops, go to your chosen [integration's settings page](https://app.loops.so/settings) in Loops. A webhook endpoint will be created for you. Copy the endpoint URL and paste it into your external platform. ![Endpoint form](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-endpoint.png) You may need to copy-paste signing secrets between the platform and Loops for extra security (we will prompt you when this is necessary and give you the steps to do it). In the endpoint form, you can select the events you want Loops to process, assign a user group, and send a Loops event (which can [trigger email sending in loops](/loop-builder#triggers)). ## Supported platforms * [Clerk](/integrations/clerk) * [Stripe](/integrations/stripe) * More coming soon! # Integrately Connect Loops to over a thousand apps. Our Integrately integration lets you: * Create, find, update and delete contacts * Send events to trigger loops * Send transactional email ## Create an automation In Integrately, create an automation by searching for and selecting the two platforms you want to connect. ![Add an automation in Integrately](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/integrately-add.png) You'll need to connect to or sign in to each platform. When it comes to connecting to Loops, you need to create or copy an API key from [your API Settings page](https://app.loops.so/settings?page=api). Paste your API key into Integrately. ![Add Loops API key](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/integrately-api.png) Now you are connected to Loops and can continue setting up the automation. ## Manage contacts To sync contacts, choose either the "Create contact" or "Create or update contact" action. You can map data between the two platforms in the automation form. In this example, the "Email" field contains the data from the "Email" column in Google Sheets. ![Create a contact automation](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/integrately-contact.png) ## Send event To send events from Integrately (i.e. to trigger [sending a loop](/loop-builder)), choose the "Send event" action and then map an "Email" and "Event name" in the available fields. ![Send event automation](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/integrately-event.png) ## Send transactional email To send transactional email from Integrately, select the "Send transactional email" action. Map the email address you want to send to. Then add the ID of your transactional email from Loops (found on the "Publish" page of the transactional email editor). Finally, add your email's data variables. You can easily map dynamic variables from the source app or add static text in the form. ![Send transactional email automation](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/integrately-transactional.png) # Make Utilize our official Make integration to manage contacts and send email. Our Make integration lets you: * Create, find, update and delete contacts * Send events to trigger loops * Send transactional email ## Create a connection Before you get started, head over to the API page and [create a new API key](https://app.loops.so/settings?page=api) in Loops. You'll need to copy the API key and paste it into Make. To get started create a new scenario and select the "Loops" module. ![Adding loops in Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/make-loops.png) Then select the "Create Connection" button and paste in your API key in the following screen. ![Create a connection with Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/make-add-create-connection.png) After pasting your API key, click the "Save" button. ![Connect Loops API with Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/make-paste-api-key.png) ## Manage contacts and trigger loops There are a number of actions available to manage contacts and send emails. ![Adding a module with Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/make-add-module.png) ## Send transactional email The Make integration does not have a specific action for sending transactional email. Instead you can use the "Make an API call" action. ![Set up transactional sending in Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/maketransactional.png) Make sure that the URL is `/v1/transactional` rather than the full endpoint URL. You can copy an email's example payload from its Publish page in Loops by clicking the **Show payload** button. Paste it into the "Body" field in Make and select the data you want to send to Loops. View the API reference for sending transactional email. # Segment Utilize our official Segment integration to manage contacts and send email. Our Segment integration lets you: * Create and update contacts * Send events to trigger loops Visit our [Segment integration](https://segment.com/catalog/integrations/actions-loops/) to learn more and follow the steps below. ## Configuring the destination After opening the link above, click **Configure Loops (Actions)**. ![Adding Loops in Segment](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-configure-loops.png) Select your data source, give the destination a name, and click **Create destination**. Next, you’ll need an API key. You can generate a new one for Segment on the [Loops API Settings page](https://app.loops.so/settings?page=api). Enter the API key on your Segment destination settings: ![Add an API key](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-api-key.png) Enable the destination and click **Save Changes**. Note that no data will start flowing until you create specific mappings for Loops. ## Mappings Segment action destinations require that you map specific fields from your source to your destination (in this case Loops). You can set this up by clicking into the **Mappings** tab and adding a new mapping. Currently we support updating contacts in Loops and sending events into Loops. ### Create or update contact First, select which events to map. Typically for contact creation and updates, the most useful event to map will be "Identify". ![Map contact properties](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-select-events.png) The next step is to load a sample event to help you map fields appropriately. Contact properties are found in the `traits` object. For this example, we’ll be using this test event: ```json { "messageId": "segment-test-message-gt3ds8", "timestamp": "2023-05-24T17:58:30.352Z", "type": "identify", "email": "adam@loops.so", "traits": { "firstName": "Adam", "favoriteColor": "blue", "favoriteNumber": 42 }, "userId": "test-user-a5h7xb" } ``` When sending a contact's details to Loops, you must include an Email and a User ID. We've provided some defaults for the mappings, which will show up in the third step, but it is important you review them: ![Default contact mappings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-default-mapping.png) #### Custom contact properties Segment does not provide an interface to provide the names and types for [custom contact properties](/contacts/properties#custom-contact-properties) that you might be using with Loops. In our example, those fields are `favoriteColor` and `favoriteNumber`. You can pass contact properties as a dictionary in the **Custom Contact Attributes** field. Ensure that the keys and values you provide match the schema you’ve created in your [Contact properties settings](https://app.loops.so/settings?page=api). Click **Edit Object** to specify the custom fields you want to send to Loops individually: ![Mapping custom contact properties](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-edit-defaults.png) #### Subscribed In most cases, you want to leave the **Subscribed** field as the default (deselected). Setting this to `true` will re-subscribe contacts who had previously unsubscribed, and setting it to `false` will unsubscribe contacts from receiving email. Leaving it deselected will default new users as subscribed to email and not update the email preference for existing contacts. ![Subscribed field](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-subscribed-field.png) #### Mailing lists To subscribe contacts to [mailing lists](/contacts/mailing-lists) there are two options. The first method is to manually edit the **Mailing Lists** data. This will allow you to enter list ID values that are the same for every contact. Click the **Edit Object** option, then the **+ Add Mapping Field** button. In the **Select event variable** field enter "true" or "false" (to subscribe or unsubscribe) and in the **Enter key name** field enter your list ID(s). You can add multiple lists by clicking on the **+ Add Mapping Field** button again. Make sure the data shown below the fields has the same structure as in the image below. ![Adding list IDs in the mapping UI](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-mapping-lists.png) Another option is to add mailing list data to your Identify call, which will let you dd more dynamic data for each contact. You can test this by adding a `mailingLists` object to `traits` in your test event with list IDs as keys and `true` (to subscribe) or `false` (to unsubscribe) as values. ```json { "messageId": "segment-test-message-gt3ds8", "timestamp": "2023-05-24T17:58:30.352Z", "type": "identify", "email": "adam@loops.so", "traits": { "firstName": "Adam", "favoriteColor": "blue", "favoriteNumber": 42, "mailingLists": { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": true, } }, "userId": "test-user-a5h7xb" } ``` Then you need to map the data. Click the **Select Object** option, then search for `traits.mailingLists` from the Event Variables options. ![Selecting the mailing list data from traits](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-mailinglists-object.png) #### Testing After the mappings are configured you can preview the data that will be sent. ![Event data preview](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-test-preview.png) You can also send a test event to Loops to verify everything is working (this will send actual data to your Loops account). If it works, you will get a successful response: ![Successful response](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-test-event.png) Check your [Loops audience page](https://app.loops.so/audience) to ensure the contact was created or updated as intended. Sending another test event with the same User ID or Email will update the existing contact instead of creating a new contact. ### Send event You can send events to trigger [loops](/loop-builder). First, select which events to map. Typically for sending an event, the most useful event to map will be "Track". It’s suggested you filter the events down to only ones that you plan on using within Loops using the **Event Name** filter: ![Sent event in Segment](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-send-event.png) Then after defining or loading a sample event in step 2, configure the mapping. If you want to add [event properties](/events/properties), you can pass them as a dictionary to the **Event Properties** field. Click **Edit Object** to map the properties to your event property names. Add property names in the "Enter key name" fields and either enter or select values in the "Select event variable" fields. ![Configure event mappings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-configure-mappings.png) If a contact already exists in your Loops audience, the **Contact Email** field is optional. Loops will trigger based on the **User ID**. If the contact does not exist in your Loops audience (perhaps you are not using an identify call), you will need to provide an email address otherwise Loops will not be able to create the contact for the event. #### Testing After configuring the mapping, you can send a test event at the bottom of the page (this will send actual data to your Loops account). You can preview the data that will be sent. ![Send event preview](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-event-preview.png) The response should indicate success: ![Success message](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/segment-test-mappings.png) You can verify the event was received on your [Events page](https://app.loops.so/settings?page=events) in Loops. ## Sending data to Segment The following examples show how you can send data from your application to Segment for the two Loops actions. The examples use Segment's [Analytics.js library](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/), but the premise is similar for other libraries. ### Create or update contact For this action you should send a `identify()` event. Add contact properties (including any [custom properties](/contacts/properties#custom-contact-properties)) in the traits object: ```javascript analytics.identify("97980cfea0067", { email: "peter@example.com", plan: "premium", logins: 5 }); ``` If you're mapping a `mailingLists` object from `traits` ([read more above](#mailing-lists)) add it like this: ```javascript analytics.identify("97980cfea0067", { email: "peter@example.com", plan: "premium", logins: 5, mailingLists: { "cm06f5v0e45nf0ml5754o9cix": true, "cm16k73gq014h0mmj5b6jdi9r": true, } }); ``` ### Send event For this action send a `track()` event. Data sent in the properties object will be sent as [event properties](/events/properties) to Loops. Make sure that you have added these event properties in your [Events Settings](https://app.loops.so/settings?page=events). ```javascript analytics.track("User Registered", { plan: "Pro Annual", accountType: "Facebook" }); ``` You have the option to update contacts when sending events by adding contact properties in a `context` object. ```javascript analytics.track("User Registered", { plan: "Pro Annual", accountType: "Facebook", }, { traits: { firstName: "Phil" } }); ``` # Stripe Sync contacts and send emails triggered by events in Stripe. Our Stripe integration lets you: * Create and update contacts * Send events to trigger loops Our Stripe integration is built on top of our [Incoming webhooks](/integrations/incoming-webhooks) feature. This system lets you send webhooks from supported platforms directly to Loops so you can easily sync users and customers as well as send automated emails. [Please read our guide about incoming webhooks](/integrations/incoming-webhooks) With Stripe, you can sync user data to Loops for customer and invoice-related events. ## Synced data We sync the following Stripe data to your Loops contacts for every incoming event: * Email address * First and last name (optional) We use the email addresses of Stripe customers to match contacts in your Loops audience. If the email address is not found in Loops, we will create a new contact. ## Supported events We accept the following events: * `customer.created` * `customer.updated` * `invoice.paid` * `invoice.payment_failed` * `invoice.upcoming` [Stripe webhook docs](https://docs.stripe.com/webhooks) If you send other events, they will be ignored. If you would like to see more events supported, please let us know by sending an email to [help@loops.so](mailto:help@loops.so). Please keep in mind only events that contain an email address are able to be processed. ## Create a webhook endpoint in Loops [Follow the instructions here](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops) to create a new webhook endpoint, which will allow you to send webhook events directly to Loops. ![Endpoint form](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-endpoint.png) ## Create a webhook in Stripe Next, you need to set up webhooks in Stripe. Go to **Developers** and then **[Webhooks](https://dashboard.stripe.com/webhooks)**. Click **+ Add endpoint**. Paste in the endpoint URL from Loops, then select the event(s) you want to send (see our [supported events](#supported-events) above). ![Adding a webhook in Stripe](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/stripe-webhook.png) Click **Add endpoint** to finish. The last step is to copy the signing secret into Loops. On the webhook page in Stripe, click **Reveal** to show the secret in the page. Copy the secret and paste it into the **Signing Secret** field in Loops. ![Reveal Stripe secret](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/stripe-secret.png) Now you're all set up. ## Testing Stripe webhooks You can test a `customer.*` webhook by creating a new customer in the [Customers](https://dashboard.stripe.com/customers) page in Stripe. You can also use the Stripe CLI tool to mimic events, by using the [`trigger` command](https://docs.stripe.com/cli/trigger). You can see all sent webhooks by going to **Developers -> Webhooks** and then clicking on an endpoint. On Loops' end, You will see new contacts appear in your [Audience](https://app.loops.so/audience) page, and triggered events in the [Events](https://app.loops.so/settings?page=events) page. ## Examples Here are some examples of how you can send data from Stripe to Loops to sync contacts and trigger useful emails to your customers. ### Syncing customers to Loops Create or update contacts in your Loops audience when a customer is created or updated in Stripe. 1. Create a new Stripe webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 2. In Stripe, create a new webhook ([instructions above](#create-a-webhook-in-stripe)) for the `customer.created` and `customer.updated` events and paste in your endpoint's URL. 3. In Loops, make sure `customer.created` and `customer.updated` are checked on the Stripe settings page. ### Send an email to all new Stripe customers Send an email from Loops when a new customer is created in Stripe. 1. Create a new loop in Loops using our **Stripe - New Customer** template. 2. For the loop trigger, select **Event received** and enter `newStripeCustomer` (or something similar). 3. Set up your Stripe webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 4. In Stripe, create a new webhook ([instructions above](#create-a-webhook-in-stripe)) for the `customer.created` event and paste in your endpoint's URL. 5. In Loops, make sure `customer.created` is checked, and select the event name you chose in Step 2 from the **Trigger an event** field. ### Successful payment email Send an email from Loops when an invoice is paid in Stripe. 1. Create a new loop in Loops using our **Stripe - Payment Successful** template. 2. For the loop trigger, select **Event received** and enter `successfulPayment` (or something similar). 3. Set up your Stripe webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 4. In Stripe, create a new webhook ([instructions above](#create-a-webhook-in-stripe)) for the `invoice.paid` event and paste in your endpoint's URL. 5. In Loops, make sure `invoice.paid` is checked, and select the event name you chose in Step 2 from the **Trigger an event** field. ### Failed payment email Send an email from Loops when an invoice payment fails in Stripe. 1. Create a new loop in Loops using our **Stripe - Payment Failed** template. 2. For the loop trigger, select **Event received** and enter `failedPayment` (or something similar). 3. Set up your Stripe webhook endpoint in Loops ([instructions](/integrations/incoming-webhooks#create-webhook-endpoints-in-loops)). 4. In Stripe, create a new webhook ([instructions above](#create-a-webhook-in-stripe)) for the `invoice.payment_failed` event and paste in your endpoint's URL. 5. In Loops, make sure `invoice.payment_failed` is checked, and select the event name you chose in Step 2 from the **Trigger an event** field. # Supabase Configure your Supabase account to send authentication emails with Loops. Set up an SMTP connection to send all of your Supabase emails with Loops. There are two big benefits to using Loops for sending your Supabase emails: You can use [Loops' design editor](/creating-emails/editor) to create (and then easily edit) beautiful transactional emails instead of having to code them with HTML. You get full visibility on which emails are being sent, when, and to whom in your Loops account. Supabase doesn't offer this view. ## Set up Loops SMTP in Supabase Go to your Authentication settings in Supabase (**Project Settings -> Authentication**) to tell Supabase to send emails using Loops' SMTP service. Scroll down and toggle the **Enable Custom SMTP** option on. In the Sender details section, you will need to enter some values into the "Sender email" and "Sender name" fields. However, *these values will always be overwritten by the values set in your Loops templates* from the next step. In the **SMTP Provider Settings** section enter the following data: | Field | Value | | ----------- | ------------------------------------------------------------------------------------------ | | Host | `smtp.loops.so` | | Port number | `587` | | Username | `loops` | | Password | An API key copied from your [API settings](http://app.loops.so/settings?page=api) in Loops | ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-smtp-settings.png) Note that for the interval and rate limit settings, you will be bound by Loops' [API rate limit](/api-reference/intro#rate-limiting) of 10 requests per second. One final step is to check that the "Confirm email" toggle is turned on in the Email section in **Authentication -> Providers**. ## Create Transactional emails in Loops Next, create new transactional emails for the emails listed in Supabase (**Authentication -> Email Templates**). You need to create both **Confirm signup** and **Magic Link** emails to be able to properly set up the integration. * Confirm signup (required) * Invite user * Magic Link (required) * Change Email Address * Reset Password Note that if a Supabase user has not previously confirmed their email, they will be sent a **Confirm signup** email when you request a **Magic Link** email. To create new transactional emails, go to the [Transactional page](https://app.loops.so/transactional) in Loops and click **New**. Alternatively, you can select one of our many ready-made templates from the [Templates page](https://app.loops.so/templates). ![Supabase template in the editor](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-template.png) You can then use [the Loops editor](/creating-emails/editor) to create nicely-designed templates or make them as simple as you like. You can even [save styles](/creating-emails/styles#saved-styles) so you can easily apply consistent branding to all of your emails. ![Saved styles](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-editor.png) For each Loops template you create, you need to [add data variables](/creating-emails/personalizing-emails#add-dynamic-content-to-emails), which allow data from Supabase to be inserted into each email. For example, you could add a `confirmationUrl` data variable that you can map to the `{{ .ConfirmationURL }}` value from Supabase. You can also build URLs by including values like `{{ .SiteUrl }}` or add in a confirmation code using `{{ .Token }}`. ![Supabase values](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-values.png) Once you're done creating the email and adding data variables, click **Next**. On the next page, click the **Show payload** button to view the API payload for your template. You will need this for the next step. ![Email payload](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-payload.png) Make sure to also publish your email! It won't send unless it's published. Read our detailed guide for sending transactional emails. ## Configure email templates in Supabase The final step is to make sure your emails in Supabase are configured to send the correct data to Loops. Loops SMTP integrations work a bit differently than most. Instead of sending a text or HTML email body, you set them up to send API-like data. In Supabase, go to **Authentication -> Email Templates**, then edit each template to contain the payload as shown in the previous step (you can click the clipboard icon in Loops to copy the full payload). Once pasted into the Message body, you need to add the Supabase message variables into the payload. Make sure you set up at least the **Confirm signup** and **Magic Link** templates in Supabase, otherwise emails will not be sent.\ Also, any variables added in the **Confirm signup** template need to also be available in **Magic link** email, because Supabase will send a **Confirm signup** email instead of a **Magic Link** email if a user hasn't confirmed their email address. Here is an example **Confirm signup** email template. This payload was copied from the template's Publish page in Loops, then the `{{ .Email }}` and `{{ .ConfirmationURL }}` Supabase variables were added. ```json { "transactionalId": "clvmzp39u035tl50pw7wrl0ri", "email": "{{ .Email }}", "dataVariables": { "confirmationUrl": "{{ .ConfirmationURL }}" } } ``` Here's how it looks in the Supabase editor: ![Supabase editor](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/supabase-email.png) With the integration now all set up, your Supabase authentication emails will be sent via Loops, giving you more visibility on your email sends and the great addition of being able to build beautiful and easy-to-update emails in the Loops editor. To view all sends of your transactional emails, click through to the email from the [Transactional](https://app.loops.so/transactional) page in Loops, where you'll find the Metrics page containing a table showing all sends and some statistics. ## Important notes * You need to add a template in Loops and set up the email in Supabase for at least the **Confirm signup** and **Magic Link** templates. * The subject in Supabase templates is always overwritten by the subject added to the corresponding template in Loops. * The sender name and sender email configured in your Supabase SMTP settings are always overwritten by the sender details added to your templates in Loops. * Any Supabase email not set up with the correct API-like payload will fail to send. # Webflow Enable signups from your site using a native Webflow form. This integration requires a paid Webflow plan to allow embedding custom scripts into your site. To allow sign ups to your audience from your Webflow site, you can utilise a native Webflow form plus some drop-in JavaScript. ## Add a custom form script to your Webflow site If you do not add the custom script in the correct place, the form may not work properly. To submit data to Loops seamlessly from your Webflow site we provide some JavaScript, which can be added to your site. Use this script in your Webflow page. ### Where to add the script * If you have a Loops form on every page of your site, add this code to the "Footer code" section in your Site settings ([read how in the Webflow docs](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags#custom-code-in-site-settings)). * If you have a Loops form on only one page, add this code to the "Before \ tag" section in your Page setting ([read how in the Webflow docs](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags#before-the-\<-body>-tag)). ## Add a form to your page Next you need to create a form in your Webflow page. Use the "Input" and "Button" elements. When you add new fields, make sure the “Name” value in the field's settings panel matches the name of the field in Loops: `email`, `firstName`, etc. You can check the full list of your available properties from your [API Settings](https://app.loops.so/settings?page=api) page. Please make sure these contact properties already exist in your Loops account. You can add new contact properties in [API Settings](https://app.loops.so/settings?page=api), with a [CSV import](/add-users/csv-upload) or [using the API](/api-reference). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/webflow-field.png) ### How to add hidden fields You may want to assign a property to all contacts that submit the form (for example, `source` or `userGroup`). For this add an "Embed" component *inside your form* on the same level as your input and button elements. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/webflow-hidden-field.png) In this Embed element add a hidden text field that passes on the custom value to Loops (make sure the `name` values match the "API Name" values in your [API Settings](https://app.loops.so/settings?page=api)). ```html ``` If you're using Lists, make sure the list is marked as Public in your [Lists settings](https://app.loops.so/settings?page=lists). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/webflow-embed.png) ## Add your Loops form endpoint URL The last step is to make sure your form submits data to Loops. You do this by adding a Loops form endpoint as the form's "Action" value. 1. Go to the [Forms page](https://app.loops.so/forms) in your Loops account. 2. Click on the **Settings** tab. 3. Copy the URL shown in the **Form Endpoint** field. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/form-endpoint.png) 4. In Webflow, click on your Form Block, go to the Settings panel and paste the URL into the **Action** field. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/webflow-action.png) Our form submission endpoint has rate limiting, so you will see an error in testing if you submit more than once per minute or submit the same email twice. # Zapier Connect Loops to thousands of apps to manage contacts and send email. Our Zapier integration lets you: * Create, find and update contacts * Send events to trigger loops * Send transactional email Zapier lets you connect thousands of other platforms to Loops. We have created Zapier Actions for managing contacts, sending events and sending transactional emails. ## Creating a new Zap To create a new Zap—for example, to connect Tally and Loops—you can either type out what you want to create. ![Create a new Zap](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-start.png) Alternatively, click the **+ Create** button. Select Tally as the **Trigger** (using the "New Submission" event) and Loops as the **Action** (selecting "Add Contact" as the event). This would send new Tally submissions directly into Loops! ![A new Zap](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-zapier.png) ### Authentication To be able to use Loops Actions, you need to connect to your Loops account. From the **Account** tab, click **Sign in**. ![Sign in to Loops](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-sign-in-loops.png) Create or copy an API key from your Loops [API settings page](https://app.loops.so/settings?page=api) and paste it into the **API Key** field. ![Add API key into Zapier](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-api-key.png) If you want to remove or edit your Zapier connections to Loops, go to [Apps -> Loops](https://zapier.com/app/connections/loops). You can create connections to multiple Loops accounts from a single Zapier account. ## Add a contact This action adds new contacts to your Loops audience. If the email address already exists, it will return an error. All default [contact properties](/contacts/properties) are available, plus [mailing lists](/contacts/mailing-lists) and any custom contact properties added to your Loops account. Only the **Email** field is required. The **Source** field defaults to “Zapier” but you can update this to whatever value you like. ![Add a contact](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-add-contact.png) ## Update a contact The "Update a contact" action will create a contact if a matching contact does not already exist, making it useful if you don't know in advance if a contact exists in Loops. This action supports the same fields as "Add a contact". Only the **Email** field is required. ![Update a contact](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-update-contact.png) ## Find a contact This action supports searching your contact list for a specific email address. You also have the option to create a new user if one is not found. ![Find a contact](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-find-contact.png) ## Delete a contact This action will delete a contact by email address. ![Delete a contact](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-delete-contact.png) ## Send an event This event will send an event in Loops. You need to specify an **Email** or **User ID** value to identify the contact, plus an **Event Name**. The action also supports [event properties](/events/properties) and [mailing lists](/contacts/mailing-lists). The **Email**/**User ID** and **Event Name** fields are required. ![Send an event](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-send-event.png) ## Send a transactional email This action sends a transactional email and can optionally add a contact to your audience. The **Transactional Id**, **Email** and **Data Variables** fields are required. ![Send a transactional email](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/zapier-transactional.png) # Loop builder Loops let you send emails based on something happening, like a contact property updating, a new contact being created or an external event happening in another platform. ## Getting Started To start building your Loop, select a template or start from scratch. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/getting-started.png) Templates are added often and we're always open to taking suggestions! ## Building a Loop A Loop is an email sequence that can be triggered by different events, and containing emails, delay timers and branches. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/building-loop.png) ## Triggers ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/triggers.png) You have four trigger options: * **Contact added**: Triggered whenever a contact has been added to your audience. Only contacts who have been added via API, Zapier, or Segment will be added based on this Trigger. Contacts uploaded via CSV or added individually to the audience table will not be included. * **Contact updated:** Triggered whenever a contact property changes from one property to another. Can also conditionally trigger only if the previous property matches the inputted value. * **Contact added to list:** Triggered whenever a contact is added to a [mailing list](/contacts/mailing-lists). * **Event received**: Enter any custom event name to trigger an email based on interactions in your app. Common custom events are `signUp`, `canceled` and `activated`. You can read more about triggers [here](/loop-builder/loop-triggers). ## Timer ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/timer.png) A **Timer** adds a fixed time period between two nodes in the loop. For example, you could add a "3 day" timer after a "Contact added" trigger to send an email three days after a signup. You can select the “Immediately” option to bypass the timer or any increment of time to extend the duration of the Loop. You can add multiple timers to your loops, to add delays in different parts of your workflow. ## Audience filters ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/audience-filters.png) By combining an [audience filter](/contacts/filters-segments) with a loop trigger, you can create fine-tuned Loops to target specific contacts. For example, you can check in with contacts that have not signed up as a paying user 3 days after signing up by setting the Custom Event to `signup` the Timer duration to 3 days and the audience filter to `paid` equals `false`. By combining events, timers and the audience filter, you should be able to target contacts at any stage of the lifecycle. ## Metrics Click over to the **Metrics** tab to view simple metrics inline within the builder. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/metrics.png) Click **View full metrics** to view detailed metrics for your Loop. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/view-full-metrics.png) ## Testing Loops You can test if a Loop works as expected by using email addresses with `@example.com` and `@test.com` domains (for example `user1@example.com` and `user2@example.com`). First, add these as contact in your audience, then depending on how your Loops are set up, you can add and update properties, or send events to these email addresses to see how contacts move through your Loops. Emails will not be sent to `@example.com` or `@test.com` email addresses so this is a good method to test emailing contacts without affecting your sending domain’s reputation. # Branching Loops Branching loops allow you to send different emails based on a contact's properties within a single Loop. ![Branches in a loop](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/loop-builder-branching.png) A common use case for crafting a user journey is to send different emails based on whether a contact has completed an action or not. For example, you may want to send a different email to users who are free users versus users who are paid users. You can do this by creating a branching Loop. ## Creating a branching Loop ![Adding a node to a branch](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/loop-builder-branching-add-node.png) To create a branching Loop, you just need to add a new node and select **Branching** as the node type. Two audience filter nodes are automatically created for you and you can edit the audience filter settings for each node to send to different contacts based on their properties. ## Best practices for branching Loops The audience filter nodes created after a branching node can be toggled between two settings: 1. **All following nodes:** The audience filter will apply to all nodes downstream of the Loop. 2. **Next node only:** The audience filter will only appy to the next node in the Loop. A common use case for the "All following nodes" option is creating a global Audience Filter. You can add an Audience Filter node after a Trigger node which will ensure that contacts who do not match the audience filter will not remain in the Loop. This is useful for sending a welcome email to new free contacts and then removing them from the Loop when they become paid contacts. # Triggering Loops Learn how to trigger a Loop to start sending emails. ## Loop triggers A Loop trigger is an event, contact update or contact addition that starts a Loop. For example, if you create a Loop that sends a welcome email to new contacts, the trigger would be when a new contact is added to your audience. ## Different types of Loop triggers There are currently four types of triggers that you can use to start a Loop: **Contact added**, **Contact updated**, **Contact added to list** and **Event received**. ### Contact added The **Contact added** trigger will start a Loop when a contact is added to your audience. This trigger is useful for sending welcome emails to new contacts or sending a series of onboarding emails to new customers. This trigger works for contacts added via an integration, a form, or an API call. Contacts uploaded via CSV or added individually to the audience table will not be included. As long as the contact is added to your audience with an automatic method the Loop will start. This trigger requires no additional setup. Once you create a Loop with this trigger, you can start adding contacts to your audience and contacts will enter the Loop. ### Contact updated The **Contact updated** trigger will start a Loop when a contact is updated in your audience. This trigger is useful for sending emails to contacts based on their actions or behavior. For example, you can send a series of emails to contacts who change their subscription plan from free to paid or from paid to canceled. You can also set the trigger to only start the Loop when a specific field is updated from a specific value to another specific value. For example, you can send a series of emails to contacts who change their subscription plan from free to paid but exclude contacts who have updated their subscription plan from paid to canceled. ### Contact added to list The **Contact added to list** trigger will start a Loop when a contact is added to a [mailing list](/contacts/mailing-lists). It triggers every time a contact is added to a list (so if the contact is removed from a list and then re-added, it will trigger again). When using this trigger, make sure to select the mailing list from the menu icon top right of the editor window. ### Event received The **Event received** trigger will start a Loop when a contact receives a specific matching event sent via API, Integrations or a form. This trigger is great for events like payment received, order placed, or a new message received. ## Trigger frequency You can choose to trigger a Loop just the first time a contact matches the trigger settings or every time the contact matches. ![Trigger frequency](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/trigger-frequency.png) For example, if you want to send to a contact every time they update their subscription plan, you can choose to trigger the Loop every time the contact is updated (select "Every time"). However, if you want to send a welcome email to a contact just the first time they are added to your audience, you can choose to trigger the Loop once when the contact is added (select "One time"). ## Changing the trigger type You can change the trigger type at any time. For example, if you create a Loop with the Contact Added trigger, you can change it to the Contact Updated trigger at any time. # Pausing Loops Learn how to pause and stop a Loop to control the sending of emails. ## Pausing vs Stopping Both pausing and stopping a Loop will prevent any emails from sending until you resume the Loop, but there are a few key differences. ### Pausing a Loop When you pause a Loop, you can resume the Loop at any time. During the pause, all contacts that were scheduled to receive an email will receive it once you resume the Loop. However, this assumes that these contacts still meet the necessary criteria to receive the email (e.g., they are subscribed and match any audience filters or other conditions you've set). New contacts that match the Trigger conditions while the Loop is paused will enter the Loop but only within the first 24 hours. Contacts that qualify for the Trigger after the 24-hour mark will not enter the Loop. We will notify you via email once the 24-hour period has elapsed, reminding you that contacts will no longer queue to enter the Loop if you resume it. The 24-hour limitation prevents contacts from entering a Loop that has been paused for an extended period and receiving irrelevant emails. For instance, if your Loop sends a welcome email to new contacts, you wouldn't want a contact to join the Loop three months after signing up and receive a welcome email. ### Stopping a Loop Stopping a Loop will not queue any new contacts to enter the Loop. Any contacts that were queued to enter the Loop will not enter the Loop. That's the key difference between pausing and stopping a Loop. # Quickstart Welcome to Loops, the platform for SaaS email. This is your guide to getting started with Loops. If you're new to setting up email for your SaaS company, this is the guide you should start with. ## What is Loops? Loops is an email platform, that helps you send marketing and transactional emails from our app, API and integrations. With Loops, you can track events and contact properties and then use that information to send emails to increase revenue, engagement or just generally improve your user's experience of your app. **Let's get started ✨** What we'll be covering... 1. [Set up your domain records](#1-set-up-your-domain-records) 2. [Import contacts](#2-import-contacts) 3. [Collect signups with a form](#3-collect-signups-with-a-form) 4. [Create your first email](#4-create-your-first-email) 5. [Send transactional email](#5-send-transactional-email) ## 1. Set up your domain records The first step is to set up your domain, so you can send emails through Loops. You need to do this before you can send any emails. We send from your domain so your emails appear as if they are coming from you. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/set-up-domain.png) We choose to send from a `mail.loops.so` subdomain but you can send from your root domain if you'd prefer. To set up your domain in Loops, you need to add some MX, TXT and CNAME records to your domain's DNS settings so that we can verify that you own the domain you want to send emails from. Once you've set up your domain records, you'll be able to start sending emails! You can always send the records to a developer to help you integrate them. [Add a member to your team](/account/adding-team-member). ## 2. Import contacts To send marketing and product emails to your contacts, you will need to import those contacts into Loops. Note: this isn't required if you only plan to use transactional email, as those contacts can be emailed directly via the API. If you have any existing contacts, i.e from a waitlist, your early access users, a database or your audience on a different platform, you can get started by importing them via CSV. You can import contacts via [CSV upload](/add-users/csv-upload), [API](/api-reference) or through one of our [integrations](/integrations). The most popular path is to import a CSV of existing contacts, then going forward automatically add contacts using our API, a form or an integration. ### Contacts Contacts are unique users in your Loop's audience. We use email and a unique identifier to distinguish contacts. The only required field a contact must have is an email. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/contacts.png) ### Contact properties Contact properties are additional pieces of information you can associate with a contact. They can include things like name, location, job title, and more. We provide [default properties](/contacts/properties#default-contact-properties) like name, user group and source, but you are free to add any number of [custom properties](/contacts/properties#custom-contact-properties) to your contacts, too. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/contact-properties.png) You can use contact properties to [segment your audience](/contacts/filters-segments) and send more targeted emails to specific groups. For example, you could send a promotional email only to contacts with a certain job title or to those in a specific user group. ## 3. Collect signups with a form Adding a form to your site is one (popular) way to automatically add new contacts to your Audience. Even if you're adding contacts programmatically via API or integration, in most cases you'll also want to have an input form on your page to collect emails for newsletters or product updates. To add a form to your site, head over to the [Forms page](https://app.loops.so/forms). You will see a handful of customization options including the form style, placeholder text, success message, font, font color, button color, and more. Make as many changes as you need to create a form that matches your brand. ![Simple form](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/simple-form.png) When you have finished customizing your form, simply copy the HTML or JSX that is automatically generated and paste it into your site. For more flexibility, you can create custom HTML forms that work with Loops. [Read our full guide about custom forms](/forms/custom-form) or check out our [form-based integrations](/integrations#manage-contacts). } href="/integrations/framer" /> } href="/integrations/webflow" /> ## 4. Create your first email To create your first email, first select the type of content you'll be sending. You can send email as a campaign, loop or transactional email. You can also choose to start with a [Template](https://app.loops.so/templates) instead of starting from scratch. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-first-email.png) **Campaigns** are single marketing emails sent to a group (e.g. newsletters, product updates, announcements, investor updates), **Loops** are automated emails sent based on specific triggers or conditions (e.g. onboarding sequences) and **Transactional** emails are one-off emails sent to a single person (e.g. forgot password, two-factor authorization codes, receipts). [Read more](/types-of-emails) In this example, we will build a product update (a campaign), which could be sent to your users if you're building a SaaS. They should be sent monthly or at a faster cadence depending on shipping speed and contain a high-level overview of what you shipped over the last 30 days. To get started, click the **Create** button on the Home screen, followed by **Campaign**. Then we’re going to personalize by adding [dynamic content](/creating-emails/personalizing-emails) and [style it](/creating-emails/editor) to match our brand. ![Adding personalization](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/basic-merge.png) ![Adding styling](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/message-visual.png) You can preview your email any time by hitting the paper airplane icon in the top right of the editor window. Once you're finished with the email content, click **Next** in the top right to choose your audience. Now we'll select the audience segment to whom we'll be sending the update. Since we're sending a product update, we want to send it to our entire audience so we won't be adding any audience filters. If you'd like to segment your audience, just click **Add filter**, which will open the filtering options. ![Filter campaign audience](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/campaign-filter.png) Click the **Next** button top right and you'll see options to send the email immediately or to schedule it for a time in the future. ![Schedule a campaign](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/campaign-schedule.png) Click **Next** one last time to see a review of your email and settings (you can also see a preview of your email, too). If you're happy with how everything looks, click **Schedule send** on the last page and we're done! The email is now scheduled to go out. By the way, you can cancel the scheduled send at any time between the send time and now to update it, or you can just send it right away. ## 6. Set up an automated mail sequence We suggest that new Loops users warm up their new sending domain with a welcome email sequence. A slow ramp up of emails sent to highly-engaged recipients will help prepare your domain for larger campaigns later on ([read more](/deliverability/sending-to-large-audience)). You can create an onboarding or welcome sequence using what we call "loops". A loop looks like this: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/building-loop.png) Go to the [Loops page](https://app.loops.so/loops) and click **New**. We'll start with the "Introduce yourself" template. This will create a loop with a "Contact added" trigger (meaning every new contact will be added to the loop), with an already-written introduction email ready for you. Edit the email and when you're ready to make it live, click **Start**. You can use [branches](/loop-builder/branching-loops) to create more complex workflows, sending contacts down different branches depending on contact properties or even whether they've interacted with campaigns you've sent from Loops. ![Branches in a loop](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/loop-builder-branching.png) ## 7. Send transactional email You'll likely need to send a password reset, login or other automatic email that confirms a user action. These non-promotional emails are considered **Transactional emails** and are the 1:1 emails that are sent to a single contact via API or integration. They're included in all paid Loops plans, and also included within the 2,000 monthly sends available in the [Free plan](/account/free-plan). To get started, click the **Create** button on the Home screen, followed by **Transactional**. Next, it’s time to write and style your email. We recommend following a similar style across all of your Transactional emails. You can do this using the [style panel](/creating-emails/styles#style-panel) and [Saved styles](/creating-emails/styles#saved-styles). Let’s create a Password Reset email together. Add copy and styling, and then to add dynamic content click the **Insert data variables** icon and specify a data variable name. These data variables will be populated with real content when you send the email using the API. ![Add data variables](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/terminal.png) Click the **Next** button top right to view the data needed in your API call. ![View the paylod](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/next.png) Hit **Publish** to finalize the email. Copy the payload details and the ID; you'll need these to send the email using the API. Make an [API request](/api-reference/send-transactional-email) to the transactional endpoint (or use the [SDK](/sdks/javascript#sendtransactionalemail)). ``` POST https://app.loops.so/api/v1/transactional ``` You will need the payload copied from before. Make sure to include values for all of the data variables you added to the email. ```json { "transactionalId": "clfq6dinn000yl70fgwwyp82l", "email": "favorite@example.com", "dataVariables": { "name": "Chris", "passwordResetLink": "https://example.com/reset-password" } } ``` To test sending transactional emails you can use an API tool like [Postman](https://www.postman.com), [Httpie](https://httpie.io) or [Insomnia](https://insomnia.rest) to make API requests. ## 8. Integrate with other platforms Loops integrates with thousands of other platforms, making it easy to send email to your audience, users or customers, regardless of where they originate. ![Create an action in Make](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/make-add-module.png) Set up connections to different apps using a tool like [Zapier](/integrations/zapier) or [Make](/integrations/make), or create contacts using events from [Segment](/integrations/segment). Our [webhook integration for Stripe](/integrations/stripe) lets you easily sync customers and send automated emails based on payment events. ## Get support from the team Whether you’re sending your very first emails for your business or are switching over from another service, we’re always here to help! Every page of Loops has a small `?` widget in the bottom right-hand corner. Click it to receive instant support. 💬 Do you prefer live chat? Click and chat! 💌 Do you prefer email? [Send away](mailto:chris@loops.so) 🧑‍💻 Do you prefer a video call? [Book it](https://calendly.com/chris-loops/loops-in-app-support) # SDKs ## Official SDKs These SDKs are produced and maintained by the Loops team. {/* Re-usable card list of official SDKs */} The official JavaScript/TypeScript SDK for Loops. } href="/sdks/nuxt"> The official Nuxt module for Loops. } href="/sdks/ruby"> The official Ruby SDK for Loops.
Don't see your favourite language or framework? Request an SDK
## Unofficial SDKs The following SDKs are community-submitted and have not been officially reviewed or endorsed by Loops. We recommend thoroughly testing and reviewing the code before integrating it into your project. * [Go](https://github.com/tilebox/loops-go) by Tilebox * [Laravel](https://github.com/plutolinks/laravel-loops) by PlutoLinks * [PHP](https://github.com/plutolinks/loops-php) by PlutoLinks * [Ruby on Rails](https://github.com/danielfriis/loops_rails) by Daniel Friis
Submit an SDK
## SMTP integrations You can also send transactional email using our [SMTP service](/smtp). Check out our guides for popular frameworks. } horizontal={true} title="Send with SMTP from Rails" /> # JavaScript SDK The official Loops SDK for JavaScript, with full TypeScript support. [![](https://img.shields.io/npm/dw/loops?style=social\&label=Downloads)](https://www.npmjs.com/package/loops) ## Installation You can install the package [from npm](https://www.npmjs.com/package/loops): ```bash npm install loops ``` Minimum Node version required: 18.0.0. 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 as `LOOPS_API_KEY` in an `.env` file). } href="/sdks/javascript/nextjs"> Read our guide for sending email from Next.js projects. ## Usage ```javascript import { LoopsClient } from "loops"; const loops = new LoopsClient(process.env.LOOPS_API_KEY); const resp = await loops.createContact("email@provider.com"); ``` See the API documentation to learn more about [rate limiting](/api-reference#rate-limiting) and [error handling](/api-reference#debugging). ## 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. ```javascript import { LoopsClient, RateLimitExceededError } from "loops"; const loops = new LoopsClient(process.env.LOOPS_API_KEY); try { const resp = await loops.createContact("email@provider.com"); } catch (error) { if (error instanceof RateLimitExceededError) { console.log(`Rate limit exceeded (${error.limit} per second)`); // Code here to re-try this request } else { // Handle other errors } } ``` ## 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](/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK. ## Methods * [testApiKey()](#testapikey) * [createContact()](#createcontact) * [updateContact()](#updatecontact) * [findContact()](#findcontact) * [deleteContact()](#deletecontact) * [getMailingLists()](#getmailinglists) * [sendEvent()](#sendevent) * [sendTransactionalEmail()](#sendtransactionalemail) * [getCustomFields()](#getcustomfields) *** ### testApiKey() Test that an API key is valid. [API Reference](/api-reference/api-key) #### Parameters None #### Example ```javascript const resp = await loops.testApiKey(); ``` #### Response This method will return a success or error message: ```json { "success": true, "teamName": "My team" } ``` ```json { "error": "Invalid API key" } ``` *** ### createContact() Create a new contact. [API Reference](/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.
Please [add custom properties](/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](/contacts/properties#dates)). | | `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Examples ```javascript const resp = await loops.createContact("hello@gmail.com"); const contactProperties = { firstName: "Bob" /* Default property */, favoriteColor: "Red" /* Custom property */, }; const mailingLists = { cm06f5v0e45nf0ml5754o9cix: true, cm16k73gq014h0mmj5b6jdi9r: false, }; const resp = await loops.createContact( "hello@gmail.com", contactProperties, mailingLists ); ``` #### Response This method will return a success or error message: ```json { "success": true, "id": "id_of_contact" } ``` ```json { "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](/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.
Please [add custom properties](/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](/contacts/properties#dates)). | | `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Example ```javascript const contactProperties = { firstName: "Bob" /* Default property */, favoriteColor: "Blue" /* Custom property */, }; const resp = await loops.updateContact("hello@gmail.com", contactProperties); /* Updating a contact's email address using userId */ const resp = await loops.updateContact("newemail@gmail.com", { userId: "1234", }); ``` #### Response This method will return a success or error message: ```json { "success": true, "id": "id_of_contact" } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### findContact() Find a contact. [API Reference](/api-reference/find-contact) #### Parameters You must use one parameter in the request. | Name | Type | Required | Notes | | -------- | ------ | -------- | ----- | | `email` | string | No | | | `userId` | string | No | | #### Examples ```javascript const resp = await loops.findContact({ email: "hello@gmail.com" }); const resp = await loops.findContact({ userId: "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](/api-reference/delete-contact) #### Parameters You must use one parameter in the request. | Name | Type | Required | Notes | | -------- | ------ | -------- | ----- | | `email` | string | No | | | `userId` | string | No | | #### Example ```javascript const resp = await loops.deleteContact({ email: "hello@gmail.com" }); const resp = await loops.deleteContact({ userId: "12345" }); ``` #### Response This method will return a success or error message: ```json { "success": true, "message": "Contact deleted." } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### getMailingLists() Get a list of your account's mailing lists. [Read more about mailing lists](/contacts/mailing-lists) [API Reference](/api-reference/list-mailing-lists) #### Parameters None #### Example ```javascript const resp = await loops.getMailingLists(); ``` #### Response This method will return a list of mailing list objects containing `id`, `name` and `isPublic` attributes. If your account has no mailing lists, an empty list will be returned. ```json [ { "id": "cm06f5v0e45nf0ml5754o9cix", "name": "Main list", "isPublic": true }, { "id": "cm16k73gq014h0mmj5b6jdi9r", "name": "Investors", "isPublic": false } ] ``` *** ### sendEvent() Send an event to trigger an email in Loops. [Read more about events](/events) [API Reference](/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.
Please [add custom properties](/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](/contacts/properties#dates)). | | `eventProperties` | object | No | An object 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](/events/properties#important-information-about-event-properties)). | | `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Examples ```javascript const resp = await loops.sendEvent({ email: "hello@gmail.com", eventName: "signup", }); const resp = await loops.sendEvent({ email: "hello@gmail.com", eventName: "signup", eventProperties: { username: "user1234", signupDate: "2024-03-21T10:09:23Z", }, mailingLists: { cm06f5v0e45nf0ml5754o9cix: true, cm16k73gq014h0mmj5b6jdi9r: false, }, }); // In this case with both email and userId present, the system will look for a contact with either a // matching `email` or `userId` value. // If a contact is found for one of the values (e.g. `email`), the other value (e.g. `userId`) will be updated. // If a contact is not found, a new contact will be created using both `email` and `userId` values. // Any values added in `contactProperties` will also be updated on the contact. const resp = await loops.sendEvent({ userId: "1234567890", email: "hello@gmail.com", eventName: "signup", contactProperties: { firstName: "Bob", plan: "pro", }, }); ``` #### Response This method will return a success or error: ```json { "success": true } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### sendTransactionalEmail() Send a transactional email to a contact. [Learn about sending transactional email](/transactional/guide) [API Reference](/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.
Values can be of type `string` or `number`. | | `attachments` | object\[] | No | A list of attachments objects.
**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](/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. | #### Examples ```javascript const resp = await loops.sendTransactionalEmail({ transactionalId: "clfq6dinn000yl70fgwwyp82l", email: "hello@gmail.com", dataVariables: { loginUrl: "https://myapp.com/login/", }, }); // Please contact us to enable attachments on your account. const resp = await loops.sendTransactionalEmail({ transactionalId: "clfq6dinn000yl70fgwwyp82l", email: "hello@gmail.com", dataVariables: { loginUrl: "https://myapp.com/login/", }, attachments: [ { filename: "presentation.pdf", contentType: "application/pdf", data: "JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...", }, ] }); ``` #### Response This method will return a success or error message. ```json { "success": true } ``` If there is a problem with the request, a descriptive error message will be returned: ```json { "success": false, "path": "dataVariables", "message": "There are required fields for this email. You need to include a 'dataVariables' object with the required fields." } ``` ```json { "success": false, "error": { "path": "dataVariables", "message": "Missing required fields: login_url" }, "transactionalId": "clfq6dinn000yl70fgwwyp82l" } ``` *** ### getCustomFields() Get a list of your account's custom fields. These are custom properties that can be added to contacts to store extra data. [Read more about contact properties](/contacts/properties) [API Reference](/api-reference/list-custom-fields) #### Parameters None #### Example ```javascript const resp = await loops.getCustomFields(); ``` #### Response This method will return a list of custom field objects containing `key`, `label` and `type` attributes. If your account has no custom fields, an empty list will be returned. ```json [ { "key": "favoriteColor", "label": "Favorite Color", "type": "string" }, { "key": "plan", "label": "Plan", "type": "string" } ] ``` *** ## Version history * `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.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. *** ## Contributing Bug reports and pull requests are welcome at [github.com/Loops-so/loops-js](https://github.com/Loops-so/loops-js). Please read our [Contributing Guidelines](https://github.com/Loops-so/loops-js/blob/main/CONTRIBUTING.md). # Set up Loops in Next.js How to send email from your Next.js project with Loops. This guide shows how to add Loops to your Next.js project, so you can send transactional emails, manage contacts and trigger automated emails. ## Install the SDK The first step is to install the Loops SDK. This is written in TypeScript so you can benefit from strict types when coding. ```bash npm i loops ``` You'll need an API key to use the SDK. Go to your [API Settings page](https://app.loops.so/settings?page=api) in Loops to generate and copy a key. Save this value in your environment variables as something like `LOOPS_API_KEY`. Then you can import the Loops SDK client like this: ```javascript import { LoopsClient } from "loops"; const loops = new LoopsClient(process.env.LOOPS_API_KEY); ``` You can also use the [Loops API](/api-reference) directly in your app, without the SDK. [Read more](#using-the-api-instead) Explore our official JS/TS SDK. Read the Loops API reference. ## Server-side only It is important that you only use the Loops API and SDK from server-side code. If you make calls directly in the browser, you risk exposing your API key, which would give other people read and write access to your Loops account data. Additionally, the Loops API does not support cross-origin requests made from client-side JavaScript. If you want to make calls from the browser—for example, to collect newsletter subscriptions from a form—create proxy endpoints. To add a new contact, create an internal API endpoint and use the Loops API/SDK within it. ```typescript app/api/contacts/route.ts import { NextRequest, NextResponse } from "next/server"; import { LoopsClient } from "loops"; const loops = new LoopsClient(process.env.LOOPS_API_KEY as string); export async function POST(request: NextRequest) { const res = await request.json(); const email = res["email"]; // Note: updateContact() will create or update a contact const resp: { success: boolean, id?: string, message?: string } = await loops.updateContact(email); return NextResponse.json({ success: resp.success }); } ``` ## Send transactional email A big use case for using Loops in a Next.js project is to send transactional email to users. These emails are one-off emails, which help users with your product, for example password reset emails notification emails. To create a transactional email, go to the Transactional page in Loops. Click **Create** or select a template. Create the email in [the editor](/creating-emails/editor), which gives you rich formatting options and components. ![Creating a transactional email](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/email.png) To add dynamic content (like rest password URLs or user data) you can add [data variables](/transactional/guide#add-data-variables) into the email from the toolbar. Give each data variable a unique name. You can populate these variables from your code when sending the email via the SDK in the next step. Make sure to Publish your transactional email when you're done. Now your email is created you can start sending emails. In your code, call `sendTransactionalEmail()` and include values for each of the data variables you added to your email. ```javascript JavaScript const dataVariables = { loginUrl: "https://myapp.com/login/", }; const resp = await loops.sendTransactionalEmail({ transactionalId: "transaction_email_id", email: "user@gmail.com", dataVariables }); if (!resp.success) { // The sending failed } else { // The email was sent successfully } ``` ```typescript TypeScript const dataVariables: { loginUrl: string } = { loginUrl: "https://myapp.com/login/", }; const resp: { success: boolean, path?: string, message?: string } | { success: false; error: { path: string; message: string; }; transactionalId?: string; } = await loops.sendTransactionalEmail({ transactionalId: "transaction_email_id", email: "user@gmail.com", dataVariables }); if (!resp.success) { // The sending failed } else { // The email was sent successfully } ``` The response will contain a `success` boolean telling you if the email was sent successfully. If it was not, you'll also receive an error message. Read more in the SDK docs. ## Sync users to Loops Another main use case for teams using Loops is to keep their Loops audience updated when user data changes in their application. To do this you can use the `updateContact()` method. `updateContact()` can be used as a shortcut "update or create" function. It will create new contacts if the provided email address and/or user ID are not found. For example, you may store custom data in Loops like subscription plan level or user usage information that you include in emails. You can update contacts in Loops like this: ```javascript JavaScript const contactProperties = { userId: 826, planName: "Pro" /* Custom property */, usage: 172629 /* Custom property */, }; const resp = await loops.updateContact("user@gmail.com", contactProperties); if (!resp.success) { // The call failed } else { // The contact was updated OK } ``` ```typescript TypeScript const contactProperties: Record = { userId: 826, planName: "Pro" /* Custom property */, usage: 172629 /* Custom property */, }; const resp: { success: boolean, id?: string, message?: string } = await loops.updateContact("user@gmail.com", contactProperties); if (!resp.success) { // The call failed } else { // The contact was updated OK } ``` The TypeScript example above shows how to properly type your `contactProperties` object and the expected response from the `updateContact` method. We recommend always populating the `userId` value for users, which should be their unique value in your platform. This allows you to change a contact's email address in the future, because they have a separate unique identifier in the system. Read more in the SDK docs. ## Trigger loops with events A third example of using the Loops SDK is to trigger [loops](/loop-builder). Loops are automated email workflows, which can send multiple emails to contacts. You can trigger these emails using [events](/events), and you can send events to Loops using the SDK. For example, you may have a loop that you send to new users after they have completed an onboarding flow in your app. First, [create a new loop](https://app.loops.so/loops) using the "Event received" trigger. Add emails, timers and audience filters to your loop as you wish. Then to trigger this email sequence, send an event to Loops. If your event name is `completedOnboarding`, your call would look like this... ```javascript JavaScript const resp = await loops.sendEvent({ email: "user@gmail.com", eventName: "completedOnboarding", }); if (!resp.success) { // The event was not sent } else { // The event was sent OK } ``` ```typescript TypeScript const resp: { success: boolean, message?: string, } = await loops.sendEvent({ email: "user@gmail.com", eventName: "completedOnboarding", }); if (!resp.success) { // The event was not sent } else { // The event was sent OK } ``` Read more in the SDK docs. ## Using the API instead If you prefer, you can use the [Loops API](/api-reference) directly instead of using the SDK. You should never call the API from your front-end code as this will expose your API key. For example, you can send a transactional email like this: ```javascript const data = { email: "user@gmail.com", transactionalId: "abcdefg", dataVariables: { loginUrl: "https://myapp.com/login/?code=1234" }, }; return fetch('https://app.loops.so/api/v1/transactional', { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.LOOPS_API_KEY}`, }, body: JSON.stringify(data), }) .then(response => response.json()) .then(response => { if (!response.success) { // The sending failed } else { // The email was sent successfully } }) .catch(err => console.error(err)); ``` On Vercel, each backend function gets its own lambda. Make sure you use `return` otherwise the lambda might be terminated before the promise is evaluated. Read through our API documentation. # Nuxt module The official Loops Nuxt module. [![](https://img.shields.io/npm/dw/nuxt-loops?style=social\&label=Downloads)](https://www.npmjs.com/package/nuxt-loops) This Nuxt module makes it easy to add the Loops [JavaScript SDK](/sdks/javascript) to your Nuxt project. ## Installation You can install the package [from npm](https://www.npmjs.com/package/nuxt-loops): ```bash npm install nuxt-loops ``` You will need a Loops API key to use the module. 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 as `LOOPS_API_KEY` in an `.env` file). Then add `nuxt-loops` to your modules list and add a reference to your API key: ```js nuxt.config.ts export default defineNuxtConfig({ modules: ['nuxt-loops'], loops: { apiKey: process.env.LOOPS_API_KEY } }); ``` ## Usage The Loops API and SDK should only be used on the server side to protect your API key. To use the module, import `loops` from the request context. Then call one of the SDK methods. Read through the [JS SDK docs](/sdks/javascript#methods) for more details. ```javascript export default defineEventHandler(async (event) => { const { loops } = event.context; const response = await loops.updateContact("hello@gmail.com", { firstName: "Bri", lastName: "Chambers", }) }); ``` See the API documentation to learn more about [rate limiting](/api-reference#rate-limiting) and [error handling](/api-reference#debugging). Explore our official JS/TS SDK. Read the Loops API reference. *** ## Version history * `v1.0.0` (Sep 6, 2024) - Initial release. # Ruby SDK The official Loops SDK for Ruby. [![Gem Total Downloads](https://img.shields.io/gem/dt/loops_sdk?style=social)](https://rubygems.org/gems/loops_sdk) ## Installation Install the gem and add it to the application's Gemfile like this: ```bash bundle add loops_sdk ``` If bundler is not being used to manage dependencies, you can install the gem like this: ```bash gem install loops_sdk ``` ## 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](/api-reference#rate-limiting) and [error handling](/api-reference#debugging). In an initializer, import and configure the SDK: ```ruby config/initializers/loops.rb require "loops_sdk" LoopsSdk.configure do |config| config.api_key = 'your_api_key' end ``` Then you can call methods in your code: ```ruby begin response = LoopsSdk::Transactional.send( transactional_id: "closfz8ui02yq...", email: "dan@loops.so", data_variables: { loginUrl: "https://app.domain.com/login?code=1234567890" } ) rescue LoopsSdk::APIError => e # Do something if there is an error from the API end ``` ## 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](/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK. ## Methods * [ApiKey.test()](#apikey-test) * [Contacts.create()](#contacts-create) * [Contacts.update()](#contacts-update) * [Contacts.find()](#contacts-find) * [Contacts.delete()](#contacts-delete) * [MailingLists.list()](#mailinglists-list) * [Events.send()](#events-send) * [Transactional.send()](#transactional-send) * [CustomFields.list()](#customfields-list) *** ### ApiKey.test() Test if your API key is valid. [API Reference](/api-reference/api-key) #### Parameters None #### Example ```ruby response LoopsSdk::ApiKey.test ``` #### Response This method will return a success or error message: ```json { "success": true, "teamName": "Company name" } ``` ```json { "error": "Invalid API key" } ``` *** ### Contacts.create() Create a new contact. [API Reference](/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.
Please [add custom properties](/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `nil` (to reset a value), `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). | | `mailing_lists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Examples ```ruby response = LoopsSdk::Contacts.create(email: "hello@gmail.com") contact_properties = { firstName: "Bob" /* Default property */, favoriteColor: "Red" /* Custom property */, }; mailing_lists = { cm06f5v0e45nf0ml5754o9cix: true, cm16k73gq014h0mmj5b6jdi9r: false, }; response = LoopsSdk::Contacts.create( email: "hello@gmail.com", properties: contact_properties, mailing_lists: mailing_lists ) ``` #### Response This method will return a success or error message: ```json { "success": true, "id": "id_of_contact" } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### Contacts.update() 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](/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.
Please [add custom properties](/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `nil` (to reset a value), `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). | | `mailing_lists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Example ```ruby contact_properties = { firstName: "Bob" /* Default property */, favoriteColor: "Blue" /* Custom property */, }; response = LoopsSdk::Contacts.update( email: "hello@gmail.com", properties: contact_properties ) # Updating a contact's email address using userId response = LoopsSdk::Contacts.update( email: "newemail@gmail.com", properties: { userId: "1234", }) ``` #### Response This method will return a success or error message: ```json { "success": true, "id": "id_of_contact" } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### Contacts.find() Find a contact. [API Reference](/api-reference/find-contact) #### Parameters You must use one parameter in the request. | Name | Type | Required | Notes | | --------- | ------ | -------- | ----- | | `email` | string | No | | | `user_id` | string | No | | #### Examples ```ruby response = LoopsSdk::Contacts.find(email: "hello@gmail.com") response = LoopsSdk::Contacts.find(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 */ } ] ``` *** ### Contacts.delete() Delete a contact. [API Reference](/api-reference/delete-contact) #### Parameters You must use one parameter in the request. | Name | Type | Required | Notes | | --------- | ------ | -------- | ----- | | `email` | string | No | | | `user_id` | string | No | | #### Example ```ruby response = LoopsSdk::Contacts.delete(email: "hello@gmail.com") response = LoopsSdk::Contacts.delete(user_id: "12345") ``` #### Response This method will return a success or error message: ```json { "success": true, "message": "Contact deleted." } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### MailingLists.list() Get a list of your account's mailing lists. [Read more about mailing lists](/contacts/mailing-lists) [API Reference](/api-reference/list-mailing-lists) #### Parameters None #### Example ```ruby response = LoopsSdk::MailingLists.list ``` #### Response This method will return a list of mailing list objects containing `id`, `name` and `isPublic` attributes. If your account has no mailing lists, an empty list will be returned. ```json [ { "id": "cm06f5v0e45nf0ml5754o9cix", "name": "Main list", "isPublic": true }, { "id": "cm16k73gq014h0mmj5b6jdi9r", "name": "Investors", "isPublic": false } ] ``` *** ### Events.send() Send an event to trigger an email in Loops. [Read more about events](/events) [API Reference](/api-reference/send-event) #### Parameters | Name | Type | Required | Notes | | -------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event_name` | string | Yes | | | `email` | string | No | The contact's email address. Required if `user_id` is not present. | | `user_id` | string | No | The 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` | object | No | An object containing contact properties, which will be updated or added to the contact when the event is received.
Please [add custom properties](/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
Values can be of type `string`, `number`, `nil` (to reset a value), `boolean` or `date` ([see allowed date formats](/contacts/properties#dates)). | | `event_properties` | object | No | An object 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](/events/properties#important-information-about-event-properties)). | | `mailing_lists` | object | No | An object of mailing list IDs and boolean subscription statuses. | #### Examples ```ruby response = LoopsSdk::Events.send( event_name: "signup", email: "hello@gmail.com" ) response = LoopsSdk::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 userId 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. response = LoopsSdk::Events.send( event_name: "signup", email: "hello@gmail.com", user_id: "1234567890", contact_properties: { firstName: "Bob", plan: "pro", }, }) ``` #### Response This method will return a success or error: ```json { "success": true } ``` ```json { "success": false, "message": "An error message here." } ``` *** ### Transactional.send() Send a transactional email to a contact. [Learn about sending transactional email](/transactional/guide) [API Reference](/api-reference/send-transactional-email) #### Parameters | Name | Type | Required | Notes | | ---------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transactional_id` | string | Yes | The ID of the transactional email to send. | | `email` | string | Yes | The email address of the recipient. | | `add_to_audience` | boolean | No | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn’t already exist). | | `data_variables` | object | No | An object containing data as defined by the data variables added to the transactional email template.
Values can be of type `string` or `number`. | | `attachments` | object\[] | No | A list of attachments objects.
**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](/transactional/attachments) | | `attachments[].filename` | string | No | The name of the file, shown in email clients. | | `attachments[].content_type` | string | No | The MIME type of the file. | | `attachments[].data` | string | No | The base64-encoded content of the file. | #### Examples ```ruby response = LoopsSdk::Transactional.send( transactional_id: "clfq6dinn000yl70fgwwyp82l", email: "hello@gmail.com", data_variables: { loginUrl: "https://myapp.com/login/", }, ) # Please contact us to enable attachments on your account. response = LoopsSdk::Transactional.send( transactional_id: "clfq6dinn000yl70fgwwyp82l", email: "hello@gmail.com", data_variables: { loginUrl: "https://myapp.com/login/", }, attachments: [ { filename: "presentation.pdf", content_type: "application/pdf", data: "JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...", }, ], }) ``` #### Response This method will return a success or error message. ```json { "success": true } ``` If there is a problem with the request, a descriptive error message will be returned: ```json { "success": false, "path": "dataVariables", "message": "There are required fields for this email. You need to include a 'dataVariables' object with the required fields." } ``` ```json { "success": false, "error": { "path": "dataVariables", "message": "Missing required fields: login_url" }, "transactionalId": "clfq6dinn000yl70fgwwyp82l" } ``` *** ### CustomFields.list() Get a list of your account's custom fields. These are custom properties that can be added to contacts to store extra data. [Read more about contact properties](/contacts/properties) [API Reference](/api-reference/list-custom-fields) #### Parameters None #### Example ```ruby response LoopsSdk::CustomFields.list ``` #### Response This method will return a list of custom field objects containing `key`, `label` and `type` attributes. If your account has no custom fields, an empty list will be returned. ```json [ { "key": "favoriteColor", "label": "Favorite Color", "type": "string" }, { "key": "plan", "label": "Plan", "type": "string" } ] ``` *** ## Version history * `v0.1.2` (Aug 16, 2024) - Support for resetting contact properties with `nil`. * `v0.1.1` (Aug 16, 2024) - Added `ApiKey.test` method for testing API keys. * `v0.1.0` (Aug 16, 2024) - Initial release. *** ## Contributing Bug reports and pull requests are welcome on GitHub at [github.com/Loops-so/loops-rb](https://github.com/Loops-so/loops-rb). Please read our [Contributing Guidelines](https://github.com/Loops-so/loops-rb/blob/main/CONTRIBUTING.md). # Setting up your domain Steps for adding a sending domain to your account. When you set up your account for the first time, you need to set up your domain records in order to start sending email. We'll be sending email on your behalf, so we need to verify that you own the domain you're sending from. Here's how to set it up in just a few steps. ## Step 1: Navigate to your domain settings page Go to **Settings -> Domain** and click **View records** (or [click this link](https://app.loops.so/sending-domain) to go directly). ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/domain-records.png) ## Step 2: Set up your records On this page, you'll see a few things. **Your records** These are SPF, DKIM and MX records that need to be set up in your domain zone editor inside of your domain registrar like Namecheap, Google Domains, AWS, Godaddy or elsewhere. Next to each record is a clipboard icon. You can use this to copy the records to your clipboard and easily paste them into your domain registrar. **Your sending domain** This is indicated below by “yourcompany.com”. This will have your domain listed. If you'd like to change domains, you can do so in the [account settings](https://app.loops.so/settings). **A verify records button** Once you have copied your records to your registrar, click this button to verify they have been set up correctly. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/set-up-records.png) ## Step 3: Add your domain records Copy and paste the records one by one into your registrar. You want to use the **Type** (indicated as TXT, CNAME and MX) in setting up your records, ***not*** the title of the record e.g. SPF, DKIM, MX. Loops' records for SPF are at envelope.sendingdomain.com, meaning they won't collide with any other SPF records you have set up.\ We specify a DMARC record so that you have one, but you can also just have a single DMARC at the root domain level. DNS records can be added from the "DNS" page within a website. Click **Add record** to open the form. Select a "Type" (TXT, MX or CNAME), then paste the "Name" and "Value" information. [Read the guide](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/#create-dns-records) Be sure to set the proxy to “DNS Only” for the CNAME records: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/cloudflare.png) Dreamhost is currently unsupported in full because you cannot add custom MX records. For GoDaddy, [read this guide](https://www.godaddy.com/help/manage-dns-records-680). Google Domains (and potentially other providers) combine the mail server and priority inputs into a single line. So if you receive an error like this when setting up the domain, make sure to instead type out the input like this: `10 feedback-smtp.us-east-1.amazonses.com` ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/google-domains.png) Google domains will also include quotes “ “ around some record names. This is expected and will not impact anything. Go to the **Advanced DNS** page for your domain. If you are using the automatic Gmail/Gsuite integration with Namecheap, you will need to disable the automatic integration and switch to **Custom MX** in the **Mail Settings** dropdown. You then need to [add an MX record](https://support.google.com/a/answer/174125#current\&legacy\&zippy=%2Cgoogle-workspace-current-version-later) to set up Gmail on your domain again. Then you can add Loops' MX records by clicking **Add new record** in the "Mail Settings" section and pasting in the values provided in Loops. Click the `✓` icon to save each record. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/namecheap-1.png) Add the TXT and CNAME records by clicking **Add new record** in the "Host Records" section. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/namecheap-2.png) For Route 53, [read this guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-creating.html). Unfortunately, Wix DNS [does not support subdomains](https://support.wix.com/en/article/request-connecting-a-mailbox-to-a-subdomain) for MX records when your nameservers are pointed at Wix. If you purchased a domain outside of Wix, you should use the ["Pointing" method](https://support.wix.com/en/article/connecting-a-domain-to-wix-using-the-pointing-method) for your domain, which will let you set up DNS records externally a domain registrar. Then you can add records using [this guide](https://support.wix.com/en/article/managing-dns-records-in-your-wix-account). Make sure you enter the “Priority” while setting up your MX record. In most registrars this is done by formatting it like “10 \{pastedrecordname}”. Occasionally you will be asked to place it on a separate line. Just make sure to read the instructions on the page as you set up your MX record and if you have any questions, just ping [adam@loops.so](mailto:adam@loops.so) ## Step 4: Verify your records are set up correctly After you have copied and pasted your records into your domain registrar, click **Verify Records** at the bottom of the page to check your configuration is correct. Sometimes records can take up to an hour to propagate across all the servers. During that time you may see different records validate. This is totally normal, just check back later. If the domain is set up correctly, you should see a page like the one below. If not, check back soon; sometimes records can take some time to propagate. Notice the "Records present" in green next to each record section. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/verified.png) Confused or have questions? Just shoot us an [email](mailto:adam@loops.so) 🙂 # Sending your first email A guide for creating and sending emails with Loops. So you're ready to send your first email from Loops! Let's go through some best practices and then see how creating an email works. ## Best practices Here are some important things to know and bear in mind when sending email with Loops. * We have a “low-html” editor, which means your emails send with a minimal amount of styles applied. We do this so your emails are highly readable and so they're more likely to not be placed in the spam folder or deprioritized in the inbox by your email provider. * Try not to use sensational copy like “sale”, “discount” or exclamation points in your emails. * It's also important to keep your emails short and to the point. Use an efficient subject line that encourages the reader to open the email and get to the point quickly in the body of the message. * Use personalization when possible to make the message more engaging and relevant to the reader by [personalizing your emails](/creating-emails/personalizing-emails). ## Send your first email First, choose which type of email you want to send: a campaign, a loop or a transactional email. [Find out about the types of email](/types-of-emails) you can send from Loops. To send your first email, simply choose a template or start an email from scratch. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/getting-started.png) ## Sending settings Along with the email subject, you can also determine the "From name" shown in email clients for each email. By clicking `>` you can also set the From email address (which is always tied to your sending domain), Reply to email and the Preview text (typically shown in email clients just beneath the Subject). ![Email sending settings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/more-button.png) You can include dynamic variables in these fields, too, making them personalized for each recipient of your campaigns, loops and transactional emails. Just click the contact property, event property or data variable icon next to each field. ![Adding dynamic data to the sending settings](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/sending-setting-data.png) ## Add contact data You may want to add contact data into your emails, for example, a first name or a purchased product's name. You can do this by adding dynamic content to your email. [Learn more about personalizing emails](/creating-emails/personalizing-emails) ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/basic-merge.png) ## Make your message visual When you design an email with Loops, you can add instructive screenshots, GIFs, or images to create an engaging email. Simply drag and drop any image (including GIFs!) into the editor. You can also easily add links, lists, buttons and dividers to create more engaging and useful emails. We also offer a styling panel to customize design elements like font size, text color, background color, borders and spacing. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/message-visual.png) [Read more about our editor](/creating-emails/editor) ## Preview your email Once you are happy with the design of your email, you can preview it in your email client. Click the **Preview** icon in the top right corner of the email editor to send a test email to yourself or anyone on your team from the "Send a Preview Email" modal. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/preview-email.png) If you have dynamic content in your email, you can add custom content on the **Dynamic content** tab. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/preview-data.png) You can also test sending emails by using email addresses with `@example.com` and `@test.com` domains. This will not actually send emails but is a great way to test loops, schedule campaigns, or test transactional emails without affecting your sending domain's reputation. ## Set up your email sending for the first time Now you're ready to send the email! You are able to select which contacts to send to, then either send it now or schedule it for later. Wondering what the next step is to get started implementing emails for your SaaS? Check out the [getting started guide](https://loops.so/getting-started). **Did we miss something?** Not to worry! Just email our support team at [chris@loops.so](mailto:chris@loops.so) ✌️ # Send with SMTP Send Loops emails over SMTP. Our SMTP service is still under development but is intended to be stable as currently deployed. Please contact [adam@loops.so](mailto:adam@loops.so) with any feedback during this period. You can send transactional emails over SMTP, meaning you can use Loops to power emails in platforms like Supabase and development tools and frameworks like Laravel, Rails, Django and Nodemailer. ## How it works The Loops way of sending emails over SMTP is a bit different from other services. First, you create transactional email templates in [the Loops editor](/creating-emails/editor) for the emails you want to send. Our rich editor helps you create beautiful, client-compatible *and* easy-to-update emails rather than hand-coding them. Use these SMTP settings in your application: | Field | Value | | ----------- | ------------------------------------------------------------------------------------------ | | Host | `smtp.loops.so` | | Port number | `587` | | Username | `loops` | | Password | An API key copied from your [API settings](http://app.loops.so/settings?page=api) in Loops | Then, when it comes to sending emails, instead of the content of an email, you send an API-like request body like this: ```json { "transactionalId": "clomzp89u035xl50px7wrl0ri", "email": "dan@loops.so", "dataVariables": { "confirmationUrl": "https://myapp.com/confirm/12345/" } } ``` This content **needs to be converted to a string** and then sent as the email body. Loops takes the provided data and compiles an email using the template you specify in the `transactionalId` value plus the provided `dataVariables`, then sends the email to `email`. Every email sent using Loops' SMTP service requires a transactional email to be set up in your Loops account. Note the `transactionalId` value in the email payload. ## SMTP Integrations Learn how to set up SMTP in platforms and developer tools. ### Integrations } href="/integrations/auth0" > Send Auth0 authentication emails with Loops. } href="/integrations/supabase" > Send Supabase authentication emails with Loops. ### Frameworks } href="/smtp/django"> Send transactional emails from your Django project. Send transactional emails from your Laravel project. } href="/smtp/rails"> Send transactional emails from your Rails project. # Django Send transactional emails from your Django project using Loops' SMTP service. As Loops' SMTP service requires sending an API-like email body rather than a full email, it's not recommended to use Loops as the default SMTP service for your app in your settings file.\ Instead, use a custom `connection` for each email request that you want to send through Loops. Sending email from Django with Loops' SMTP service is easy but there's one gotcha: the email body needs to be an [API-like payload](/smtp#how-it-works). This may seem strange at first but it allows you to use Loops' WYSIWYG editor to craft your emails and keep email templating outside of your code repo. We are using a custom `connection` for sending this email as typically only some emails in a project will be sent through Loops. Add these settings to your project (e.g. in an `.env` file). | Field | Value | | ----------- | ------------------------------------------------------------------------------------------ | | Host | `smtp.loops.so` | | Port number | `587` | | Username | `loops` | | Password | An API key copied from your [API settings](http://app.loops.so/settings?page=api) in Loops | Every email sent from Django over Loops SMTP requires a transactional email to be set up in your Loops account. Note the `transactionalId` value in the email payload. ```python from django.core.mail import send_mail, get_connection import json import os with get_connection( host=os.environ['LOOPS_SMTP_HOST'], port=os.environ['LOOPS_SMTP_PORT'], username=os.environ['LOOPS_SMTP_USER'], password=os.environ['LOOPS_SMTP_PASSWORD'], use_tls=True # Has to be True ) as connection: email = 'dan@loops.so' # This payload can be copied from a transactional email's # Publish page in Loops payload = { "transactionalId": "clomzp89u635xl30px7wrl0ri", "email": email, "dataVariables": { "buttonUrl": "https://myapp.com/login/", "userName": "Bob" } } send_mail( "Subject here", # Overwritten by Loops template json.dumps(payload), # Stringify the payload "from@example.com", # Overwritten by Loops template [email], fail_silently=False, connection=connection ) ``` # Laravel Send transactional emails from your Laravel project using Loops' SMTP service. Transactional emails with Loops simplifies your code. With our WYSIWYG editor and API-like payloads, you can design and manage email templates outside of your codebase, ensuring cleaner code and easier template maintenance. Unlike older SMTP services, Loops requires the body of emails sent via SMTP to be formatted as an [API-like payload](/smtp#how-it-works). This approach allows you to use Loops' [powerful email editor](/creating-emails/editor) to craft your emails and keep email templating outside of your application code. Every email sent over Loops SMTP requires a transactional email to be set up in your Loops account. Note the `transactionalId` value in the email payload. Here's how you can set up transactional emails with Loops SMTP in Laravel: Create transactional emails in Loops using the [editor](/creating-emails/editor). Add [data variables](/transactional/guide#add-data-variables) to your emails for any dynamic content you want to send from your Laravel application. ![Add a data variable](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/terminal.png) To configure Loops SMTP in your Laravel project, add the following values to your `.env` file. `MAIL_PASSWORD` should be an API key from your [API Settings](http://app.loops.so/settings?page=api) page. ```bash .env MAIL_MAILER=smtp MAIL_HOST=smtp.loops.so MAIL_PORT=587 MAIL_USERNAME=loops MAIL_PASSWORD= MAIL_ENCRYPTION=tls ``` Now you can send emails from your application. If you haven't already, create a mailable class, for example `AuthEmail`: ```bash php artisan make:mail AuthEmail ``` Loops' SMTP system doesn't send full HTML emails directly. Instead, you should provide a structured API-like payload, which Loops will then use to render an HTML email. Create a view for your email, like below. You can copy an example payload from the **Publish** page of your transactional email in Loops. ```json resources/views/mail/auth-email-text.blade.php { "transactionalId": "clomzp89u635xl30px7wrl0ri", "email": "{{ $email }}", /* recipient */ "dataVariables": { "loginUrl": "https://myapp.com/login?code={{ $auth_code }}" } } ``` Then add a reference to your template in the `Content` definition using the `text` key. You also need to pass the values for the recipient email address and any data variables in your email. In this case we are using a `$user` property added to the constructor. ```php app/Mail/AuthEmail.php use App\Models\User; class AuthEmail extends Mailable { /** * Create a new message instance. */ public function __construct( private User $user, ) {} /** * Get the message content definition. */ public function content(): Content { return new Content( text: 'mail.auth-email-text', with: [ 'email' => $this->user->email, 'auth_code' => $this->user->auth_code, ] ); } } ``` You can omit the `view` option typically required for HTML emails in Laravel. Loops handles HTML rendering using the provided payload. You can skip adding values to the `Envelope` because the "from" address and subject are all defined within Loops on your transactional email. Now you can send transactional emails. ```php use \App\Mail\AuthEmail; Mail::to('anything.here@mail.com')->send(new AuthEmail($user)); ``` Note that the email address defined in `to()` will not be used for sending the email even though it's a required parameter. **You have to provide the recipient's email to the template itself**. You can read more about sending emails from Laravel [in their docs](https://laravel.com/docs/11.x/mail#writing-mailables). Read how to send transactional email with our API. Learn how to send transactional email with Loops. # Ruby on Rails Send transactional emails from your Rails project using Loops' SMTP service. Transactional emails with Loops simplifies your code. With our WYSIWYG editor and API-like payloads, you can design and manage email templates outside of your codebase, ensuring cleaner code and easier template maintenance. Unlike older SMTP services, Loops requires the body of emails sent via SMTP to be formatted as an [API-like payload](/smtp#how-it-works). This approach allows you to use Loops' [powerful email editor](/creating-emails/editor) to craft your emails and keep email templating outside of your application code. Every email sent over Loops SMTP requires a transactional email to be set up in your Loops account. Note the `transactionalId` value in the email payload. Here's how you can set up transactional emails with Loops SMTP in Rails: Create transactional emails in Loops using the [editor](/creating-emails/editor). Add [data variables](/transactional/guide#add-data-variables) to your emails for any dynamic content you want to send from your Rails application. ![Add a data variable](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/terminal.png) To use Loops SMTP, configure Action Mailer using the following settings. The `password` value should be an API key from your [API Settings](http://app.loops.so/settings?page=api) page. ```ruby config/environments/production.rb config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.loops.so', port: 587, user_name: 'loops', password: '', authentication: 'plain', enable_starttls: true } ``` Now you can send emails from your application. Here's an example Mailer: ```ruby app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def login_email @user = params[:user] # Note: the "to" address is required by Action Mailer # but is overwritten by the email provided in the view # (see below). Likewise, a subject is not required here # because Loops will use the subject provided in the editor. mail(to: @user.email) end end ``` Loops' SMTP system doesn't send full HTML emails directly. Instead, you should provide a structured API-like payload, which Loops will then use to render an HTML email. Create a text view for your email, like below. You can copy an example payload from the **Publish** page of your transactional email in Loops. ```json app/views/user_mailer/login_email.text.erb { "transactionalId": "clomzp89u635xl30px7wrl0ri", "email": "<%= @user.email %>", /* recipient */ "dataVariables": { "loginUrl": "https://myapp.com/login?code=<%= @user.auth_code %>" } } ``` You do not need to provide an HTML view for your emails when using Loops SMTP (i.e.: `login_email.html.erb` is not required). Read how to send transactional email with our API. Learn how to send transactional email with Loops. # Introduction to transactional email Learn about sending transactional email with Loops. ## About transactional email Transactional emails are automated, API-triggered emails that are sent to individual contacts based on a specific action they have taken. Examples include **confirmation emails**, **password reset emails**, and **purchase confirmations**. Unlike marketing emails (campaigns or loops), transactional emails are not promotional in nature and as a result, they do not require unsubscribe information to be included in the email. Read how to send transactional email with our API. Read our detailed guide for sending transactional emails. ## Transactional vs marketing emails **Marketing emails** are a type of promotional email that are sent to your list of subscribers or customers with the goal of promoting your product, service, or brand. These types of emails can take many forms such as investor updates, product update newsletters, special offers, webinar invitations, customer case studies, and much more. Marketing emails are a 1-to-many communication, meaning that the same exact email that you craft can (and probably will) be sent and read by a number of recipients or customers. **Transactional emails** are automated messages that are triggered by a specific user action. These types of emails can include things like a purchase confirmation or password reset instructions. Transactional emails are a 1-to-1 communication, meaning that the specific email sent will only be read by the single recipient it was sent to. These emails will typically include content that is unique and changing based on who is receiving it. ## Unsubscribe links Marketing email (campaigns and loops) you send with Loops will contain an [unsubscribe link](/creating-emails/editor#footer-content). Transactional emails will not. This key difference is due to legal requirements. You **should not** send marketing emails as transactional emails through Loops. ## Contacts Contacts behave slightly differently between transactional and marketing emails (campaigns and loops). * Your Audience only contains marketing contacts. If a new contact is sent a transactional email, they are not added to your Audience. * Sending a transactional email to a new contact will not trigger the ["Contact added" loop trigger](/loop-builder#triggers). * The "Subscribed" [contact property](/contacts/properties#subscribed) does not affect transactional emails. Unsubscribed contacts will still receive all transactional emails they are sent. # Attachments How to send attachments with your transactional email. Need to send attachments with your transactional email? Just [reach out to us](mailto:help@loops.so) and we can enable the feature on your account. ## Sending attachments Check out [the transactional API documentation](/api-reference/send-transactional-email) for a refresher on the transactional API email payload. To attach a file to a transactional message, you'll need to add a `attachments` key to the standard transaction API email payload. The `attachments` key should be an array of objects, each with the following keys: * `filename` - the name of the file * `contentType` - the MIME type of the file * `data` - the base64 encoded content of the file Here's an example of a transactional email payload with [this ICS file](https://gist.github.com/phil-loops/c0cb5d84d502a3949651934252d306af) attached: ```json { "transactionalId": "*********", "email": "phil+attachments@loops.so", "attachments": [ { "filename": "oil-change-invite.ics", "contentType": "text/calendar", "data": "QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wClBST0RJRDotLy9pY2FsLm1hcnVkb3QuY29tLy9pQ2FsIEV2ZW50IE1ha2VyCkNBTFNDQUxFOkdSRUdPUklBTgpCRUdJTjpWVElNRVpPTkUKVFpJRDpBbWVyaWNhL0xvc19BbmdlbGVzCkxBU1QtTU9ESUZJRUQ6MjAyMzA0MDdUMDUwNzUwWgpUWlVSTDpodHRwczovL3d3dy50enVybC5vcmcvem9uZWluZm8tb3V0bG9vay9BbWVyaWNhL0xvc19BbmdlbGVzClgtTElDLUxPQ0FUSU9OOkFtZXJpY2EvTG9zX0FuZ2VsZXMKQkVHSU46REFZTElHSFQKVFpOQU1FOlBEVApUWk9GRlNFVEZST006LTA4MDAKVFpPRkZTRVRUTzotMDcwMApEVFNUQVJUOjE5NzAwMzA4VDAyMDAwMApSUlVMRTpGUkVRPVlFQVJMWTtCWU1PTlRIPTM7QllEQVk9MlNVCkVORDpEQVlMSUdIVApCRUdJTjpTVEFOREFSRApUWk5BTUU6UFNUClRaT0ZGU0VURlJPTTotMDcwMApUWk9GRlNFVFRPOi0wODAwCkRUU1RBUlQ6MTk3MDExMDFUMDIwMDAwClJSVUxFOkZSRVE9WUVBUkxZO0JZTU9OVEg9MTE7QllEQVk9MVNVCkVORDpTVEFOREFSRApFTkQ6VlRJTUVaT05FCkJFR0lOOlZFVkVOVApEVFNUQU1QOjIwMjMwOTExVDE4NTY0M1oKVUlEOjE2OTQ0NTg1ODM0MzgtOTkyMjJAaWNhbC5tYXJ1ZG90LmNvbQpEVFNUQVJUO1RaSUQ9QW1lcmljYS9Mb3NfQW5nZWxlczoyMDI1MDUwMVQxMjAwMDAKRFRFTkQ7VFpJRD1BbWVyaWNhL0xvc19BbmdlbGVzOjIwMjUwNTAxVDEyMDAwMApTVU1NQVJZOkNoYW5nZSBPaWwKVVJMOmd1bWRyb3AuZXhhbXBsZS5jb20KREVTQ1JJUFRJT046WW91ciBjYXIgaXMgcmVhZHkgZm9yIGFuIG9pbCBjaGFuZ2UhCkxPQ0FUSU9OOkd1bWRyb3AgVmlsbGFnZQpFTkQ6VkVWRU5UCkVORDpWQ0FMRU5EQVI=" } ] } ``` When you send this payload, the recipient will receive an email with the ICS file attached: ![An email with an ICS file attached](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/transactional-attachment-send.png) All the usual [transactional API email payload](/api-reference/send-transactional-email) keys are still required. The `attachments` key is in addition to the standard payload. ## Limitations * The total size of the payload must be less than 4MB * Attachments are not generally available. Please [contact us](mailto:help@loops.so) if you need this feature enabled on your account. # Transactional email guide How to send transactional email with Loops. ## How it works Sending transactional email with Loops has two steps. First, you need to create transactional emails within Loops using our handy email editor. In your email you add data variables, which let you insert custom data into each email you send. To send transactional emails you need to use the Loops API. All it takes is a simple call to our transactional endpoint. Your request needs to include the ID of the transactional email you created, the receipient's email address and the data variables needed for the email. Read on for more details about [creating](#compose-your-email), [editing](#editing-the-email) and [sending transactional emails](#send-your-email). ## Compose your email There are two ways to create emails in Loops: using our editor or import an MJML template. ### Use our editor You can [create emails in the editor](/creating-emails/editor), letting you easily add formatting, images and buttons. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/email.png) You can add dynamic data into your emails by using data variables (like `name` in the example above). Read on for more information about data variables. ### Bring your own MJML You can also [upload your own MJML code](/creating-emails/uploading-custom-email) to use in the email. This is useful if you have a pre-existing template you want to use. Like emails created in our editor, you can add data variables to MJML templates. To add a data variable called `PasswordResetLink`, you can use it in your MJML like this: ```html {DATA_VARIABLE:PasswordResetLink} ``` Note the uppercase “DATA\_VARIABLE” and the colon before the variable name. ## Add data variables Data variables let you insert dynamic values into each transactional email you send. Once you have added data variables into your email, you specify the value of each data variable in the API call to send each transactional email. Let's say you have a password reset email that is sent once a user clicks a "Reset Password" button in your application. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/email.png) For this we add two data variables to the email: `name` and `resetUrl`. We do not currently offer optional or conditional data variables. Using an empty string "" will also cause the email to fail. You can insert data variables into the text of the email with the **Insert data variable** button: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/terminal.png) You can also add data variables as links (on text, images and buttons). This is how we add `resetUrl` as the button's link in our example: ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/passwordresetlink.png) If you want to write the tags manually in your content, you can use our [dynamic tag syntax](/creating-emails/personalizing-emails#dynamic-tag-syntax). To add a reset URL you can write a tag like this: ``` {DATA_VARIABLE:resetUrl} ``` Data variables are also available in the email sending settings fields, like **From**, **Reply** and **Subject**. Click the `>` button to view all fields and then click the data variable icon. To send data to these fields, simply include the data variables in your API call as normal. ![Adding a data variable in the Subject field](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/data-variables-in-settings.png) Read more about adding dynamic content into your emails. ### Important information about data variables * Make sure that when you [send a transactional email using the API](/api-reference/send-transactional-email) that you include *all data variables* in your request. If you do not include the correct set of data variables in your API call, the send will fail. * We do not currently offer optional or conditional data variables. Using an empty string `""` will also cause the email to fail. * Data variable names are case-sensitive (meaning `LastLoggedIn` and `lastLoggedIn` are different variables). * Data variable names can only contain: * letters * numbers * underscores * dashes * Data variable values sent over the API can be `string` or `number`. ## Review your email On the next page, after clicking **Next**, you'll see the API Details section. This contains the data variables used in the email as well as a sample payload for reference. The “Transactional ID” lets you distinguish between different transactional emails when calling the API and is required. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/next.png) ## Publish the email To enable sending the email, it needs to be published by clicking **Publish**. ## Send your email In the example above, two Data Variables were used in the email and there is a transactional ID needed as part of our API call. Any data variables created in the email are required when making the API request. Here's an example of the request for sending this email: **Send a POST to this endpoint (make sure to authenticate)** ``` https://app.loops.so/api/v1/transactional ``` **Payload** You can copy an email's example payload from its Publish page in Loops by clicking the **Show payload** button. ```json { "transactionalId": "clfq6dinn000yl70fgwwyp82l", "email": "favorite@example.com", "dataVariables": { "name": "Chris", "passwordResetLink": "https://example.com/reset-password" } } ``` You can add contacts to your audience from this call by adding `"addToAudience": true` to your payload. Read how to send transactional email with our API. Integrate our SDK into your JS or TS application. ## Editing the email To edit the email, click **Edit Draft** on the Compose page. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/duplicate.png) The previous published version of the email will remain, and will continue sending until you republish the email. This means you can make changes to transactional emails without disrupting ongoing email sending. When you have a draft, we retain both versions and you can switch between your draft and the published version using the toggles in the top left. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/view-draft.png) To publish your changes, simply click **Republish** and click the confirmation. Your draft will seamlessly become the published version. ## Metrics After emails are sent, details are shown in the email's Metrics page. These include send time and if messages experienced any issues with delivery (bounces or spam complaints). Note that open and click tracking is disabled for transactional messages to improve deliverability for infrastructure-level communications. ## Testing transactional emails You can test your transactional email integration by sending to email addresses with `@example.com` and `@test.com` domains (for example `user1@example.com` and `user2@example.com`). Everything will work as normal (e.g. you will receive success responses from the API), but no emails will be sent to `@example.com` or `@test.com` email addresses, making this a good way to test transactional emails without affecting your sending domain’s reputation. ## Errors ### Links look like `x-webdoc://....` This is a known issue with Apple Mail. Make sure that your links start with `https://` or `http://` and they should work fine. ### API (400-level error) The first place to start is to check the body of the response. It will contain a JSON object with a `message` property that will give you more information about the error. Here are common reasons that the API might return with a 400-level error: * Using the API without a [verified domain](/sending-domain). * Trying to use the API for a transactional email without a published email message. * Missing a required parameter: `transactionalId`, `email`, or `dataVariables`. * Missing a data variable that is required by the email message. # Types of emails Learn about the three types of emails that you can send with Loops: Campaigns, Loops, and Transactional. ![](https://mintlify.s3-us-west-1.amazonaws.com/loops/images/create-first-email.png) ## Campaigns A Campaign is the right type of email for a one-off send to your audience or a segment of your audience. **Examples:** * Newsletters * Investor updates * Product updates * User feedback requests **How to send a Campaign:** 1. Hit “Create” in the top right corner of your Loops dashboard. 2. Select “Campaign” in the popup module. 3. Enter the subject line, preview text, and content of the email. 4. Select your audience segment. 5. Schedule the email to send later or send it immediately. ## Loops A Loop is an email that is triggered by an event, a contact being added to your audience, or a contact property update. **Examples:** * Welcome emails * User onboarding sequences * User check-ins For more information on sending your first Loop, [visit this guide](https://loops.so/docs/loop-builder). ## Transactional A Transactional email is an automated message that is triggered by a specific contact action. **Examples:** * Password resets * Purchase or upgrade confirmations * Shipping information * Account cancellation emails For more information on sending your first Transactional messages, [visit this guide](https://loops.so/docs/transactional/guide). Are you stuck and wondering exactly how you should be setting up your email flows within Loops? Email [chris@loops.so](mailto:chris@loops.so) and we will help get you set up. **Start using these features today to enhance your email communication strategy!**