# Reloop Documentation (full) > Full-document snapshot of Reloop docs for long-context agents. > Hosted on the marketing web app (source of truth for agent files). > Curated docs index: https://reloop.sh/llms-docs.txt > Site index: https://reloop.sh/llms.txt > Product skill: https://reloop.sh/skill.md > Prefer per-page markdown: append `.md` under https://reloop.sh/docs/ Generated from 382 source files. --- # introduction.mdx Source: https://reloop.sh/docs Markdown: https://reloop.sh/docs/introduction.md To get started with Reloop, you'll need: 1. Your own domain, [verified with Reloop](/docs/learn/domain) 2. A Reloop [API key](/docs/learn/api-keys) Then, you'll be ready to send [transactional emails](/docs/learn/emails): - **Transactional emails:** personalized, event-driven communication - **SMTP relay:** deliver through any standard SMTP client ## Quickstart Learn how to get Reloop set up in your project. ### Node.js ### Python ### Go ### Ruby ### PHP ### Rust ### SMTP --- # learn/ai/api-keys.curl.md Source: https://reloop.sh/docs/learn/ai/api-keys.curl Markdown: https://reloop.sh/docs/learn/ai/api-keys.curl.md # API Keys (cURL) > Agent-optimized samples for managing Reloop API keys in cURL. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```bash curl -X POST https://reloop.sh/api/api-key/v1/ \ -H "x-api-key: rl_123456789" \ -H "Content-Type: application/json" \ -d '{"name": "Production Key"}' ``` ## List API keys `GET /api/api-key/v1/` ```bash curl "https://reloop.sh/api/api-key/v1/?page=1&limit=10" \ -H "x-api-key: rl_123456789" ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```bash curl "https://reloop.sh/api/api-key/v1/key_123456789" \ -H "x-api-key: rl_123456789" ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```bash curl -X PATCH https://reloop.sh/api/api-key/v1/key_123456789 \ -H "x-api-key: rl_123456789" \ -H "Content-Type: application/json" \ -d '{"name": "Updated Key Name"}' ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```bash curl -X POST https://reloop.sh/api/api-key/v1/rotate/key_123456789 \ -H "x-api-key: rl_123456789" ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```bash curl -X POST https://reloop.sh/api/api-key/v1/disable/key_123456789 \ -H "x-api-key: rl_123456789" ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```bash curl -X POST https://reloop.sh/api/api-key/v1/enable/key_123456789 \ -H "x-api-key: rl_123456789" ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```bash curl -X DELETE https://reloop.sh/api/api-key/v1/key_123456789 \ -H "x-api-key: rl_123456789" ``` --- # learn/ai/api-keys.dotnet.md Source: https://reloop.sh/docs/learn/ai/api-keys.dotnet Markdown: https://reloop.sh/docs/learn/ai/api-keys.dotnet.md # API Keys (.NET) > Agent-optimized samples for managing Reloop API keys in .NET. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); var apiKey = await reloop.ApiKeys.CreateAsync(new CreateApiKeyParams(Name: "Production Key")); ``` ## List API keys `GET /api/api-key/v1/` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); var apiKeys = await reloop.ApiKeys.ListAsync(new ApiKeyListParams { Page = 1, Limit = 10, }); ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); await reloop.ApiKeys.GetAsync("key_123456789"); ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); var apiKey = await reloop.ApiKeys.UpdateAsync("key_123456789", new UpdateApiKeyParams(Name: "Updated Key Name")); ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); await reloop.ApiKeys.RotateAsync("key_123456789"); ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); await reloop.ApiKeys.DisableAsync("key_123456789"); ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); await reloop.ApiKeys.EnableAsync("key_123456789"); ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```csharp using Reloop; using Reloop.Models; var reloop = new ReloopClient("rl_123456789"); await reloop.ApiKeys.DeleteAsync("key_123456789"); ``` --- # learn/ai/api-keys.go.md Source: https://reloop.sh/docs/learn/ai/api-keys.go Markdown: https://reloop.sh/docs/learn/ai/api-keys.go.md # API Keys (Go) > Agent-optimized samples for managing Reloop API keys in Go. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) apiKey, _ := client.ApiKeys.Create(reloop.CreateApiKeyParams{ Name: "Production Key", }) ``` ## List API keys `GET /api/api-key/v1/` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) apiKeys, _ := client.ApiKeys.List(&reloop.ApiKeyListParams{ Page: reloop.Int(1), Limit: reloop.Int(10), }) ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) _, _ = client.ApiKeys.Get("key_123456789") ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) apiKey, _ := client.ApiKeys.Update("key_123456789", reloop.UpdateApiKeyParams{ Name: "Updated Key Name", }) ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) _, _ = client.ApiKeys.Rotate("key_123456789") ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) _, _ = client.ApiKeys.Disable("key_123456789") ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) _, _ = client.ApiKeys.Enable("key_123456789") ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```go client, _ := reloop.NewClient(reloop.ClientOptions{ APIKey: "rl_123456789", }) _, _ = client.ApiKeys.Delete("key_123456789") ``` --- # learn/ai/api-keys.java.md Source: https://reloop.sh/docs/learn/ai/api-keys.java Markdown: https://reloop.sh/docs/learn/ai/api-keys.java.md # API Keys (Java) > Agent-optimized samples for managing Reloop API keys in Java. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); CreateApiKeyParams params = new CreateApiKeyParams(); params.name = "Production Key"; var apiKey = reloop.apiKey.create(params); System.out.println(apiKey.id + " " + apiKey.key); ``` ## List API keys `GET /api/api-key/v1/` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); ApiKeyListParams params = new ApiKeyListParams(); params.page = 1; params.limit = 10; params.enabled = true; var apiKeys = reloop.apiKey.list(params); System.out.println(apiKeys.total + " " + apiKeys.apiKeys); ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); var apiKey = reloop.apiKey.get("key_123456789"); System.out.println(apiKey.id + " " + apiKey.name + " " + apiKey.enabled); ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); UpdateApiKeyParams params = new UpdateApiKeyParams(); params.name = "Updated Key Name"; var apiKey = reloop.apiKey.update("key_123456789", params); System.out.println(apiKey.id + " " + apiKey.name); ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); var apiKey = reloop.apiKey.rotate("key_123456789"); System.out.println(apiKey.id + " " + apiKey.key); ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); var apiKey = reloop.apiKey.disable("key_123456789"); System.out.println(apiKey.id + " " + apiKey.enabled); ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); var apiKey = reloop.apiKey.enable("key_123456789"); System.out.println(apiKey.id + " " + apiKey.enabled); ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```java ReloopClient reloop = new ReloopClient("rl_123456789"); var apiKey = reloop.apiKey.delete("key_123456789"); System.out.println(apiKey.id + " " + apiKey.message); ``` --- # learn/ai/api-keys.md Source: https://reloop.sh/docs/learn/ai/api-keys Markdown: https://reloop.sh/docs/learn/ai/api-keys.md # API Keys (agent guide) > Prefer this guide over the human dashboard page at `/docs/learn/api-keys`. Use a language-specific file below for runnable SDK samples. ## Auth - Send `x-api-key: ` on every request. - Secrets are prefixed with `rl_`. - Store secrets in env vars / a secret manager. Never commit them. - The full secret is returned **once** on create and rotate. Reloop stores a hash; it cannot be retrieved again. ## Rules - **Disable** pauses the key (requests return 401); you can re-enable later. - **Delete** permanently revokes the key; irreversible. - API keys work for REST (`x-api-key`) and SMTP (password = secret). - Creating or managing keys requires an existing authenticated API key. ## Endpoints | Action | Method | Path | |--------|--------|------| | Create | POST | /api/api-key/v1/ | | List | GET | /api/api-key/v1/ | | Get | GET | /api/api-key/v1/:api_key_id | | Update | PATCH | /api/api-key/v1/:api_key_id | | Rotate | POST | /api/api-key/v1/rotate/:api_key_id | | Disable | POST | /api/api-key/v1/disable/:api_key_id | | Enable | POST | /api/api-key/v1/enable/:api_key_id | | Delete | DELETE | /api/api-key/v1/:api_key_id | Base URL: `https://reloop.sh` ## Language guides - [Node](./api-keys.node.md) - [cURL](./api-keys.curl.md) - [Python](./api-keys.python.md) - [PHP](./api-keys.php.md) - [Java](./api-keys.java.md) - [.NET](./api-keys.dotnet.md) - [Go](./api-keys.go.md) - [Rust](./api-keys.rust.md) - [Ruby](./api-keys.ruby.md) ## Dashboard (humans) Dashboard UI walkthrough (GIFs, tabs): [/docs/learn/api-keys](https://reloop.sh/docs/learn/api-keys) --- # learn/ai/api-keys.node.md Source: https://reloop.sh/docs/learn/ai/api-keys.node Markdown: https://reloop.sh/docs/learn/ai/api-keys.node.md # API Keys (Node) > Agent-optimized samples for managing Reloop API keys in Node. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.create({ name: "Production Key", }); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.key); ``` ## List API keys `GET /api/api-key/v1/` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKeys, apiKeyError } = await reloop.apiKey.list({ page: 1, limit: 10, enabled: true, }); if (apiKeyError) throw apiKeyError; console.log(apiKeys.total, apiKeys.apiKeys); ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.get("key_123456789"); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.name, apiKey.enabled); ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.update("key_123456789", { name: "Updated Key Name", }); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.name); ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.rotate("key_123456789"); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.key); ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.disable("key_123456789"); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.enabled); ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.enable("key_123456789"); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.enabled); ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```javascript const reloop = new Reloop({ apiKey: "rl_123456789" }); const { apiKey, apiKeyError } = await reloop.apiKey.delete("key_123456789"); if (apiKeyError) throw apiKeyError; console.log(apiKey.id, apiKey.message); ``` --- # learn/ai/api-keys.php.md Source: https://reloop.sh/docs/learn/ai/api-keys.php Markdown: https://reloop.sh/docs/learn/ai/api-keys.php.md # API Keys (PHP) > Agent-optimized samples for managing Reloop API keys in PHP. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```php apiKey->create([ 'name' => 'Production Key', ]); echo $apiKey['id'] . ' ' . $apiKey['key'] . PHP_EOL; ``` ## List API keys `GET /api/api-key/v1/` ```php apiKey->list([ 'page' => 1, 'limit' => 10, 'enabled' => true, ]); echo $apiKeys['total'] . ' ' . $apiKeys['apiKeys'] . PHP_EOL; ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```php apiKey->get('key_123456789'); echo $apiKey['id'] . ' ' . $apiKey['name'] . ' ' . $apiKey['enabled'] . PHP_EOL; ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```php apiKey->update('key_123456789', [ 'name' => 'Updated Key Name', ]); echo $apiKey['id'] . ' ' . $apiKey['name'] . PHP_EOL; ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```php apiKey->rotate('key_123456789'); echo $apiKey['id'] . ' ' . $apiKey['key'] . PHP_EOL; ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```php apiKey->disable('key_123456789'); echo $apiKey['id'] . ' ' . $apiKey['enabled'] . PHP_EOL; ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```php apiKey->enable('key_123456789'); echo $apiKey['id'] . ' ' . $apiKey['enabled'] . PHP_EOL; ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```php apiKey->delete('key_123456789'); echo $apiKey['id'] . ' ' . $apiKey['message'] . PHP_EOL; ``` --- # learn/ai/api-keys.python.md Source: https://reloop.sh/docs/learn/ai/api-keys.python Markdown: https://reloop.sh/docs/learn/ai/api-keys.python.md # API Keys (Python) > Agent-optimized samples for managing Reloop API keys in Python. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.create({ "name": "Production Key", }) if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["key"]) ``` ## List API keys `GET /api/api-key/v1/` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.list({ "page": 1, "limit": 10, "enabled": True, }) if result.api_key_error: raise result.api_key_error print(result.api_keys["total"], result.api_keys["apiKeys"]) ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.get("key_123456789") if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["name"], result.api_key["enabled"]) ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.update("key_123456789", { "name": "Updated Key Name", }) if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["name"]) ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.rotate("key_123456789") if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["key"]) ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.disable("key_123456789") if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["enabled"]) ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.enable("key_123456789") if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["enabled"]) ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```python from reloop_email import Reloop reloop = Reloop(api_key="rl_123456789") result = reloop.api_key.delete("key_123456789") if result.api_key_error: raise result.api_key_error print(result.api_key["id"], result.api_key["message"]) ``` --- # learn/ai/api-keys.ruby.md Source: https://reloop.sh/docs/learn/ai/api-keys.ruby Markdown: https://reloop.sh/docs/learn/ai/api-keys.ruby.md # API Keys (Ruby) > Agent-optimized samples for managing Reloop API keys in Ruby. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") api_key = reloop.api_keys.create(name: "Production Key") ``` ## List API keys `GET /api/api-key/v1/` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") api_keys = reloop.api_keys.list(page: 1, limit: 10) ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") reloop.api_keys.get("key_123456789") ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") api_key = reloop.api_keys.update("key_123456789", name: "Updated Key Name") ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") reloop.api_keys.rotate("key_123456789") ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") reloop.api_keys.disable("key_123456789") ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") reloop.api_keys.enable("key_123456789") ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```ruby require "reloop" reloop = Reloop::Client.new(api_key: "rl_123456789") reloop.api_keys.delete("key_123456789") ``` --- # learn/ai/api-keys.rust.md Source: https://reloop.sh/docs/learn/ai/api-keys.rust Markdown: https://reloop.sh/docs/learn/ai/api-keys.rust.md # API Keys (Rust) > Agent-optimized samples for managing Reloop API keys in Rust. Index: [api-keys.md](./api-keys.md) ## Auth reminder - Header: `x-api-key: rl_...` - Secret shown once on create/rotate ## Create API key `POST /api/api-key/v1/` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().create(CreateApiKeyParams { name: "Production Key".to_string(), }).await?; Ok(()) } ``` ## List API keys `GET /api/api-key/v1/` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().list(Some(ApiKeyListParams { page: Some(1), limit: Some(10), ..Default::default() })).await?; Ok(()) } ``` ## Get API key `GET /api/api-key/v1/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().get("key_123456789").await?; Ok(()) } ``` ## Update API key `PATCH /api/api-key/v1/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().update("key_123456789", UpdateApiKeyParams { name: "Updated Key Name".to_string(), }).await?; Ok(()) } ``` ## Rotate API key `POST /api/api-key/v1/rotate/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().rotate("key_123456789").await?; Ok(()) } ``` ## Disable API key `POST /api/api-key/v1/disable/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().disable("key_123456789").await?; Ok(()) } ``` ## Enable API key `POST /api/api-key/v1/enable/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().enable("key_123456789").await?; Ok(()) } ``` ## Delete API key `DELETE /api/api-key/v1/:api_key_id` ```rust use reloop::ReloopClient; #[tokio::main] async fn main() -> Result<(), Box> { let reloop = ReloopClient::new("rl_123456789".to_string(), None); reloop.api_keys().delete("key_123456789").await?; Ok(()) } ``` --- # learn/api-keys.mdx Source: https://reloop.sh/docs/learn/api-keys Markdown: https://reloop.sh/docs/learn/api-keys.md ## View all API Keys The [API Keys Dashboard](https://reloop.sh/dashboard/api-keys) shows you all the API Keys you have created along with their details, including the last time you used an API Key. ![API Keys list in the Reloop dashboard](/docs/images/docs/api-keys/list-api-key.png) ## Create an API key 1. Go to [API Keys](https://reloop.sh/dashboard/api-keys) 2. Click **Create API key** (or press `C`) 3. Enter a descriptive name (e.g., `Production Server` or `Staging Worker`) 4. Click **Create API Key** 5. Copy the secret key, then click **Cancel** Copy the secret key immediately upon creation. Reloop stores a secure hash, so the full secret can never be retrieved again after closing the dialog. ## View all API Keys Fetch a paginated list of API keys in your organization. ## Create an API key Use an existing API key to programmatically create new keys for services or team members. Copy the secret key immediately upon creation. Reloop stores a secure hash, so the full secret can never be retrieved again. ## Edit an API key ## Rotate an API key ## Disable or enable an API key ## Delete an API key ## FAQ No. Reloop displays the full secret key string only once when created or rotated. After closing the modal, only the prefix (e.g. `rl_prod_17aQUCC-e`) is visible. Reloop stores a secure hash, so if a secret is lost, you must rotate the key or create a new one. **Disabling** pauses access temporarily. Requests using the key fail with `401 Unauthorized`, but you can re-enable it later. **Deleting** permanently removes the key record and cannot be undone. Yes. Reloop API keys work across both REST API endpoints (as the `x-api-key` header) and SMTP authentication (using the API key prefix/secret as SMTP credentials). Reloop does not enforce a hard limit on total API keys per organization. However, we recommend maintaining clean key hygiene by deleting stale or unused keys. --- # api/api-key/delete-api-api-key-v1by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/delete-api-api-key-v1by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/delete-api-api-key-v1by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/get-api-api-key-v1.mdx Source: https://reloop.sh/docs/api/api-key/get-api-api-key-v1 Markdown: https://reloop.sh/docs/api/api-key/get-api-api-key-v1.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/get-api-api-key-v1by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/get-api-api-key-v1by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/get-api-api-key-v1by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/patch-api-api-key-v1by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/patch-api-api-key-v1by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/patch-api-api-key-v1by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/post-api-api-key-v1.mdx Source: https://reloop.sh/docs/api/api-key/post-api-api-key-v1 Markdown: https://reloop.sh/docs/api/api-key/post-api-api-key-v1.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/post-api-api-key-v1disable-by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/post-api-api-key-v1disable-by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/post-api-api-key-v1disable-by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/post-api-api-key-v1enable-by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/post-api-api-key-v1enable-by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/post-api-api-key-v1enable-by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/api-key/post-api-api-key-v1rotate-by-api_key_id.mdx Source: https://reloop.sh/docs/api/api-key/post-api-api-key-v1rotate-by-api_key_id Markdown: https://reloop.sh/docs/api/api-key/post-api-api-key-v1rotate-by-api_key_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/channels/delete-api-contacts-v1channels-by-channel_id.mdx Source: https://reloop.sh/docs/api/contacts/channels/delete-api-contacts-v1channels-by-channel_id Markdown: https://reloop.sh/docs/api/contacts/channels/delete-api-contacts-v1channels-by-channel_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/channels/get-api-contacts-v1channels-by-channel_id.mdx Source: https://reloop.sh/docs/api/contacts/channels/get-api-contacts-v1channels-by-channel_id Markdown: https://reloop.sh/docs/api/contacts/channels/get-api-contacts-v1channels-by-channel_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/channels/get-api-contacts-v1channels-list.mdx Source: https://reloop.sh/docs/api/contacts/channels/get-api-contacts-v1channels-list Markdown: https://reloop.sh/docs/api/contacts/channels/get-api-contacts-v1channels-list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/channels/patch-api-contacts-v1channels-by-channel_id.mdx Source: https://reloop.sh/docs/api/contacts/channels/patch-api-contacts-v1channels-by-channel_id Markdown: https://reloop.sh/docs/api/contacts/channels/patch-api-contacts-v1channels-by-channel_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/channels/post-api-contacts-v1channels-create.mdx Source: https://reloop.sh/docs/api/contacts/channels/post-api-contacts-v1channels-create Markdown: https://reloop.sh/docs/api/contacts/channels/post-api-contacts-v1channels-create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/contact-properties/delete-api-contacts-v1properties-by-contact_property_id.mdx Source: https://reloop.sh/docs/api/contacts/contact-properties/delete-api-contacts-v1properties-by-contact_property_id Markdown: https://reloop.sh/docs/api/contacts/contact-properties/delete-api-contacts-v1properties-by-contact_property_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/contact-properties/get-api-contacts-v1properties-list.mdx Source: https://reloop.sh/docs/api/contacts/contact-properties/get-api-contacts-v1properties-list Markdown: https://reloop.sh/docs/api/contacts/contact-properties/get-api-contacts-v1properties-list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/contact-properties/patch-api-contacts-v1properties-by-contact_property_id.mdx Source: https://reloop.sh/docs/api/contacts/contact-properties/patch-api-contacts-v1properties-by-contact_property_id Markdown: https://reloop.sh/docs/api/contacts/contact-properties/patch-api-contacts-v1properties-by-contact_property_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/contact-properties/post-api-contacts-v1properties-create.mdx Source: https://reloop.sh/docs/api/contacts/contact-properties/post-api-contacts-v1properties-create Markdown: https://reloop.sh/docs/api/contacts/contact-properties/post-api-contacts-v1properties-create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/delete-api-contacts-v1by-contact_id.mdx Source: https://reloop.sh/docs/api/contacts/delete-api-contacts-v1by-contact_id Markdown: https://reloop.sh/docs/api/contacts/delete-api-contacts-v1by-contact_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/delete-api-contacts-v1group-by-group_id.mdx Source: https://reloop.sh/docs/api/contacts/delete-api-contacts-v1group-by-group_id Markdown: https://reloop.sh/docs/api/contacts/delete-api-contacts-v1group-by-group_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1export-download.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1export-download Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1export-download.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1export.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1export Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1export.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1exports-by-export_id.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1exports-by-export_id Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1exports-by-export_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1list.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1list Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1retrieve-by-contact_id.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1retrieve-by-contact_id Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1retrieve-by-contact_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/get-api-contacts-v1stats-subscription-activity.mdx Source: https://reloop.sh/docs/api/contacts/get-api-contacts-v1stats-subscription-activity Markdown: https://reloop.sh/docs/api/contacts/get-api-contacts-v1stats-subscription-activity.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/delete-api-contacts-v1groups-by-group_id.mdx Source: https://reloop.sh/docs/api/contacts/groups/delete-api-contacts-v1groups-by-group_id Markdown: https://reloop.sh/docs/api/contacts/groups/delete-api-contacts-v1groups-by-group_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/get-api-contacts-v1groups-by-group_id-contacts.mdx Source: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-by-group_id-contacts Markdown: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-by-group_id-contacts.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/get-api-contacts-v1groups-by-group_id.mdx Source: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-by-group_id Markdown: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-by-group_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/get-api-contacts-v1groups-list.mdx Source: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-list Markdown: https://reloop.sh/docs/api/contacts/groups/get-api-contacts-v1groups-list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/patch-api-contacts-v1groups-by-group_id.mdx Source: https://reloop.sh/docs/api/contacts/groups/patch-api-contacts-v1groups-by-group_id Markdown: https://reloop.sh/docs/api/contacts/groups/patch-api-contacts-v1groups-by-group_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/groups/post-api-contacts-v1groups-create.mdx Source: https://reloop.sh/docs/api/contacts/groups/post-api-contacts-v1groups-create Markdown: https://reloop.sh/docs/api/contacts/groups/post-api-contacts-v1groups-create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/patch-api-contacts-v1by-contact_id.mdx Source: https://reloop.sh/docs/api/contacts/patch-api-contacts-v1by-contact_id Markdown: https://reloop.sh/docs/api/contacts/patch-api-contacts-v1by-contact_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/patch-api-contacts-v1channel-by-channel_id.mdx Source: https://reloop.sh/docs/api/contacts/patch-api-contacts-v1channel-by-channel_id Markdown: https://reloop.sh/docs/api/contacts/patch-api-contacts-v1channel-by-channel_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/post-api-contacts-v1channel-by-channel_id.mdx Source: https://reloop.sh/docs/api/contacts/post-api-contacts-v1channel-by-channel_id Markdown: https://reloop.sh/docs/api/contacts/post-api-contacts-v1channel-by-channel_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/post-api-contacts-v1create.mdx Source: https://reloop.sh/docs/api/contacts/post-api-contacts-v1create Markdown: https://reloop.sh/docs/api/contacts/post-api-contacts-v1create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/post-api-contacts-v1export-email.mdx Source: https://reloop.sh/docs/api/contacts/post-api-contacts-v1export-email Markdown: https://reloop.sh/docs/api/contacts/post-api-contacts-v1export-email.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/post-api-contacts-v1group-by-group_id.mdx Source: https://reloop.sh/docs/api/contacts/post-api-contacts-v1group-by-group_id Markdown: https://reloop.sh/docs/api/contacts/post-api-contacts-v1group-by-group_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/contacts/post-api-contacts-v1suppressions.mdx Source: https://reloop.sh/docs/api/contacts/post-api-contacts-v1suppressions Markdown: https://reloop.sh/docs/api/contacts/post-api-contacts-v1suppressions.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/delete-api-domain-v1by-domain_id.mdx Source: https://reloop.sh/docs/api/domain/delete-api-domain-v1by-domain_id Markdown: https://reloop.sh/docs/api/domain/delete-api-domain-v1by-domain_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/get-api-domain-v1by-domain_id.mdx Source: https://reloop.sh/docs/api/domain/get-api-domain-v1by-domain_id Markdown: https://reloop.sh/docs/api/domain/get-api-domain-v1by-domain_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/get-api-domain-v1list.mdx Source: https://reloop.sh/docs/api/domain/get-api-domain-v1list Markdown: https://reloop.sh/docs/api/domain/get-api-domain-v1list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/get-api-domain-v1nameservers-by-domain_id.mdx Source: https://reloop.sh/docs/api/domain/get-api-domain-v1nameservers-by-domain_id Markdown: https://reloop.sh/docs/api/domain/get-api-domain-v1nameservers-by-domain_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/patch-api-domain-v1by-domain_id.mdx Source: https://reloop.sh/docs/api/domain/patch-api-domain-v1by-domain_id Markdown: https://reloop.sh/docs/api/domain/patch-api-domain-v1by-domain_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/post-api-domain-v1create.mdx Source: https://reloop.sh/docs/api/domain/post-api-domain-v1create Markdown: https://reloop.sh/docs/api/domain/post-api-domain-v1create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/post-api-domain-v1verify-by-domain_id-forward-dns.mdx Source: https://reloop.sh/docs/api/domain/post-api-domain-v1verify-by-domain_id-forward-dns Markdown: https://reloop.sh/docs/api/domain/post-api-domain-v1verify-by-domain_id-forward-dns.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/domain/post-api-domain-v1verify-by-domain_id.mdx Source: https://reloop.sh/docs/api/domain/post-api-domain-v1verify-by-domain_id Markdown: https://reloop.sh/docs/api/domain/post-api-domain-v1verify-by-domain_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/errors.mdx Source: https://reloop.sh/docs/api/errors Markdown: https://reloop.sh/docs/api/errors.md Reloop uses standard HTTP response codes to indicate the success or failure of an API request. Every error response includes structured fields so you can programmatically handle failures without parsing human-readable messages. ## Error Response Format Every error response is a JSON object matching the `Error` schema in the [OpenAPI document](https://reloop.sh/openapi.json): ```json { "code": "not_found", "message": "Domain not found", "why": "The domain acme.com was not found or is not authorized for your organization", "fix": "Ensure the domain is registered and verified in your dashboard", "link": "https://reloop.sh/docs/api/errors" } ``` | Field | Type | Description | |-------|------|-------------| | `code` | `string` | A stable, machine-readable error code. Branch on this, not on `message`. Always present. | | `message` | `string` | A short summary of what went wrong (e.g., `"Domain not found"`). Always present. | | `why` | `string` | A detailed explanation of what triggered the error, often including the offending value. | | `fix` | `string` | Actionable steps to resolve the issue. | | `link` | `string` | A URL to the relevant documentation page. Always present. | | `errors` | `array` | On `validation_error` only: one `{ "path", "message" }` entry per invalid field, where `path` is a JSON Pointer such as `/email`. | ## Error Codes | `code` | Status | Meaning | |--------|--------|---------| | `bad_request` | `400` | The request was invalid or its body could not be parsed. | | `unauthorized` | `401` | The API key or token is missing, invalid or revoked. | | `payment_required` | `402` | Your plan or credits do not cover this request. | | `forbidden` | `403` | You are authenticated but not allowed to do this. | | `not_found` | `404` | The resource or endpoint does not exist. | | `conflict` | `409` | The request conflicts with an existing resource. | | `payload_too_large` | `413` | The request body or an attachment is too large. | | `validation_error` | `422` | The request did not match the endpoint's schema. See `errors`. | | `rate_limited` | `429` | You exceeded a [rate limit](/docs/api/usage-limits). | | `internal_error` | `500` | Something went wrong on Reloop's side. | | `service_unavailable` | `503` | A dependency is temporarily unavailable. Retry later. | | `invalid_idempotency_key` | `400` | The `Idempotency-Key` header is too long. See [Idempotency](/docs/api/idempotency). | | `idempotency_in_progress` | `409` | A request with the same `Idempotency-Key` is still running. | | `idempotency_key_reused` | `422` | The `Idempotency-Key` was used before with a different request. | New codes may be added. Treat an unknown `code` by its HTTP status. ## Error Codes Reference ### 400 Bad Request The request was invalid. This usually means a required parameter is missing, a value is the wrong type, or an input failed validation. **Common causes:** - Missing required fields in the request body. - A DNS health check failed because records are not configured correctly. - An email body was missing both `html` and `text` content, and the referenced template had no rendered HTML. ```json { "code": "bad_request", "message": "DNS health check failed", "why": "Domain acme.com is missing required DNS records: SPF, DKIM", "fix": "Update your DNS configuration with the required SPF, DKIM, and DMARC records" } ``` --- ### 401 Unauthorized The API key is missing, invalid, or has been revoked. **Common causes:** - The `x-api-key` header was not included in the request. - The API key has been disabled or rotated. - The key format is incorrect (valid keys start with `rl_prod_`). ```json { "code": "unauthorized", "message": "Unauthorized", "why": "No valid API key was provided in the x-api-key header", "fix": "Please provide valid credentials" } ``` --- ### 403 Forbidden The API key is valid, but it doesn't have permission to access the requested resource. **Common causes:** - Attempting to access a resource that belongs to a different organization. - The authenticated user is not a member of the organization tied to the API key. ```json { "code": "forbidden", "message": "Forbidden", "why": "User is not a member of an organization", "fix": "Ensure your API key is associated with the correct organization" } ``` --- ### 404 Not Found The requested resource doesn't exist or isn't accessible to your organization. **Common causes:** - The resource ID is incorrect or was deleted. - The resource belongs to a different organization than your API key. ```json { "code": "not_found", "message": "Domain not found", "why": "The domain acme.com was not found or is not authorized for your organization", "fix": "Ensure the domain is registered and verified in your dashboard" } ``` --- ### 409 Conflict The request conflicts with an existing resource. **Common causes:** - Creating a domain that is already registered. - Creating a channel or contact property with a name that already exists in your organization. ```json { "code": "conflict", "message": "Channel already exists", "why": "A channel with the name 'Weekly Newsletter' already exists in your organization", "fix": "Use a unique channel name or update the existing channel" } ``` --- ### 422 Validation Error The request body, query or path parameters did not match the endpoint's schema. `errors` lists each invalid field. ```json { "code": "validation_error", "message": "Invalid request", "why": "The body did not match the schema for this endpoint.", "fix": "Fix the fields listed in `errors` and retry. Schemas are in the OpenAPI document at https://reloop.sh/openapi.json.", "link": "https://reloop.sh/docs/api/errors", "errors": [ { "path": "/to", "message": "Expected string" } ] } ``` --- ### 429 Too Many Requests You've exceeded a rate limit. The response includes additional fields and headers to help you retry. ```json { "code": "rate_limited", "message": "Too many requests", "why": "You have exceeded the limit of 10 requests per 60s window for this operation.", "fix": "Wait 42 second(s) before retrying.", "retryAfter": 42 } ``` Check the `Retry-After` response header for the number of seconds to wait, or use the `retryAfter` field in the body. See [Usage Limits](/docs/api/usage-limits) for per-endpoint limits. --- ### 500 Internal Server Error Something went wrong on Reloop's end. These errors are automatically logged and investigated. **What to do:** - Retry the request after a short delay. - If the issue persists, contact [support@reloop.sh](mailto:support@reloop.sh) with the request details and timestamp. --- # api/idempotency.mdx Source: https://reloop.sh/docs/api/idempotency Markdown: https://reloop.sh/docs/api/idempotency.md Network failures and timeouts leave you unsure whether a request went through. Send an `Idempotency-Key` header on `POST` requests and you can retry them without sending the same email twice or creating duplicate resources. ## How it works Generate a unique key for each operation, such as a UUID, and send it with the request. If you retry with the same key and the same body, Reloop returns the original response instead of running the request again. ```bash curl -X POST "https://reloop.sh/api/contacts/create" \ -H "x-api-key: rl_prod_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 5f1c2a9e-8d3b-4c7a-9f60-2b7e1d4a8c35" \ -d '{"email": "ada@example.com"}' ``` A replayed response has the same status code and body as the original, plus this header: ```http Idempotent-Replayed: true ``` ## Rules - **Scope.** Keys are scoped to the API key (or dashboard session) that sent them. Two different API keys can use the same key value without colliding. - **Lifetime.** A successful response can be replayed for 24 hours. After that the key can be reused. - **Length.** Keys can be up to 255 characters. Sending email accepts up to 500. - **Same request only.** Reusing a key with a different body or a different endpoint returns `422`. - **Failures are not stored.** If the request fails (any non-`2xx` response), the key is released, so you can fix the problem and retry with the same key. - **JSON bodies only.** File uploads do not support idempotency keys. ## Error responses | Status | When | What to do | |--------|------|------------| | `400` | The key is longer than 255 characters. | Use a shorter key, such as a UUID. | | `409` | A request with this key is still running. The response includes `Retry-After: 1`. | Wait and retry with the same key to get the original result. | | `422` | The key was already used with a different body or endpoint. | Use a new key for each distinct request. | ## Supported endpoints Every `POST` endpoint in these services accepts `Idempotency-Key`: send email, agent inboxes, domains, webhooks, contacts, automations, templates and campaigns. The [OpenAPI document](https://reloop.sh/openapi.json) marks each supported operation. These endpoints do not accept it because their responses contain secrets that Reloop will not store for replay: - Creating an API key, and rotating one - Creating a webhook, which returns its signing secret ## Sending email `POST /api/mail/v1/send` stores keys with the email log rather than for 24 hours, so a retry returns the original send for as long as the log exists. If the earlier attempt failed to transmit, a retry with the same key sends the email again. You can also pass the key as `idempotency_key` in the body; the header takes precedence. --- # api/inbox/mailboxes/delete-api-inbox-v1mailboxes-by-id.mdx Source: https://reloop.sh/docs/api/inbox/mailboxes/delete-api-inbox-v1mailboxes-by-id Markdown: https://reloop.sh/docs/api/inbox/mailboxes/delete-api-inbox-v1mailboxes-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/mailboxes/get-api-inbox-v1mailboxes-by-id.mdx Source: https://reloop.sh/docs/api/inbox/mailboxes/get-api-inbox-v1mailboxes-by-id Markdown: https://reloop.sh/docs/api/inbox/mailboxes/get-api-inbox-v1mailboxes-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/mailboxes/get-api-inbox-v1mailboxes-list.mdx Source: https://reloop.sh/docs/api/inbox/mailboxes/get-api-inbox-v1mailboxes-list Markdown: https://reloop.sh/docs/api/inbox/mailboxes/get-api-inbox-v1mailboxes-list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/mailboxes/patch-api-inbox-v1mailboxes-by-id.mdx Source: https://reloop.sh/docs/api/inbox/mailboxes/patch-api-inbox-v1mailboxes-by-id Markdown: https://reloop.sh/docs/api/inbox/mailboxes/patch-api-inbox-v1mailboxes-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/mailboxes/post-api-inbox-v1mailboxes-create.mdx Source: https://reloop.sh/docs/api/inbox/mailboxes/post-api-inbox-v1mailboxes-create Markdown: https://reloop.sh/docs/api/inbox/mailboxes/post-api-inbox-v1mailboxes-create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/delete-api-inbox-v1messages-by-id.mdx Source: https://reloop.sh/docs/api/inbox/messages/delete-api-inbox-v1messages-by-id Markdown: https://reloop.sh/docs/api/inbox/messages/delete-api-inbox-v1messages-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/get-api-inbox-v1messages-by-id-attachments-by-attachment-id.mdx Source: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id-attachments-by-attachment-id Markdown: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id-attachments-by-attachment-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/get-api-inbox-v1messages-by-id-raw.mdx Source: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id-raw Markdown: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id-raw.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/get-api-inbox-v1messages-by-id.mdx Source: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id Markdown: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/get-api-inbox-v1messages.mdx Source: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages Markdown: https://reloop.sh/docs/api/inbox/messages/get-api-inbox-v1messages.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/patch-api-inbox-v1messages-by-id-read.mdx Source: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id-read Markdown: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id-read.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/patch-api-inbox-v1messages-by-id-star.mdx Source: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id-star Markdown: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id-star.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/patch-api-inbox-v1messages-by-id.mdx Source: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id Markdown: https://reloop.sh/docs/api/inbox/messages/patch-api-inbox-v1messages-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/post-api-inbox-v1messages-batch.mdx Source: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-batch Markdown: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-batch.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/post-api-inbox-v1messages-by-id-forward.mdx Source: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-forward Markdown: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-forward.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/post-api-inbox-v1messages-by-id-reply-all.mdx Source: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-reply-all Markdown: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-reply-all.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/post-api-inbox-v1messages-by-id-reply.mdx Source: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-reply Markdown: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-by-id-reply.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/messages/post-api-inbox-v1messages-send.mdx Source: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-send Markdown: https://reloop.sh/docs/api/inbox/messages/post-api-inbox-v1messages-send.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/delete-api-inbox-v1threads-by-id.mdx Source: https://reloop.sh/docs/api/inbox/threads/delete-api-inbox-v1threads-by-id Markdown: https://reloop.sh/docs/api/inbox/threads/delete-api-inbox-v1threads-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/get-api-inbox-v1threads-by-id-attachments-by-attachment-id.mdx Source: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads-by-id-attachments-by-attachment-id Markdown: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads-by-id-attachments-by-attachment-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/get-api-inbox-v1threads-by-id.mdx Source: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads-by-id Markdown: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/get-api-inbox-v1threads.mdx Source: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads Markdown: https://reloop.sh/docs/api/inbox/threads/get-api-inbox-v1threads.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/patch-api-inbox-v1threads-by-id-read.mdx Source: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id-read Markdown: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id-read.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/patch-api-inbox-v1threads-by-id-star.mdx Source: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id-star Markdown: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id-star.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/patch-api-inbox-v1threads-by-id.mdx Source: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id Markdown: https://reloop.sh/docs/api/inbox/threads/patch-api-inbox-v1threads-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/inbox/threads/post-api-inbox-v1threads-by-id-archive.mdx Source: https://reloop.sh/docs/api/inbox/threads/post-api-inbox-v1threads-by-id-archive Markdown: https://reloop.sh/docs/api/inbox/threads/post-api-inbox-v1threads-by-id-archive.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/index.mdx Source: https://reloop.sh/docs/api Markdown: https://reloop.sh/docs/api.md Welcome to the Reloop API Reference. The Reloop API is built on **REST** principles. We enforce HTTPS in every request to improve data security, integrity, and privacy. ## Authentication Every request must include your API key in the `x-api-key` header. API keys are scoped to a single **organization**: all resources you create, read, or modify are automatically associated with that organization. ```bash curl -X GET "https://reloop.sh/api/domain/v1/list" \ -H "x-api-key: rl_prod_YOUR_API_KEY" ``` You can create, rotate, and revoke API keys from the [Reloop Dashboard](https://reloop.sh/dashboard/api-keys). ### Required Headers | Header | Value | When | |--------|-------|------| | `x-api-key` | Your Reloop API key (prefixed `rl_prod_`) | Every request | | `Content-Type` | `application/json` | Requests with a body (`POST`, `PUT`, `PATCH`) | ## Response Format All responses, including errors, are returned as JSON. Successful responses typically include an `event` field describing the action performed: ```json { "event": "contact.created", ... } ``` ## Response Codes The API uses standard HTTP status codes to indicate the outcome of a request: | Code | Meaning | When You'll See It | |------|---------|-------------------| | `200` | OK | Request succeeded. | | `201` | Created | A new resource was created successfully. | | `202` | Accepted | A background job was started. Poll the URL in the `Location` header (see [Long-running operations](#long-running-operations)). | | `400` | Bad Request | Missing or invalid parameters. Check the `why` field for details. | | `401` | Unauthorized | The `x-api-key` header is missing, invalid, or the key has been revoked. | | `403` | Forbidden | The API key doesn't have permission to access the requested resource. | | `404` | Not Found | The resource doesn't exist, or it belongs to a different organization. | | `409` | Conflict | A resource with the same unique identifier already exists (e.g., duplicate domain or channel name), or a request with the same [Idempotency-Key](/docs/api/idempotency) is still running. | | `422` | Unprocessable Content | The request did not match the endpoint's schema (`errors` lists each field), or an [Idempotency-Key](/docs/api/idempotency) was reused with a different request. | | `429` | Too Many Requests | You've exceeded a [rate limit](/docs/api/usage-limits). Retry after the `Retry-After` header value. | | `500` | Internal Server Error | Something went wrong on our end. If the issue persists, contact [support](mailto:support@reloop.sh). | ## Long-running operations Operations that take longer than a request, such as emailing a contacts export, return `202 Accepted` right away and run in the background. The response includes: - A `Location` header with the URL of the job's status resource (also in the body as `statusUrl`). - A `Retry-After` header with the seconds to wait before polling. ```bash curl -X POST "https://reloop.sh/api/contacts/v1/export/email" \ -H "x-api-key: rl_prod_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "subscribed"}' # HTTP/1.1 202 Accepted # Location: /api/contacts/v1/exports/exp_abc123 # Retry-After: 5 curl "https://reloop.sh/api/contacts/v1/exports/exp_abc123" \ -H "x-api-key: rl_prod_YOUR_API_KEY" # { "object": "contact_export", "id": "exp_abc123", "status": "processing", ... } ``` Poll the `Location` URL with `GET` until `status` is final (`ready` or `failed`), waiting `Retry-After` seconds between requests. Job status is kept for 7 days. ## Errors When a request fails, the response body is a JSON object with a stable `code` plus fields that help you diagnose and fix the issue: ```json { "code": "not_found", "message": "Domain not found", "why": "The domain acme.com was not found or is not authorized for your organization", "fix": "Ensure the domain is registered and verified in your dashboard", "link": "https://reloop.sh/docs/api/errors" } ``` | Field | Description | |-------|-------------| | `code` | A stable, machine-readable error code such as `validation_error` or `rate_limited`. Branch on this. | | `message` | A short, human-readable summary of the error. | | `why` | A detailed explanation of what went wrong and what triggered the error. | | `fix` | Actionable steps you can take to resolve the issue. | | `link` | A URL to the relevant documentation page for more context. | See the [Errors](/docs/api/errors) page for a full reference of common error codes and troubleshooting tips. ## Rate Limits Rate limits are applied per **organization** and vary by endpoint. Every API response includes headers so you can monitor your usage: | Header | Description | |--------|-------------| | `X-RateLimit-Limit` | Maximum requests allowed in the current window. | | `X-RateLimit-Remaining` | Requests remaining before the limit is reached. | | `X-RateLimit-Window` | Window size in seconds (management endpoints like domains, contacts, and channels). | | `X-RateLimit-Reset` | Unix epoch timestamp when the window resets (email sending endpoints). | | `Retry-After` | Seconds to wait before retrying (only returned with `429` responses). | For the full breakdown of per-endpoint limits and the multi-layer email sending strategy, see [Usage Limits](/docs/api/usage-limits). --- # api/logs/get-api-logs-v1by-log_id.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1by-log_id Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1by-log_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/logs/get-api-logs-v1emails-by-id.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-by-id Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/logs/get-api-logs-v1emails-contact-activity.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-contact-activity Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-contact-activity.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/logs/get-api-logs-v1emails-stats.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-stats Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1emails-stats.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/logs/get-api-logs-v1emails.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1emails Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1emails.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/logs/get-api-logs-v1list.mdx Source: https://reloop.sh/docs/api/logs/get-api-logs-v1list Markdown: https://reloop.sh/docs/api/logs/get-api-logs-v1list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/mail/post-api-mail-v1send.mdx Source: https://reloop.sh/docs/api/mail/post-api-mail-v1send Markdown: https://reloop.sh/docs/api/mail/post-api-mail-v1send.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/pagination.mdx Source: https://reloop.sh/docs/api/pagination Markdown: https://reloop.sh/docs/api/pagination.md List endpoints support two ways to page through results: - **Cursor pagination (recommended).** Pass the `nextCursor` from one response as `cursor` in the next request. Pages stay stable while new records are created, so you never skip or repeat a record. Use this for syncing, exports and agents. - **Page numbers.** Pass `page` and `limit`. Useful for numbered pages in a UI. Every list response includes the fields for both. ## Cursor Pagination Request the first page without a cursor, then follow `nextCursor` until `hasMore` is `false`: ```bash curl "https://reloop.sh/api/contacts/v1/list?limit=100" \ -H "x-api-key: rl_prod_YOUR_API_KEY" # Response includes: "hasMore": true, "nextCursor": "eyJ0IjoiMjAyNi0xMC0yNVQx..." curl "https://reloop.sh/api/contacts/v1/list?limit=100&cursor=eyJ0IjoiMjAyNi0xMC0yNVQx..." \ -H "x-api-key: rl_prod_YOUR_API_KEY" ``` ```javascript let cursor = null; do { const url = new URL("https://reloop.sh/api/contacts/v1/list"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url, { headers: { "x-api-key": process.env.RELOOP_API_KEY } }); const page = await res.json(); for (const contact of page.contacts) handle(contact); cursor = page.nextCursor; } while (cursor); ``` | Field | Type | Description | |-------|------|-------------| | `hasMore` | `boolean` | Whether more results exist after this page. | | `nextCursor` | `string \| null` | Pass as `cursor` to get the next page. `null` on the last page. | Rules: - Treat cursors as opaque strings. Pass them back unchanged; do not build or edit them. An invalid cursor returns `400`. - Keep the same filters (and `limit`, if you like) on every request in a walk. A cursor only marks a position. - When `cursor` is set, `page` is ignored. You can start in page mode and switch: every page-mode response also returns a `nextCursor`. - Results are ordered newest first unless an endpoint says otherwise. - A few endpoints offer alternative sort orders. Cursors only work with the default order; combining `cursor` with another sort returns `400`, and `nextCursor` is `null` for those sorts: - List contacts with `suppressed=true` (sorted by suppression date). - List API keys with a `sort` other than `createdAt:desc`. - Campaign recipients with `category=clicked` (sorted by clicks). ### Exceptions - **Automations and custom events** use page numbers only. They are sorted by last update, which changes when a record is edited, so a cursor would not be stable. These lists are small; fetch them with `limit=100`. - **Agent inbox messages and threads** use `limit` and `offset` and return a plain array. - **Group members** (`/api/contacts/v1/groups/{group_id}/contacts`) are ordered oldest first. ## Page Numbers ### Query Parameters | Parameter | Type | Default | Max | Description | |-----------|------|---------|-----|-------------| | `page` | `number` | `1` | - | The page number to retrieve (1-indexed). | | `limit` | `number` | `20` | `100` | The number of records to return per page. | **Example request:** ```bash curl "https://reloop.sh/api/contacts/v1/list?page=2&limit=50" \ -H "x-api-key: rl_prod_YOUR_API_KEY" ``` ### Response Structure Pagination metadata is returned at the **root** of the JSON response alongside the data array. The exact field names vary by endpoint, but every paginated response includes `total`, `page`, and `limit`. #### Contacts The contacts list endpoint includes additional aggregate counts: ```json { "object": "contact", "contacts": [...], "total": 120, "page": 1, "limit": 20, "hasMore": true, "nextCursor": "eyJ0IjoiMjAyNi0xMC0yNVQx...", "totalContacts": 120, "subscribedContacts": 100, "unsubscribedContacts": 20, "event": "contact.list" } ``` | Field | Type | Description | |-------|------|-------------| | `contacts` | `array` | The list of contact objects for the current page. | | `total` | `number` | Total number of contacts matching the query. | | `page` | `number` | The current page number. | | `limit` | `number` | The page size used for this request. | | `totalContacts` | `number` | Total contacts in the organization. | | `subscribedContacts` | `number` | Number of contacts with an active subscription. | | `unsubscribedContacts` | `number` | Number of contacts who have unsubscribed. | #### Templates ```json { "templates": [...], "total": 45, "page": 1, "limit": 20, "hasMore": true, "nextCursor": "eyJ0IjoiMjAyNi0xMC0yNVQx...", "event": "template.list" } ``` | Field | Type | Description | |-------|------|-------------| | `templates` | `array` | The list of template objects for the current page. | | `total` | `number` | Total number of templates matching the query. | | `page` | `number` | The current page number. | | `limit` | `number` | The page size used for this request. | ### Calculating Total Pages Use the `total` and `limit` fields to calculate the number of pages: ```javascript const totalPages = Math.ceil(response.total / response.limit); const hasNextPage = response.page < totalPages; ``` --- # api/template/delete-api-template-v1by-id-versions-by-version-id.mdx Source: https://reloop.sh/docs/api/template/delete-api-template-v1by-id-versions-by-version-id Markdown: https://reloop.sh/docs/api/template/delete-api-template-v1by-id-versions-by-version-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/delete-api-template-v1by-id.mdx Source: https://reloop.sh/docs/api/template/delete-api-template-v1by-id Markdown: https://reloop.sh/docs/api/template/delete-api-template-v1by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/get-api-template-v1by-id-versions.mdx Source: https://reloop.sh/docs/api/template/get-api-template-v1by-id-versions Markdown: https://reloop.sh/docs/api/template/get-api-template-v1by-id-versions.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/get-api-template-v1by-id.mdx Source: https://reloop.sh/docs/api/template/get-api-template-v1by-id Markdown: https://reloop.sh/docs/api/template/get-api-template-v1by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/get-api-template-v1list.mdx Source: https://reloop.sh/docs/api/template/get-api-template-v1list Markdown: https://reloop.sh/docs/api/template/get-api-template-v1list.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/post-api-template-v1by-id-duplicate.mdx Source: https://reloop.sh/docs/api/template/post-api-template-v1by-id-duplicate Markdown: https://reloop.sh/docs/api/template/post-api-template-v1by-id-duplicate.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/post-api-template-v1by-id-test.mdx Source: https://reloop.sh/docs/api/template/post-api-template-v1by-id-test Markdown: https://reloop.sh/docs/api/template/post-api-template-v1by-id-test.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/post-api-template-v1by-id-versions-by-version-id-restore.mdx Source: https://reloop.sh/docs/api/template/post-api-template-v1by-id-versions-by-version-id-restore Markdown: https://reloop.sh/docs/api/template/post-api-template-v1by-id-versions-by-version-id-restore.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/post-api-template-v1by-id-versions.mdx Source: https://reloop.sh/docs/api/template/post-api-template-v1by-id-versions Markdown: https://reloop.sh/docs/api/template/post-api-template-v1by-id-versions.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/post-api-template-v1create.mdx Source: https://reloop.sh/docs/api/template/post-api-template-v1create Markdown: https://reloop.sh/docs/api/template/post-api-template-v1create.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/template/put-api-template-v1by-id.mdx Source: https://reloop.sh/docs/api/template/put-api-template-v1by-id Markdown: https://reloop.sh/docs/api/template/put-api-template-v1by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/get-api-tools-v1deliverability-test-by-token.mdx Source: https://reloop.sh/docs/api/tools/get-api-tools-v1deliverability-test-by-token Markdown: https://reloop.sh/docs/api/tools/get-api-tools-v1deliverability-test-by-token.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/get-api-tools-v1email-health-check-batch-by-token.mdx Source: https://reloop.sh/docs/api/tools/get-api-tools-v1email-health-check-batch-by-token Markdown: https://reloop.sh/docs/api/tools/get-api-tools-v1email-health-check-batch-by-token.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1auth-checker.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1auth-checker Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1auth-checker.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1bimi-check.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1bimi-check Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1bimi-check.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1blocklist-check.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1blocklist-check Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1blocklist-check.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1deliverability-test-inject.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1deliverability-test-inject Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1deliverability-test-inject.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1deliverability-test.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1deliverability-test Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1deliverability-test.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1dns-lookup.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1dns-lookup Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1dns-lookup.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1domain-age.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1domain-age Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1domain-age.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1email-health-check-batch.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1email-health-check-batch Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1email-health-check-batch.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1email-health-check.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1email-health-check Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1email-health-check.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1lookalike-watch.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1lookalike-watch Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1lookalike-watch.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1spam-check-rewrite.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1spam-check-rewrite Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1spam-check-rewrite.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1spam-check.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1spam-check Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1spam-check.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1spoof-checker.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1spoof-checker Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1spoof-checker.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1temp-email-checker.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1temp-email-checker Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1temp-email-checker.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/tools/post-api-tools-v1who-sends.mdx Source: https://reloop.sh/docs/api/tools/post-api-tools-v1who-sends Markdown: https://reloop.sh/docs/api/tools/post-api-tools-v1who-sends.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/upload/delete-api-upload-v1files-by-file-id.mdx Source: https://reloop.sh/docs/api/upload/delete-api-upload-v1files-by-file-id Markdown: https://reloop.sh/docs/api/upload/delete-api-upload-v1files-by-file-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/upload/post-api-upload-v1upload.mdx Source: https://reloop.sh/docs/api/upload/post-api-upload-v1upload Markdown: https://reloop.sh/docs/api/upload/post-api-upload-v1upload.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/usage-limits.mdx Source: https://reloop.sh/docs/api/usage-limits Markdown: https://reloop.sh/docs/api/usage-limits.md Reloop enforces rate limits on API requests to ensure stability and fair usage across all organizations. Limits are tracked with fixed-window counters backed by Redis. If a limit is exceeded, the API returns a `429 Too Many Requests` response with a `Retry-After` header indicating how many seconds to wait. ## Baseline Limit Every API endpoint shares a baseline of **600 requests per 60 seconds per client**. A client is identified by its API key, then its dashboard session, then its IP address for unauthenticated requests. The budget is shared across all services, so calls to `/api/domain`, `/api/contacts` and `/api/logs` draw from the same bucket. The endpoint-specific limits below apply on top of the baseline, and the stricter one wins. Self-hosted deployments can change the baseline with the `API_RATE_LIMIT_MAX` and `API_RATE_LIMIT_WINDOW_SECONDS` environment variables. ## Management Endpoint Limits Management endpoints cover everything outside of email sending: domains, contacts, channels, groups, properties, API keys, and templates. Each endpoint category has its own independent limit. ### Domain Service | Operation | Limit | Window | |-----------|-------|--------| | Create Domain | 10 requests | 60s | | Update Domain | 10 requests | 60s | | Delete Domain | 10 requests | 60s | | Verify DNS | 10 requests | 60s | ### Contacts Service | Operation | Limit | Window | |-----------|-------|--------| | List Contacts | 60 requests | 60s | | Get Contact | 60 requests | 60s | | Create Contact | 200 requests | 60s | | Update Contact | 30 requests | 60s | | Delete Contact | 30 requests | 60s | ### Channels Service | Operation | Limit | Window | |-----------|-------|--------| | List Channels | 60 requests | 60s | | Get Channel | 60 requests | 60s | | Create Channel | 30 requests | 60s | | Update Channel | 30 requests | 60s | | Delete Channel | 30 requests | 60s | ### Groups Service | Operation | Limit | Window | |-----------|-------|--------| | List Groups | 60 requests | 60s | | Get Group | 60 requests | 60s | | Create Group | 30 requests | 60s | | Update Group | 30 requests | 60s | | Delete Group | 30 requests | 60s | | Add Contact to Group | 30 requests | 60s | | Remove Contact from Group | 30 requests | 60s | ### Contact Properties Service | Operation | Limit | Window | |-----------|-------|--------| | List Properties | 60 requests | 60s | | Create Property | 30 requests | 60s | | Update Property | 30 requests | 60s | | Delete Property | 30 requests | 60s | ### API Keys Service | Operation | Limit | Window | |-----------|-------|--------| | Create API Key | 10 requests | 60s | | Update API Key | 30 requests | 60s | | Delete API Key | 20 requests | 60s | | Rotate API Key | 20 requests | 60s | | Enable / Disable API Key | 30 requests | 60s | ### Preferences Service | Operation | Limit | Window | |-----------|-------|--------| | Generate Token | 30 requests | 60s | | Update Preference | 30 requests | 60s | --- ## Email Sending Limits The email sending endpoint (`/api/mail/v1/send`) uses a **multi-layer** rate limiting strategy. All five layers are checked in parallel. If any single layer is exceeded, the request is rejected. The organization daily layer applies only to plans with a daily sending limit (Free); Pay as you go, Pro and higher plans have no daily cap. | Layer | Limit | Window | What It Protects | |-------|-------|--------|-----------------| | IP | 20 requests | 60s | Prevents brute-force from a single source. | | User | 50 requests | 60s | Stops abuse from a single user within an organization. | | Organization | 100 requests | 60s | Prevents runaway integrations per tenant. | | Organization Daily | 5,000 requests | 24h | Daily cap per organization on the Free plan. Not applied to Pay as you go, Pro and higher. | | Global | 500 requests | 60s | Protects infrastructure from DDoS. | When a sending request is rate limited, the error response tells you which layer was exceeded: ```json { "message": "Too Many Requests", "why": "Rate limit exceeded on the organization layer. You have sent too many requests in the current time window.", "fix": "Please wait 42 seconds before retrying, or contact support to increase your limits." } ``` --- ## Rate Limit Headers Every API response, including errors such as `401` and `404`, includes rate limit headers. When an endpoint has its own stricter limit, the headers describe that limit. | Header | Description | Example | |--------|-------------|---------| | `ratelimit-limit` | Maximum requests allowed in the current window. | `600` | | `ratelimit-remaining` | Requests remaining in the current window. | `587` | | `ratelimit-reset` | Seconds until the window resets (a countdown, not a timestamp). | `42` | | `retry-after` | Seconds to wait before retrying (only on `429`). | `42` | `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` carry the same values for older clients. Email sending responses also include `x-reloop-daily-quota` and `x-reloop-monthly-quota` with your used send counts. --- ## Best Practices - **Monitor headers proactively.** Check `ratelimit-remaining` before it reaches zero to avoid disruptions. - **Implement exponential backoff.** When you receive a `429`, wait for the duration specified in `Retry-After`, then retry with increasing delays if the limit is still exceeded. - **Batch where possible.** Use list endpoints with pagination instead of fetching resources one at a time. - **Separate read and write traffic.** Read operations (list, get) generally have higher limits than write operations (create, update, delete). --- # api/versioning.mdx Source: https://reloop.sh/docs/api/versioning Markdown: https://reloop.sh/docs/api/versioning.md The Reloop API is versioned in the URL. The current version is `v1`, as in `https://reloop.sh/api/mail/v1/send`. Within a version, Reloop does not make breaking changes. When an endpoint must change in a breaking way, the new behavior ships as a new endpoint or version, and the old one is deprecated with at least **180 days** of notice before it is removed. ## Non-breaking changes These can ship at any time without a new version. Build your client to tolerate them: - New endpoints. - New optional request fields, query parameters and headers. - New fields in responses, and new values in fields documented as open-ended. - New error `code` values. Handle an unknown `code` by its HTTP status. See [Errors](/docs/api/errors). - New webhook event types. - Changes to human-readable text such as `message`, `why` and `fix`. - Changes to rate limits. See [Usage Limits](/docs/api/usage-limits). ## Breaking changes These only happen in a new version, after the deprecation process below: - Removing or renaming an endpoint, request field or response field. - Making an optional request field required. - Changing a field's type or format. - Changing authentication. - Changing the meaning of an existing status code or error `code`. Fixes for security issues are the one exception. When a vulnerability requires it, Reloop may change behavior with shorter notice and will explain why in the [changelog](https://reloop.sh/changelog). ## Deprecation process 1. **Announce.** The deprecation is published in the [changelog](https://reloop.sh/changelog) ([RSS](https://reloop.sh/changelog/feed.xml)) with the replacement and the removal date. 2. **Signal.** From that day, every response from the deprecated endpoint carries headers you can monitor: ```http Deprecation: @1793491200 Sunset: Sat, 01 May 2027 00:00:00 GMT Link: ; rel="deprecation", ; rel="successor-version" ``` | Header | Meaning | |--------|---------| | `Deprecation` | When the endpoint was deprecated, as `@` plus Unix seconds ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)). | | `Sunset` | The earliest date the endpoint may be removed, as an HTTP date ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)). | | `Link` | This policy (`rel="deprecation"`) and the replacement endpoint, if there is one (`rel="successor-version"`). | The [OpenAPI document](https://reloop.sh/openapi.json) marks the operation `deprecated: true`, with the removal date in `x-sunset` and the replacement in `x-successor`. 3. **Remind.** Organizations still calling the endpoint are contacted before the sunset date. 4. **Remove.** On or after the `Sunset` date, the endpoint may be removed and return `404` (`code: not_found`). `Sunset` is always at least 180 days after `Deprecation`. ## Staying ahead of changes - Log a warning whenever a response includes a `Deprecation` header. - Subscribe to the [changelog feed](https://reloop.sh/changelog/feed.xml). - Ignore unknown response fields instead of failing on them. - Branch on error `code`, not on `message` text. ## Self-hosted deployments Self-hosted Reloop follows the same API versions. Deprecated endpoints are removed in a release after their `Sunset` date, and the release notes list them. --- # api/webhook/delete-api-webhook-v1by-id.mdx Source: https://reloop.sh/docs/api/webhook/delete-api-webhook-v1by-id Markdown: https://reloop.sh/docs/api/webhook/delete-api-webhook-v1by-id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/get-api-webhook-v1.mdx Source: https://reloop.sh/docs/api/webhook/get-api-webhook-v1 Markdown: https://reloop.sh/docs/api/webhook/get-api-webhook-v1.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/get-api-webhook-v1by-webhook_id-deliveries.mdx Source: https://reloop.sh/docs/api/webhook/get-api-webhook-v1by-webhook_id-deliveries Markdown: https://reloop.sh/docs/api/webhook/get-api-webhook-v1by-webhook_id-deliveries.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/get-api-webhook-v1by-webhook_id.mdx Source: https://reloop.sh/docs/api/webhook/get-api-webhook-v1by-webhook_id Markdown: https://reloop.sh/docs/api/webhook/get-api-webhook-v1by-webhook_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/patch-api-webhook-v1by-webhook_id.mdx Source: https://reloop.sh/docs/api/webhook/patch-api-webhook-v1by-webhook_id Markdown: https://reloop.sh/docs/api/webhook/patch-api-webhook-v1by-webhook_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/post-api-webhook-deliveries-by-delivery_id-retry.mdx Source: https://reloop.sh/docs/api/webhook/post-api-webhook-deliveries-by-delivery_id-retry Markdown: https://reloop.sh/docs/api/webhook/post-api-webhook-deliveries-by-delivery_id-retry.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/post-api-webhook-v1.mdx Source: https://reloop.sh/docs/api/webhook/post-api-webhook-v1 Markdown: https://reloop.sh/docs/api/webhook/post-api-webhook-v1.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/webhook/post-api-webhook-v1trigger.mdx Source: https://reloop.sh/docs/api/webhook/post-api-webhook-v1trigger Markdown: https://reloop.sh/docs/api/webhook/post-api-webhook-v1trigger.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/delete-api-workflow-v1automations-by-automation_id.mdx Source: https://reloop.sh/docs/api/workflow/automations/delete-api-workflow-v1automations-by-automation_id Markdown: https://reloop.sh/docs/api/workflow/automations/delete-api-workflow-v1automations-by-automation_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments-by-enrollment_id.mdx Source: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments-by-enrollment_id Markdown: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments-by-enrollment_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments.mdx Source: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments Markdown: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id-enrollments.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/get-api-workflow-v1automations-by-automation_id.mdx Source: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id Markdown: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations-by-automation_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/get-api-workflow-v1automations.mdx Source: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations Markdown: https://reloop.sh/docs/api/workflow/automations/get-api-workflow-v1automations.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/patch-api-workflow-v1automations-by-automation_id.mdx Source: https://reloop.sh/docs/api/workflow/automations/patch-api-workflow-v1automations-by-automation_id Markdown: https://reloop.sh/docs/api/workflow/automations/patch-api-workflow-v1automations-by-automation_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/post-api-workflow-v1automations-by-automation_id-activate.mdx Source: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-activate Markdown: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-activate.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/post-api-workflow-v1automations-by-automation_id-enroll.mdx Source: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-enroll Markdown: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-enroll.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/post-api-workflow-v1automations-by-automation_id-pause.mdx Source: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-pause Markdown: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations-by-automation_id-pause.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/automations/post-api-workflow-v1automations.mdx Source: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations Markdown: https://reloop.sh/docs/api/workflow/automations/post-api-workflow-v1automations.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/delete-api-workflow-v1events-by-event_id.mdx Source: https://reloop.sh/docs/api/workflow/events/delete-api-workflow-v1events-by-event_id Markdown: https://reloop.sh/docs/api/workflow/events/delete-api-workflow-v1events-by-event_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/get-api-workflow-v1events-by-event_id.mdx Source: https://reloop.sh/docs/api/workflow/events/get-api-workflow-v1events-by-event_id Markdown: https://reloop.sh/docs/api/workflow/events/get-api-workflow-v1events-by-event_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/get-api-workflow-v1events.mdx Source: https://reloop.sh/docs/api/workflow/events/get-api-workflow-v1events Markdown: https://reloop.sh/docs/api/workflow/events/get-api-workflow-v1events.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/patch-api-workflow-v1events-by-event_id.mdx Source: https://reloop.sh/docs/api/workflow/events/patch-api-workflow-v1events-by-event_id Markdown: https://reloop.sh/docs/api/workflow/events/patch-api-workflow-v1events-by-event_id.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/post-api-workflow-v1events-track.mdx Source: https://reloop.sh/docs/api/workflow/events/post-api-workflow-v1events-track Markdown: https://reloop.sh/docs/api/workflow/events/post-api-workflow-v1events-track.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # api/workflow/events/post-api-workflow-v1events.mdx Source: https://reloop.sh/docs/api/workflow/events/post-api-workflow-v1events Markdown: https://reloop.sh/docs/api/workflow/events/post-api-workflow-v1events.md {/* This file was generated by the OpenAPI doc generator. Do not edit this file directly. Run `bun run generate:api-docs` to regenerate. */} --- # webhooks/contacts/created.mdx Source: https://reloop.sh/docs/webhooks/contacts/created Markdown: https://reloop.sh/docs/webhooks/contacts/created.md The `contact.create` event is triggered when a new contact is added (via API or auto-capture from sends). ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`contact.create`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Contact identity and status fields (`id`, `email`, `first_name`, `last_name`, `status`). ## Handling this event When your endpoint receives a `contact.create` event, you might want to: - Sync the contact with your primary database. - Trigger a welcome email sequence. - Tag the contact in your CRM. ```json { "id": "whev_01h…", "type": "contact.create", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "con_123456789", "email": "contact@example.com", "first_name": "Ada", "last_name": "Lovelace", "status": "subscribed" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/contacts/deleted.mdx Source: https://reloop.sh/docs/webhooks/contacts/deleted Markdown: https://reloop.sh/docs/webhooks/contacts/deleted.md The `contact.delete` event is triggered when a contact is permanently removed. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`contact.delete`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` The deleted contact identity and last known fields. ## Handling this event When your endpoint receives a `contact.delete` event, you might want to: - Purge the contact from your local cache or database. - Stop any active communication threads. - Update your audience metrics. ```json { "id": "whev_01h…", "type": "contact.delete", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "con_123456789", "email": "contact@example.com", "first_name": null, "last_name": null, "status": "subscribed" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/contacts/updated.mdx Source: https://reloop.sh/docs/webhooks/contacts/updated Markdown: https://reloop.sh/docs/webhooks/contacts/updated.md The `contact.update` event is triggered when a contact's details change. Status-only transitions also emit dedicated events (`contact.subscribed`, `contact.unsubscribed`, `contact.blocked`). ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`contact.update`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Updated contact fields (`id`, `email`, `first_name`, `last_name`, `status`). ## Handling this event When your endpoint receives a `contact.update` event, you might want to: - Update the contact's record in your system. - Synchronize subscription preferences. - Log the change for audit purposes. ```json { "id": "whev_01h…", "type": "contact.update", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "con_123456789", "email": "contact@example.com", "first_name": "Ada", "last_name": "Lovelace", "status": "unsubscribed" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/domains/created.mdx Source: https://reloop.sh/docs/webhooks/domains/created Markdown: https://reloop.sh/docs/webhooks/domains/created.md The `domain.create` event is triggered when a new domain is successfully added to your Reloop account. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`domain.create`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Contains the details of the newly created domain (`id`, `name`, `status`). ## Handling this event When your endpoint receives a `domain.create` event, you might want to: - Sync the domain status with your internal system. - Trigger DNS verification workflows. - Log the activity for auditing. ```json { "id": "whev_01h…", "type": "domain.create", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "dom_123456789", "name": "example.com", "status": "pending" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/domains/deleted.mdx Source: https://reloop.sh/docs/webhooks/domains/deleted Markdown: https://reloop.sh/docs/webhooks/domains/deleted.md The `domain.delete` event is triggered when a domain is removed from your organization. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`domain.delete`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Contains the deleted domain identity (`id`, `name`, `status`). ## Handling this event When your endpoint receives a `domain.delete` event, you might want to: - Stop sending from that domain in your application. - Remove DNS setup reminders. - Clean up local domain caches. ```json { "id": "whev_01h…", "type": "domain.delete", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "dom_123456789", "name": "example.com", "status": "pending" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/domains/updated.mdx Source: https://reloop.sh/docs/webhooks/domains/updated Markdown: https://reloop.sh/docs/webhooks/domains/updated.md The `domain.update` event is triggered when a domain's settings change (tracking, TLS, send/receive flags, etc.). ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`domain.update`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Contains the updated domain identity and status (`id`, `name`, `status`). ## Handling this event When your endpoint receives a `domain.update` event, you might want to: - Refresh cached domain configuration. - Re-run internal validation against the new settings. ```json { "id": "whev_01h…", "type": "domain.update", "created_at": "2026-07-22T12:00:00.000Z", "data": { "id": "dom_123456789", "name": "example.com", "status": "active" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/bounced.mdx Source: https://reloop.sh/docs/webhooks/emails/bounced Markdown: https://reloop.sh/docs/webhooks/emails/bounced.md The `email.bounced` event is triggered when delivery permanently fails (hard bounce, admin bounce, or OOB). ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.bounced`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Includes standard email fields plus `error` with SMTP details when available. ## Handling this event When your endpoint receives an `email.bounced` event, you might want to: - Suppress the recipient from future sends. - Notify the sender or update CRM status. ```json { "id": "whev_01h…", "type": "email.bounced", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["bad@example.com"], "subject": "Hello World", "status": "bounced", "error": { "code": 550, "message": "User unknown" } } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/clicked.mdx Source: https://reloop.sh/docs/webhooks/emails/clicked Markdown: https://reloop.sh/docs/webhooks/emails/clicked.md The `email.clicked` event is triggered when click tracking records a link click. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.clicked`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Standard outbound email fields plus `url` for the clicked destination. ## Handling this event When your endpoint receives an `email.clicked` event, you might want to: - Track link engagement. - Personalize follow-up based on which URL was clicked. ```json { "id": "whev_01h…", "type": "email.clicked", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "delivered", "url": "https://example.com/pricing" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/complained.mdx Source: https://reloop.sh/docs/webhooks/emails/complained Markdown: https://reloop.sh/docs/webhooks/emails/complained.md The `email.complained` event is triggered when a feedback loop reports spam. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.complained`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Includes standard email fields; `status` is `spam` and `error` may include FBL details. ## Handling this event When your endpoint receives an `email.complained` event, you might want to: - Immediately suppress the recipient. - Review content or list hygiene. ```json { "id": "whev_01h…", "type": "email.complained", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "spam", "error": { "message": "Feedback loop: spam complaint" } } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/delivered.mdx Source: https://reloop.sh/docs/webhooks/emails/delivered Markdown: https://reloop.sh/docs/webhooks/emails/delivered.md The `email.delivered` event is triggered when an email is delivered to the recipient MTA. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.delivered`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Outbound email fields: `email_id`, `from`, `to`, `subject`, `status`. ## Handling this event When your endpoint receives an `email.delivered` event, you might want to: - Update the status in your internal database. - Trigger an automated confirmation email. - Log the event for analytics purposes. ```json { "id": "whev_01h…", "type": "email.delivered", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "delivered" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/delivery-delayed.mdx Source: https://reloop.sh/docs/webhooks/emails/delivery-delayed Markdown: https://reloop.sh/docs/webhooks/emails/delivery-delayed.md The `email.delivery_delayed` event is triggered on a transient delivery failure (soft bounce / deferral). ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.delivery_delayed`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Includes standard email fields plus `error` describing the temporary failure. ## Handling this event When your endpoint receives an `email.delivery_delayed` event, you might want to: - Track deliverability health without treating it as a permanent failure. - Alert if delays spike for a domain. ```json { "id": "whev_01h…", "type": "email.delivery_delayed", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "sent", "error": { "code": 450, "message": "Mailbox temporarily unavailable" } } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/failed.mdx Source: https://reloop.sh/docs/webhooks/emails/failed Markdown: https://reloop.sh/docs/webhooks/emails/failed.md The `email.failed` event is triggered when an email fails permanently before delivery (provider error at send time) or expires in the queue. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.failed`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Includes standard email fields plus `error`. ## Handling this event When your endpoint receives an `email.failed` event, you might want to: - Notify the user about the failure. - Check the error details for troubleshooting. - Update the status in your internal database. ```json { "id": "whev_01h…", "type": "email.failed", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "failed", "error": { "message": "Upstream provider rejected the message" } } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/opened.mdx Source: https://reloop.sh/docs/webhooks/emails/opened Markdown: https://reloop.sh/docs/webhooks/emails/opened.md The `email.opened` event is triggered when open tracking records a pixel load. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.opened`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Standard outbound email fields. `status` reflects the email log status (usually `delivered` or `sent`), not a separate open status. ## Handling this event When your endpoint receives an `email.opened` event, you might want to: - Track email open rates. - Update user engagement scores. - Trigger follow-up workflows. ```json { "id": "whev_01h…", "type": "email.opened", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "delivered" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/received.mdx Source: https://reloop.sh/docs/webhooks/emails/received Markdown: https://reloop.sh/docs/webhooks/emails/received.md The `email.received` event is triggered when an inbound email is successfully received by Reloop. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.received`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Inbound fields: `email_id`, `mailbox_id`, `from`, `from_name`, `to`, `cc`, `subject`, `thread_id`, `has_attachments`, `is_spam`, `status`, `message_id`. ## Handling this event When your endpoint receives an `email.received` event, you might want to: - Process inbound messages for your application. - Fetch the full body via the inbox API using `email_id`. - Store attachments in your cloud storage. ```json { "id": "whev_01h…", "type": "email.received", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "in_123456789", "mailbox_id": "mb_123456789", "from": "sender@example.com", "from_name": "Sender", "to": ["inbound@yourdomain.com"], "cc": [], "subject": "Inbound Message", "thread_id": "thr_123456789", "has_attachments": false, "is_spam": false, "status": "received", "message_id": "" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/scheduled.mdx Source: https://reloop.sh/docs/webhooks/emails/scheduled Markdown: https://reloop.sh/docs/webhooks/emails/scheduled.md The `email.scheduled` event is triggered when an email is accepted with a future `scheduled_at` time. It replaces `email.sent` for that message; later lifecycle events (`email.delivered`, `email.bounced`, …) still fire when the message is actually processed. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.scheduled`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Standard outbound email fields with `status: "scheduled"` and `scheduled_at` (ISO 8601). ## Handling this event When your endpoint receives an `email.scheduled` event, you might want to: - Show the message as queued for a future send in your UI. - Cancel or reschedule downstream automations until delivery. ```json { "id": "whev_01h…", "type": "email.scheduled", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "scheduled", "scheduled_at": "2026-07-23T09:00:00.000Z" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/sent.mdx Source: https://reloop.sh/docs/webhooks/emails/sent Markdown: https://reloop.sh/docs/webhooks/emails/sent.md The `email.sent` event is triggered when an email is accepted for delivery by Reloop. ## Event Details All webhook payloads follow a consistent top-level structure with event-specific data nested within the `data` object. ### type `string` The event type that triggered the webhook (`email.sent`). ### created_at `string` ISO 8601 timestamp when the webhook event was created. ### data `object` Outbound email fields: `email_id`, `from`, `to`, `subject`, `status`. ## Handling this event When your endpoint receives an `email.sent` event, you might want to: - Update the status in your internal database. - Trigger automated workflows or notifications. - Log the event for analytics purposes. ```json { "id": "whev_01h…", "type": "email.sent", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["recipient@example.com"], "subject": "Hello World", "status": "sent" } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/emails/suppressed.mdx Source: https://reloop.sh/docs/webhooks/emails/suppressed Markdown: https://reloop.sh/docs/webhooks/emails/suppressed.md The `email.suppressed` event ID is reserved for when Reloop rejects a send because the recipient is on a suppression list. **Status:** inactive. Not available for new webhook subscriptions until pre-send suppression publishes this event. Contact suppressions currently surface as `contact.blocked` when auto-capture blocks a recipient. ## Planned `data` shape ```json { "id": "whev_01h…", "type": "email.suppressed", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_123456789", "from": "sender@example.com", "to": ["blocked@example.com"], "subject": "Hello World", "status": "failed", "error": { "message": "Recipient is on the suppression list" } } } ``` ## Learn More - [Webhooks Introduction](/docs/webhooks) - [All Event Types](/docs/webhooks/event-types) --- # webhooks/event-types.mdx Source: https://reloop.sh/docs/webhooks/event-types Markdown: https://reloop.sh/docs/webhooks/event-types.md Reloop supports the following **active** event types. Each delivery uses a consistent top-level envelope; event-specific fields live under `data` and use **snake_case**. ## Email lifecycle | Event | Description | | --- | --- | | `email.sent` | Email accepted for delivery (immediate send) | | `email.scheduled` | Email accepted with a future `scheduled_at` (replaces `email.sent` for that message) | | `email.delivered` | Delivered to the recipient MTA | | `email.bounced` | Permanent delivery failure | | `email.delivery_delayed` | Transient failure / delay | | `email.complained` | Recipient marked as spam (feedback loop) | | `email.failed` | Permanent send failure before delivery (or message expired) | | `email.opened` | Recipient opened the email | | `email.clicked` | Recipient clicked a link in the email | | `email.received` | Inbound email successfully received | ### Email `data` shape (outbound) ```json { "email_id": "em_…", "from": "Acme ", "to": ["user@example.com"], "subject": "Hello", "status": "delivered", "error": { "code": 550, "message": "User unknown" }, "url": "https://example.com/path" } ``` - `error` is present on bounce, delay, complaint, and failure events. - `url` is present on `email.clicked`. - `scheduled_at` is present on `email.scheduled`. ### Inbound `email.received` `data` shape ```json { "email_id": "in_…", "mailbox_id": "mb_…", "from": "sender@example.com", "from_name": "Sender", "to": ["inbox@yourdomain.com"], "cc": [], "subject": "Inbound Message", "thread_id": "thr_…", "has_attachments": false, "is_spam": false, "status": "received", "message_id": "" } ``` ## Domain lifecycle | Event | Description | | --- | --- | | `domain.create` | Domain created | | `domain.update` | Domain updated | | `domain.delete` | Domain deleted | | `domain.undelete` | Domain restored | | `domain.verify` | Domain DNS verified | ### Domain `data` shape ```json { "id": "dom_…", "name": "example.com", "status": "active" } ``` ## API key lifecycle | Event | Description | | --- | --- | | `api-key.create` | API key created | | `api-key.update` | API key updated / enabled / rotated | | `api-key.delete` | API key deleted | | `api-key.revoke` | API key revoked (disabled) | ### API key `data` shape ```json { "api_key_id": "key_…", "status": "disabled", "action": "revoked" } ``` `status` and `action` are optional and only set for some transitions. ## Contact lifecycle | Event | Description | | --- | --- | | `contact.create` | Contact created | | `contact.update` | Contact updated | | `contact.delete` | Contact deleted | | `contact.subscribed` | Contact status set to subscribed | | `contact.unsubscribed` | Contact status set to unsubscribed | | `contact.blocked` | Contact status set to blocked | | `contact.group.create` | Contact group created | | `contact.group.update` | Contact group updated | | `contact.group.delete` | Contact group deleted | ### Contact `data` shape ```json { "id": "con_…", "email": "contact@example.com", "first_name": "Ada", "last_name": "Lovelace", "status": "subscribed" } ``` ### Contact group `data` shape ```json { "id": "grp_…", "name": "VIP" } ``` ## Envelope ```json { "id": "whev_…", "type": "email.delivered", "created_at": "2026-07-22T12:00:00.000Z", "data": { } } ``` ## Coming soon (not subscribable yet) These event IDs exist in the catalog but are **inactive** until the pipeline emits them: | Event | Notes | | --- | --- | | `email.suppressed` | Pre-send suppression event not wired yet | | `api-key.rate_limited` | Rate-limit publisher not wired yet | --- ## Learn More - [Introduction](/docs/webhooks) - [Verify Webhooks Requests](/docs/webhooks/verify-webhooks-requests) - [Retries and Replays](/docs/webhooks/retries-and-replays) --- # webhooks/index.mdx Source: https://reloop.sh/docs/webhooks Markdown: https://reloop.sh/docs/webhooks.md Webhooks allow you to build or set up integrations which subscribe to certain events on Reloop. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL. ## What is a webhook? Webhooks are "user-defined HTTP callbacks". They are usually triggered by some event, such as an email being delivered or a domain being verified. When that event occurs, the source site makes an HTTP request to the URL configured for the webhook. Users can configure them to cause events on one site to invoke behavior on another. ## Why use webhooks? - Automatically remove bounced email addresses from mailing lists - Create alerts in your messaging or incident tools based on event types - Store all send events in your own database for custom reporting/retention - Receive emails using [Inbound](/docs/webhooks/ingester) ## How to receive webhooks ### 1. Create a dev endpoint to receive requests. Set up a URL on your server that can receive HTTP POST requests. ```javascript if (req.method === 'POST') { const event = req.body; console.log(event); res.status(200).send('OK'); } }; ``` To test it while it runs on localhost, use [`reloop webhooks listen`](/docs/webhooks/testing-webhooks#test-on-localhost-with-the-cli). It forwards test events to your machine without exposing it to the internet. ### 2. Add a webhook in Reloop. Go to the [Webhooks page](https://reloop.sh/dashboard/webhooks) in your dashboard. 1. Add your publicly accessible HTTPS URL. 2. Select all events you want to observe. ### 3. Test your local endpoint. Open the webhook's **Test** page and send a test event to your URL or to a local listener. See [Test Webhooks](/docs/webhooks/testing-webhooks). When you send an email or trigger an event, Reloop will send a payload like this to your endpoint: ```json { "id": "whev_01h…", "type": "email.bounced", "created_at": "2026-07-22T23:41:12.126Z", "data": { "email_id": "em_123456789", "from": "Acme ", "to": ["bounced@reloop.sh"], "subject": "Sending this example", "status": "bounced", "error": { "code": 550, "message": "User unknown" } } } ``` All `data` fields use **snake_case**. See [Event Types](/docs/webhooks/event-types) for the full catalog and per-category shapes. Headers include `Reloop-Id`, `Reloop-Timestamp`, and `Reloop-Signature` for verification. ### 4. Update and deploy your production endpoint. Once you've tested your logic, update your endpoint to handle specific event types and deploy it to your production server. ```javascript if (req.method === 'POST') { const event = req.body; if (event.type === "email.bounced") { // Handle bounce logic } res.status(200).send('OK'); } }; ``` ### 5. Register your production webhook endpoint Update your webhook URL in the [Reloop Dashboard](https://reloop.sh/dashboard/webhooks) with your production URL. ## FAQ If your server returns a non-2xx response, we retry up to 7 attempts: - Immediate - 5 seconds - 5 minutes - 30 minutes - 2 hours - 5 hours - 10 hours Use your endpoint secret (`whsec_…`) to verify `Reloop-Signature`. See [Verify Webhooks Requests](/docs/webhooks/verify-webhooks-requests). - `54.148.139.208` - `2600:1f24:64:8000::/52` Reloop guarantees "at-least-once" delivery of webhooks. Each webhook has a unique `reloop-id` (or Svix ID) header that you can use for idempotency to ensure you don't process the same event twice. We do not guarantee that events will arrive in the order they occurred. For example, an `email.opened` event might arrive before an `email.delivered` event due to network latency or retries. You should use the `created_at` timestamp in the payload to order them. Yes, you can manually replay any failed or successful webhook event from the [Reloop Dashboard](https://reloop.sh/dashboard/webhooks). --- ## Learn More - [Event Types](/docs/webhooks/event-types) - [Webhook Ingester](/docs/webhooks/ingester) - [Verifying Requests](/docs/webhooks/verify-webhooks-requests) --- # webhooks/ingester.mdx Source: https://reloop.sh/docs/webhooks/ingester Markdown: https://reloop.sh/docs/webhooks/ingester.md The Webhook Ingester is a self-hosted tool that allows you to store all your Reloop webhook events directly in your own database. This is ideal for long-term data retention, custom analytics, and building robust event-driven workflows without worrying about webhook delivery order or duplicate handling. ## Why use the Webhook Ingester? - **Signature Verification**: Built-in verification to ensure webhook authenticity. - **Idempotency**: Safely handles duplicate webhook deliveries using unique event IDs. - **Multiple Databases**: Support for PostgreSQL, MySQL, MongoDB, and data warehouses. - **Easy Deployment**: One-click deployment to Vercel, Railway, or Render. ## Deploy You can run the ingester using our official Docker image: ```bash docker pull ghcr.io/reloop-labs/reloop-webhooks-ingester ``` ## Supported Databases We support a wide range of databases to store your webhook events: - **PostgreSQL** (including Supabase and Neon) - **MySQL** (including PlanetScale) - **MongoDB** - **Data Warehouses** (Snowflake, BigQuery, ClickHouse) ## Quick Start Clone the repository and install dependencies: ```bash git clone https://github.com/reloop-labs/reloop-webhooks-ingester.git cd reloop-webhooks-ingester pnpm install ``` Copy the example environment file and add your credentials: ```bash cp .env.example .env.local ``` Required variables: ```bash # Your Reloop webhook signing secret from the dashboard RELOOP_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx # Database connection string POSTGRESQL_URL=postgresql://user:password@host:5432/database ``` Run the setup command to create the necessary tables/collections: ```bash pnpm db:setup --postgresql # or --mysql, --mongodb ``` Deploy your instance and add the endpoint in the [Reloop Dashboard](https://reloop.sh/dashboard/webhooks). Your endpoint URL will be: `https://your-app.com/postgresql` (or your chosen connector). ## Database Schemas The ingester creates tables with a standardized schema for different event categories: - `reloop_wh_emails`: Stores all email-related events. - `reloop_wh_contacts`: Stores audience and contact events. - `reloop_wh_domains`: Stores domain verification events. ### Core Fields Every event includes these essential fields: - `event_id`: Unique webhook event ID for idempotency. - `event_type`: The type of event (e.g., `email.delivered`). - `event_created_at`: When the event occurred in Reloop. - `webhook_received_at`: When the webhook was stored in your database. - `data`: JSON object containing the full event payload. ## Idempotency The ingester uses the unique event ID provided by Reloop to ensure that even if a webhook is delivered multiple times, it is only stored once in your database. ## Configuration Reference ### Required Environment Variables | Variable | Description | |----------|-------------| | `RELOOP_WEBHOOK_SECRET` | The signing secret used to verify webhook authenticity. | ### Database-Specific Variables #### Supabase / PostgreSQL ```bash POSTGRESQL_URL=postgresql://user:password@host:5432/database ``` #### MongoDB ```bash MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/ MONGODB_DATABASE=reloop_webhooks ``` ## Example Queries Once your data is in your database, you can run powerful queries: **Count daily events by type:** ```sql SELECT DATE(event_created_at) AS day, event_type, COUNT(*) AS count FROM reloop_wh_emails GROUP BY DATE(event_created_at), event_type ORDER BY day DESC; ``` ## Data Retention Since the data is in your own database, you have full control over retention. You can set up TTL indexes in MongoDB or use a cron job to purge old data: ```sql DELETE FROM reloop_wh_emails WHERE event_created_at < NOW() - INTERVAL '90 days'; ``` ## Troubleshooting - **Signature failing**: Ensure `RELOOP_WEBHOOK_SECRET` matches your dashboard secret. - **Connection errors**: Check your database firewall rules and connection string. - **Webhooks not received**: Verify your endpoint is publicly accessible and returning a 200 OK. --- ## Learn More - [Verifying Webhooks](/docs/webhooks/verify-webhooks-requests) - [Event Types Reference](/docs/webhooks/event-types) --- # webhooks/retries-and-replays.mdx Source: https://reloop.sh/docs/webhooks/retries-and-replays Markdown: https://reloop.sh/docs/webhooks/retries-and-replays.md Reloop delivers webhooks with at-least-once semantics. If your server is down or returns a non-2xx status, we automatically retry the delivery. ## Automatic Retries Each delivery is attempted up to **`maxRetries` times** (default **7**, maximum **7**) with a fixed schedule. `maxRetries` is total attempts including the first try. Delays are measured from the previous failure: | Attempt | Delay after previous failure | | --- | --- | | 1 | Immediate | | 2 | 5 seconds | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | | 6 | 5 hours | | 7 | 10 hours | If you set a lower `maxRetries` on the endpoint (for example `3`), only the first rows of the schedule apply. SSRF-blocked or non-HTTPS endpoints are **not** retried (terminal failure). After many consecutive **terminal** failures on an endpoint, Reloop may disable the webhook (`status: failed`). Re-enable it from the dashboard after fixing your endpoint. ## Outbound rate limiting When `rateLimitEnabled` is true (default), Reloop caps outbound POSTs per endpoint at `maxRequestsPerMinute` (default **60**). Extra deliveries are delayed into the next minute window rather than dropped. ## Filtering Optional `filteringOptions` on an endpoint: - **`matchConditions`**: only deliver when every listed top-level `data` field equals the expected value (shallow equality). - **`excludeFields`**: strip listed top-level keys from `data` before POST (and in the stored delivery payload). Example: ```json { "filteringOptions": { "matchConditions": { "status": "bounced" }, "excludeFields": ["subject"] } } ``` ## Manual Replays You can manually replay any delivery from the dashboard or API. Replay creates a **new** delivery row (linked to the original) so history stays intact, then enqueues it on the delivery queue using the endpoint's current `maxRetries`. Navigate to the Webhooks page in your dashboard. Open the webhook endpoint you are using. Locate the delivery in the delivery logs. Trigger a manual redelivery. --- ## Learn More - [Webhooks Introduction](/docs/webhooks) - [Event Types](/docs/webhooks/event-types) - [Verify Webhooks Requests](/docs/webhooks/verify-webhooks-requests) --- # webhooks/testing-webhooks.mdx Source: https://reloop.sh/docs/webhooks/testing-webhooks Markdown: https://reloop.sh/docs/webhooks/testing-webhooks.md You can test a webhook before any real email goes out. There are two ways to do it: - **Send a test event to your webhook URL** from the dashboard. Use this when your endpoint is already deployed. - **Forward test events to localhost** with `reloop webhooks listen`. Use this while you build your handler on your own machine. You don't need ngrok or a public URL. Either way, Reloop signs the request and sends it **from its own servers**, exactly like a real delivery. Your endpoint doesn't need CORS headers, and your [signature check](/docs/webhooks/verify-webhooks-requests) runs unchanged. Test events are not stored. They don't show up in delivery logs, and they don't count toward retries or auto-disable. ## Send a test event to your webhook URL Go to [Webhooks](https://reloop.sh/dashboard/webhooks), open your webhook, and choose **Test**. Under **Send to**, keep **Webhook URL** selected. Pick one of the webhook's subscribed events. The right-hand panel shows the exact envelope that will be sent. Click **Send test event** (or press ⌘/Ctrl + Enter). The result shows the HTTP status, how long the request took, your endpoint's response body, and the signed headers that were sent. Paused or disabled webhooks can't receive tests at their URL. Resume the webhook first, or send to a local listener instead. ## Test on localhost with the CLI `reloop webhooks listen` opens a connection from your machine to Reloop. When you send a test event to the listener, Reloop pushes it down that connection, and the CLI POSTs it to your local server. Your server's response then travels back to the dashboard. ``` Dashboard ──► Reloop ══ connection opened by the CLI ══► reloop CLI ──► localhost:3000 ▲ │ └────────────── status · response body · timing ◄─────────┘ ``` The connection starts on your machine, so this works behind NAT, firewalls, and corporate networks. ```bash npm install -g reloop-cli reloop login ``` In CI or a container, set `RELOOP_API_KEY` instead of running `reloop login`. Run the app that handles webhooks, for example on `http://localhost:3000/webhooks`. ```bash reloop webhooks listen --forward-to localhost:3000/webhooks ``` ``` Ready. Forwarding webhook events to http://localhost:3000/webhooks Listener wls_o3g9g7vk7k82ule1s4d4at8x. Pick it as the target on a webhook's Test page. Ctrl-C to stop. ``` On the webhook's **Test** page, a **Local listener** option appears under **Send to** within a few seconds. Select it, pick an event, and click **Send test event**. The CLI prints one line per event: ``` 14:02:11 email.sent → POST http://localhost:3000/webhooks [200] 12ms ``` The dashboard shows the same status and your server's response body. Press Ctrl + C to stop. The listener disappears from the dashboard straight away. ### Signatures Requests forwarded to a listener are signed with the secret of **the webhook whose Test page you sent from**. Configure your local server with that webhook's signing secret (`whsec_…`) and verify requests as usual. See [Verify Webhooks Requests](/docs/webhooks/verify-webhooks-requests). ### Options | Flag | Description | | --- | --- | | `--forward-to`, `-f` | Local URL to POST events to. Accepts a full URL, `localhost:3000/webhooks`, or just a port (`3000` means `http://localhost:3000/`). | | `--json` | Print one JSON object per event, for scripts and agents. | | `--endpoint` | Use a self-hosted Reloop instead of Reloop Cloud. | | `--debug` | Log the connection to the relay. | ```bash # Forward to a port on localhost reloop webhooks listen -f 8080 # Pipe results into jq reloop webhooks listen -f localhost:3000/webhooks --json | jq '{type: .eventType, status}' ``` If the connection drops, for example when your laptop sleeps, the CLI reconnects with backoff and prints a warning. Its listener ID changes, so select the new listener in the dashboard. ## Troubleshooting **The listener doesn't appear in the dashboard.** Check that the CLI printed `Ready.` and is signed in to the same organization you're viewing (`reloop auth status`). The list refreshes every few seconds. **`Could not reach http://localhost:3000/webhooks`** The CLI is connected, but nothing is listening at `--forward-to`. Check that your server is running, and check the port and path. **`Local endpoint returned HTTP 4xx/5xx`** Your server received the request and rejected it. The response body is shown in the dashboard and the CLI. A `401` or `400` is usually a signature check that uses the wrong secret, or that re-serializes the JSON before verifying. **`The listener did not report a result within 15s`** Your server took longer than 10 seconds to respond. Return `2xx` quickly, and do slow work after responding. **`Outbound request to private/local IP address … is blocked`** You sent a test to a webhook URL that points at a private or local address. Reloop's servers can't reach your machine. Use a local listener instead. --- # webhooks/verify-webhooks-requests.mdx Source: https://reloop.sh/docs/webhooks/verify-webhooks-requests Markdown: https://reloop.sh/docs/webhooks/verify-webhooks-requests.md To ensure that webhook requests are authentic and coming from Reloop, verify the signature included in the request headers. This prevents unauthorized requests from reaching your application logic. ## Headers Every delivery includes: | Header | Description | | --- | --- | | `Reloop-Id` | Event id (`whev_…`), same as body `id` | | `Reloop-Timestamp` | Unix timestamp (seconds) used in the signature | | `Reloop-Signature` | `t=,v1=` | ## Signature algorithm 1. Read the **raw** request body bytes (do not re-serialize JSON). 2. Read `Reloop-Timestamp` (or parse `t=` from `Reloop-Signature`). 3. Compute HMAC-SHA256 over the string `` `${timestamp}.${rawBody}` `` using your endpoint secret (`whsec_…`). 4. Compare the hex digest to the `v1=` value with a constant-time compare. 5. Reject if the timestamp is older than ~5 minutes (replay protection). ```typescript secret: string; rawBody: string; signatureHeader: string; toleranceSeconds?: number; }): boolean { const parts = Object.fromEntries( input.signatureHeader.split(",").map((p) => { const [k, ...rest] = p.trim().split("="); return [k, rest.join("=")]; }), ); const timestamp = Number(parts.t); const v1 = parts.v1; if (!Number.isFinite(timestamp) || !v1) return false; const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > (input.toleranceSeconds ?? 300)) return false; const expected = createHmac("sha256", input.secret) .update(`${timestamp}.${input.rawBody}`) .digest("hex"); try { const a = Buffer.from(expected, "hex"); const b = Buffer.from(v1, "hex"); return a.length === b.length && timingSafeEqual(a, b); } catch { return false; } } ``` ## Payload shape ```json { "id": "whev_01h…", "type": "email.delivered", "created_at": "2026-07-22T12:00:00.000Z", "data": { "email_id": "em_…", "from": "hello@example.com", "to": ["user@example.com"], "subject": "Welcome", "status": "delivered" } } ``` ## Why verify webhooks? Verification is critical because it allows you to: - Confirm the request was signed with your endpoint secret - Reject forged events from third parties - Detect and drop replayed requests outside the time window --- # integrations/ai-tools/cli-agents.mdx Source: https://reloop.sh/docs/integrations/ai-tools/cli-agents Markdown: https://reloop.sh/docs/integrations/ai-tools/cli-agents.md The [Reloop CLI](/docs/resources/cli) is purpose-built for AI agent workflows: every command supports machine-readable JSON output, deterministic exit codes, and stdin piping. See the full [CLI reference](/docs/resources/cli) for all available commands. ## Agent Skills Install the CLI agent skill so your agent knows how to use the Reloop CLI effectively: ```bash npx skills add reloop/reloop-cli ``` ## Non-interactive mode Every CLI command supports `--json` for machine-readable output: - **Output**: JSON to stdout, no progress indicators - **Exit codes**: `0` for success, `1` for errors - **Errors**: Always include `message` and `code` fields ```json { "error": { "message": "No API key found", "code": "auth_error" } } ``` ## Piping from stdin Use `-` as the value of any file flag to read from stdin: ```bash echo "Your order has shipped." | reloop emails send \ --from "Acme " \ --to delivered@example.com \ --subject "Order update" \ --text-file - ``` This also works with `--html-file -` and `--file -` for [batch commands](#batch-sending). ## Batch sending Use `emails batch` to send up to 100 emails in a single request: ```bash cat emails.json | reloop emails batch --file - ``` ## Safe retries Use `--idempotency-key` to safely retry failed sends without risking duplicates: ```bash reloop emails send \ --from "Acme " \ --to delivered@example.com \ --subject "Welcome" \ --text "Hello!" \ --idempotency-key "welcome-user-123" ``` Idempotency keys are supported on both `emails send` and `emails batch` commands. ## Scheduling Use `--scheduled-at` to schedule emails for future delivery: ```bash reloop emails send \ --from "Acme " \ --to delivered@example.com \ --subject "Your trial ends soon" \ --text "Your free trial expires in 3 days." \ --scheduled-at "tomorrow at 9am ET" ``` You can cancel or reschedule with: ```bash reloop emails cancel reloop emails update --scheduled-at ``` ## Reading inbound emails The `emails receiving listen` command starts a long-running process that watches for new inbound emails: ```bash reloop emails receiving listen --json ``` To fetch a specific received email: ```bash reloop emails receiving get ``` Make sure you have a [verified domain](/docs/learn/domain) with receiving enabled. ## Closing the loop with webhooks The `webhooks listen` command subscribes to [webhook events](/docs/webhooks) and streams them to your terminal: ```bash reloop webhooks listen \ --url https://hostname.tailnet-name.ts.net \ --events email.delivered email.bounced email.received ``` Press `Ctrl+C` to stop listening. The webhook is automatically removed when you disconnect. To forward webhooks to a local development server, use `--forward-to`: ```bash reloop webhooks listen \ --url https://hostname.tailnet-name.ts.net \ --forward-to http://localhost:4321/api/webhook ``` The `--url` flag requires a publicly reachable URL. We recommend [Tailscale Funnel](https://tailscale.com/kb/1223/funnel), [ngrok](https://ngrok.com/), or [localtunnel](https://theboringtech.io/) for local development. If you prefer to set up webhooks manually, use `reloop webhooks create`. --- # integrations/ai-tools/mcp-prompts.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp-prompts Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp-prompts.md Once your client is connected to the [Reloop MCP server](/docs/integrations/ai-tools/mcp-server), you can ask for things in plain language. Here are prompts to start with, grouped by area. Swap in your own addresses, names, and details. Your assistant shows each tool call before it runs. Read sends and deletes before you approve them: email that's been sent can't be recalled. ## Emails > "Send a welcome email to user@example.com with a personalized greeting." Writes a welcome email and sends it from a verified domain in your organization. > "List all emails I sent today and show their delivery status." Pulls today's email logs and reports whether each one was delivered, bounced, or is still queued. > "Send a batch of onboarding emails to these five new users." Sends one onboarding email to each address you list, in a single batch. ## Contacts > "Add a new contact with their name and email." Creates a contact with the name and email you give it. Creating a contact doesn't send anything. > "Create a contact group for users who signed up this month." Creates a group and adds the contacts that signed up this month, so you can target them later. ## Campaigns > "Create a campaign for our product launch announcement." Drafts a launch campaign with a subject line and content, ready for you to review, schedule, or send. ## Domains > "Verify my domain and check the DNS configuration status." Checks your domain's DNS records, runs verification, and tells you which records are missing or wrong. ## Inbound > "Show me all inbound emails I received this week." Lists the emails your receiving domains got this week, with senders and subjects. ## Webhooks > "Set up a webhook to notify me when an email bounces." Creates a webhook subscribed to bounce events and sends them to the URL you choose. ## Templates > "Create a password reset email template that matches our brand." Builds a reusable password reset template with a variable for the reset link. ## Metrics > "Why did my bounce rate go up yesterday?" Compares yesterday's bounces with earlier days and points out the domains, recipients, or messages behind the change. ## Automation > "Build an automation that sends a welcome series when someone signs up." Sets up a flow that starts when a contact signs up and sends a sequence of welcome emails. ## Tips for better results - **Name the sender.** Say which address to send from, for example "from hello@example.com". It has to be on a verified domain. - **Ask before you act.** For bulk changes, ask for a preview first: "Show me who would get this before sending." - **Be specific about time.** "Today", "this week", and "yesterday" work, and so do exact dates. - **Mention Reloop.** If your assistant has several tools, saying "in Reloop" helps it pick the right one. --- # integrations/ai-tools/mcp-server.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp-server Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp-server.md The [Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open standard that lets AI assistants call external tools. The Reloop MCP server connects your assistant to your Reloop organization, so you can send email, manage contacts, check deliverability, and more by describing what you want in plain language. Reloop runs a hosted MCP server for you. Add one URL to your client, sign in with your Reloop account, and you're done. There's no API key to copy and nothing to install. | Setting | Value | |---|---| | **Server URL** | `https://reloop.sh/mcp` | | **Transport** | Streamable HTTP | | **Authentication** | Sign in with Reloop (OAuth) | The [MCP page in your dashboard](https://reloop.sh/dashboard/integrations/mcp) has setup steps, one-click install buttons, and copyable config for each client. ## What you can do The MCP server is built to do everything you can do in the Reloop dashboard: - **Emails**: Send transactional email with HTML or plain text, CC/BCC, reply-to, tags, templates, and scheduled delivery. Check what you sent and its delivery status. - **Contacts**: Look up, create, update, unsubscribe, and delete contacts. Manage custom properties, groups, and channel subscriptions. - **Campaigns**: Draft, schedule, and send campaigns to your audience. - **Templates**: Create and edit email templates and reuse them when sending. - **Automation**: Build flows such as a welcome series that runs when someone signs up. - **Inbox**: Read inbound email and attachments on your receiving domains. - **Metrics & logs**: Inspect email logs, bounces, and engagement, and ask why a number changed. - **Domains**: Add sending domains, check DNS records, and verify them. - **Webhooks & API keys**: Create and manage webhooks for email events, and manage API keys. See [example prompts](/docs/integrations/ai-tools/mcp-prompts) for ideas. Sending email is real and can't be undone. Most clients ask you to approve each tool call; read the sender, recipients, and content before you approve a send or a delete. ## Quick setup Pick your client for full steps, including how to verify the connection and fix common problems. Add Reloop with one `claude mcp add` command. Add a custom connector on the web or in the desktop app. One-click install or `.cursor/mcp.json`. GitHub Copilot agent mode with `.vscode/mcp.json`. Add Reloop to `~/.codex/config.toml`. The short version for each: ```bash claude mcp add --transport http reloop https://reloop.sh/mcp ``` Then run `/mcp` in Claude Code, pick **reloop**, and choose **Authenticate**. In Claude, go to **Settings → Connectors**, click **Add custom connector**, name it `Reloop`, and use this URL: ```text https://reloop.sh/mcp ``` Click **Connect** and sign in to Reloop. Add to `.cursor/mcp.json` (or `~/.cursor/mcp.json` for every project): ```json { "mcpServers": { "reloop": { "url": "https://reloop.sh/mcp" } } } ``` Then click **Connect** next to reloop in **Cursor Settings → MCP & Integrations**. Add to `.vscode/mcp.json`: ```json { "servers": { "reloop": { "type": "http", "url": "https://reloop.sh/mcp" } } } ``` Click **Start** above the server and sign in to Reloop when asked. Add to `~/.codex/config.toml`: ```toml [mcp_servers.reloop] url = "https://reloop.sh/mcp" ``` Then sign in: ```bash codex mcp login reloop ``` Point any client that supports Streamable HTTP at this URL: ```text https://reloop.sh/mcp ``` It discovers Reloop's OAuth server and signs you in through the browser. If your client can only launch local servers, use the [local server](#local-server-stdio) instead. ## How sign-in works The hosted server is an OAuth protected resource. You don't need an API key. 1. Your client connects to `https://reloop.sh/mcp` and gets a `401` with a `WWW-Authenticate` challenge. 2. It reads `https://reloop.sh/.well-known/oauth-protected-resource/mcp` to find Reloop's authorization server, and registers itself automatically. 3. Your browser opens the Reloop sign-in page. You sign in, pick the organization the client can access, and approve. 4. The client sends the access token as `Authorization: Bearer ` on every request. Tokens are short-lived, scoped to the MCP server, and tied to the organization you approved. Reloop checks every call against your permissions, so the assistant can only do what your account can do. ### Choosing an organization If you belong to several organizations, you pick one on the consent screen. To switch, sign in again from your client and pick a different one. Clients that support custom headers can also send an `x-organization-id` header to select an organization per request. ## Local server (stdio) Use the local server when your client can't connect to a remote URL, or when you prefer an API key over signing in. Your client starts `reloop-mcp` as a child process and talks to it over stdin/stdout. You'll need Node.js 20 or later (for `npx`) and a [Reloop API key](https://reloop.sh/dashboard/api-keys). Replace `rl_prod_your_api_key` with your key. ```bash claude mcp add reloop --env RELOOP_API_KEY=rl_prod_your_api_key -- npx -y reloop-mcp ``` Add to `claude_desktop_config.json` (Claude Desktop) or `.cursor/mcp.json` (Cursor): ```json { "mcpServers": { "reloop": { "command": "npx", "args": ["-y", "reloop-mcp"], "env": { "RELOOP_API_KEY": "rl_prod_your_api_key" } } } } ``` Add to `.vscode/mcp.json`: ```json { "servers": { "reloop": { "type": "stdio", "command": "npx", "args": ["-y", "reloop-mcp"], "env": { "RELOOP_API_KEY": "rl_prod_your_api_key" } } } } ``` Add to `~/.codex/config.toml`: ```toml [mcp_servers.reloop] command = "npx" args = ["-y", "reloop-mcp"] env = { RELOOP_API_KEY = "rl_prod_your_api_key" } ``` Add to `~/.gemini/settings.json`: ```json { "mcpServers": { "reloop": { "command": "npx", "args": ["-y", "reloop-mcp"], "env": { "RELOOP_API_KEY": "rl_prod_your_api_key" } } } } ``` Add to `opencode.json`: ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "reloop": { "type": "local", "command": ["npx", "-y", "reloop-mcp"], "enabled": true, "environment": { "RELOOP_API_KEY": "rl_prod_your_api_key" } } } } ``` For any other stdio client, run `npx -y reloop-mcp` (or `bunx reloop-mcp`) with `RELOOP_API_KEY` in the environment. An API key is scoped to the organization it was created in, so the local server always acts in that organization. ## Self-hosted Reloop If you run your own Reloop instance, set `RELOOP_BASE_URL` to its origin. Nothing is tied to `reloop.sh` beyond the default. For the hosted-style setup, your instance serves the MCP endpoint at `https:///mcp` and every remote setup above works with that URL in place of `https://reloop.sh/mcp`. For the local server, add `RELOOP_BASE_URL` next to your key: ```json { "mcpServers": { "reloop": { "command": "npx", "args": ["-y", "reloop-mcp"], "env": { "RELOOP_API_KEY": "rl_prod_your_api_key", "RELOOP_BASE_URL": "https://mail.example.com" } } } } ``` ## Run the HTTP server yourself You can run the Streamable HTTP server on your own infrastructure. It's stateless: there are no sessions, so you can run several instances behind any load balancer. ```bash bunx reloop-mcp --transport http --host 0.0.0.0 --port 3000 ``` The standalone HTTP server needs [Bun](https://bun.sh). On Node.js, use the Docker image instead: ```bash docker build -t reloop/mcp . docker run --rm -p 3000:3000 -e RELOOP_BASE_URL=https://mail.example.com reloop/mcp ``` Clients connect to `http://:3000/mcp` and authenticate on every request with one of: - An OAuth access token: `Authorization: Bearer ` (recommended; clients get this by signing in) - A Reloop API key: `Authorization: Bearer rl_prod_...` or `x-api-key: rl_prod_...` `RELOOP_API_KEY` is not used by the HTTP transport. For example, to connect Claude Code with an API key: ```bash claude mcp add --transport http reloop http://127.0.0.1:3000/mcp \ --header "Authorization: Bearer rl_prod_your_api_key" ``` Check that the server is up: ```bash curl -s http://127.0.0.1:3000/mcp/healthz ``` ```json { "status": "ok", "name": "reloop", "version": "0.1.0" } ``` `GET /healthz` returns the same response. ## Options **CLI flags** | Flag | Default | Description | |---|---|---| | `--transport ` | `stdio` | Transport to serve | | `--host ` | `127.0.0.1` | HTTP bind hostname | | `--port ` | `3000` | HTTP bind port | | `--base-url ` | `https://reloop.sh` | Origin of the Reloop instance | | `--version` | | Print the version and exit | | `--help` | | Print help and exit | **Environment variables** | Variable | Default | Description | |---|---|---| | `RELOOP_API_KEY` | | Reloop API key. Required for stdio, ignored by HTTP | | `RELOOP_BASE_URL` | `https://reloop.sh` | Origin of the Reloop instance | | `RELOOP_MCP_RESOURCE` | `/mcp` | Canonical MCP resource identifier. Must match the auth server's `MCP_RESOURCE` | | `RELOOP_AUTH_ISSUER` | `/api/auth/v1` | OAuth issuer advertised in the protected resource metadata | | `RELOOP_TIMEOUT_MS` | `30000` | Per-request timeout to Reloop, from 1000 to 600000 | | `HOST` | `127.0.0.1` | HTTP bind hostname | | `PORT` | `3000` | HTTP bind port | | `MCP_ALLOWED_HOSTS` | `*` | Comma-separated `Host` header allowlist. `*` or unset accepts any host | | `MCP_ALLOWED_ORIGINS` | loopback origins | Comma-separated `Origin` header allowlist | | `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`, or `silent` | | `OTEL_EXPORTER_OTLP_ENDPOINT` | unset | OTLP/HTTP endpoint (for example SigNoz). When set, every log line is also exported there | | `OTEL_EXPORTER_OTLP_HEADERS` | unset | Headers for the OTLP endpoint as `key=value,key2=value2`, for example `signoz-ingestion-key=` | Even with `MCP_ALLOWED_HOSTS` unset, every request still needs an OAuth token or API key. Set it to restrict which hostnames the server answers on. ## Security - Credentials are per request. The server forwards the caller's token or key to Reloop and never stores, caches, or shares it. - API keys and tokens are redacted from logs, and email content is never logged. - `Host` and `Origin` headers are validated to block DNS-rebinding attacks from a browser. - `RELOOP_BASE_URL` is server configuration, never a tool parameter, so a model can't redirect traffic to another host. - Tools that send email or delete data are marked destructive, so clients can ask you to confirm before they run. ## Local development The server's source is at [github.com/reloop-labs/reloop-mcp](https://github.com/reloop-labs/reloop-mcp). ```bash git clone https://github.com/reloop-labs/reloop-mcp.git cd reloop-mcp bun install bun run dev:stdio # stdio transport bun run dev:http # HTTP transport on http://127.0.0.1:3000/mcp, reloads on change bun run build # builds dist/cli.js ``` To point a stdio client at your local build, replace `npx -y reloop-mcp` with `node`: ```json { "mcpServers": { "reloop": { "command": "node", "args": ["ABSOLUTE_PATH_TO_PROJECT/dist/cli.js"], "env": { "RELOOP_API_KEY": "rl_prod_your_api_key" } } } } ``` Before opening a pull request, run `bun run lint`, `bun run typecheck`, and `bun test`. ## Test with MCP Inspector [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is a browser UI for calling MCP tools by hand. ### Stdio From the `reloop-mcp` folder: ```bash npx @modelcontextprotocol/inspector -e RELOOP_API_KEY=rl_prod_your_api_key bun src/cli.ts ``` Inspector opens in your browser with the server already configured. Click **Connect**, then **List Tools**. ### HTTP 1. Start the server: ```bash bun run dev:http ``` 2. Start Inspector: ```bash npx @modelcontextprotocol/inspector ``` 3. In the browser, choose **Streamable HTTP** and set the URL to `http://127.0.0.1:3000/mcp`. 4. Add a header `Authorization: Bearer rl_prod_your_api_key` and turn it on. 5. Click **Connect**, then **List Tools**. To test against the hosted server, use `https://reloop.sh/mcp` without a header. Inspector walks you through the OAuth sign-in. ## Troubleshooting Remove the saved sign-in from your client and authenticate again. Each client page above has the exact steps. If you're on a self-hosted instance, check that `RELOOP_MCP_RESOURCE` matches the auth server's `MCP_RESOURCE`. Sign in again and pick the right organization on the consent screen, or send an `x-organization-id` header if your client supports custom headers. With an API key, create the key in the organization you want. The `from` address must use a sending domain that's verified in your organization. Ask your assistant to list your domains, or check [Domains](https://reloop.sh/dashboard/domain). The stdio transport needs `RELOOP_API_KEY`, and the key has to look like a Reloop key (for example `rl_prod_...`). Copy it again from the [API keys page](https://reloop.sh/dashboard/api-keys). The server reached something that isn't a Reloop API, which usually means `RELOOP_BASE_URL` is wrong. Set it to the origin of your instance, without a path. Retry reads. Don't blindly retry a send: the first one may have gone out. Check your email logs first. You can raise `RELOOP_TIMEOUT_MS` if you run the server yourself. --- # integrations/ai-tools/mcp/claude-code.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp/claude-code Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp/claude-code.md [Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's coding agent for the terminal. Once you add Reloop, Claude Code can send email, manage contacts, check domains, and more from your terminal session. ## Prerequisites - Claude Code installed and signed in - A [Reloop account](https://reloop.sh/dashboard) - A [verified domain](https://reloop.sh/dashboard/domain) if you want to send email ## Setup Run this in your terminal: ```bash claude mcp add --transport http reloop https://reloop.sh/mcp ``` This adds Reloop to the current project. Add `--scope user` to make it available in every project: ```bash claude mcp add --transport http --scope user reloop https://reloop.sh/mcp ``` Start Claude Code and run: ```text /mcp ``` Pick **reloop** and choose **Authenticate**. Your browser opens the Reloop sign-in page. Sign in, choose the organization Claude Code can access, and approve. ## Verify it works Run `/mcp` again. **reloop** should show as connected, with its tools listed. From your shell, `claude mcp list` also shows the server and its status. Then ask something read-only to confirm it reaches your organization: > "List my Reloop domains and tell me which ones are verified." ## Example prompts - "Send a welcome email to user@example.com with a personalized greeting." - "Add a new contact for jane@example.com named Jane Doe." - "List all emails I sent today and show their delivery status." See [more example prompts](/docs/integrations/ai-tools/mcp-prompts). ## Troubleshooting Run `/mcp`, pick **reloop**, and choose **Authenticate** again. If sign-in keeps failing, choose **Clear authentication** first, then authenticate. Clear authentication from `/mcp`, authenticate again, and pick the right organization on the consent screen. You can also pin an organization with a header: ```bash claude mcp remove reloop claude mcp add --transport http reloop https://reloop.sh/mcp \ --header "x-organization-id: YOUR_ORGANIZATION_ID" ``` You may have added it in a different project. Run `claude mcp list` in the project you're working in, or add it again with `--scope user`. Use the [local server](/docs/integrations/ai-tools/mcp-server#local-server-stdio): ```bash claude mcp add reloop --env RELOOP_API_KEY=rl_prod_your_api_key -- npx -y reloop-mcp ``` --- # integrations/ai-tools/mcp/claude.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp/claude Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp/claude.md Add Reloop to [Claude](https://claude.ai) as a custom connector to work with your email from any chat. The connector works on the web and in the Claude desktop app, and you only set it up once. ## Prerequisites - A Claude plan that supports custom connectors - On Team and Enterprise plans, an owner has to add the connector under **Organization settings** before members can connect - A [Reloop account](https://reloop.sh/dashboard) - A [verified domain](https://reloop.sh/dashboard/domain) if you want to send email ## Setup In Claude, go to **Settings → Connectors** and click **Add custom connector**. Name it `Reloop` and use this URL: ```text https://reloop.sh/mcp ``` Leave the advanced OAuth fields empty. Reloop registers the client automatically. Click **Connect**. Sign in to Reloop, choose the organization Claude can access, and approve. Open the tools menu in a chat and turn on **Reloop**. ## Verify it works In **Settings → Connectors**, Reloop shows as connected. In a new chat with Reloop turned on, ask something read-only: > "List my Reloop domains and tell me which ones are verified." Claude asks permission before it calls a Reloop tool the first time. ## Example prompts - "Show me all inbound emails I received this week." - "Why did my bounce rate go up yesterday?" - "Create a password reset email template that matches our brand." See [more example prompts](/docs/integrations/ai-tools/mcp-prompts). ## Troubleshooting In **Settings → Connectors**, open Reloop, disconnect it, and click **Connect** again. Disconnect Reloop, connect again, and pick the right organization on the consent screen. Custom connectors can't send extra headers, so the consent screen is how you choose. Your plan may not include custom connectors, or on Team and Enterprise plans an owner has to add Reloop first. Check that Reloop is turned on in the tools menu for that chat, and mention Reloop in your prompt. --- # integrations/ai-tools/mcp/codex.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp/codex Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp/codex.md [Codex](https://developers.openai.com/codex) is OpenAI's coding agent for the terminal and IDE. The CLI and the IDE extension share one config file, so you set up Reloop once for both. ## Prerequisites - The Codex CLI or IDE extension installed - A [Reloop account](https://reloop.sh/dashboard) - A [verified domain](https://reloop.sh/dashboard/domain) if you want to send email ## Setup Add this to `~/.codex/config.toml`: ```toml [mcp_servers.reloop] url = "https://reloop.sh/mcp" ``` Run the login command: ```bash codex mcp login reloop ``` Your browser opens the Reloop sign-in page. Sign in, choose the organization Codex can access, and approve. ## Verify it works Run `codex mcp list` and check that reloop is listed. In a Codex session, run `/mcp` to see its tools. Then ask something read-only: > "List my Reloop domains and tell me which ones are verified." ## Example prompts - "Create a campaign for our product launch announcement." - "Create a contact group for users who signed up this month." - "Build an automation that sends a welcome series when someone signs up." See [more example prompts](/docs/integrations/ai-tools/mcp-prompts). ## Troubleshooting Log out and back in: ```bash codex mcp logout reloop codex mcp login reloop ``` Update Codex to the latest version. Remote servers with OAuth need a recent release. Log out, log in again, and pick the right organization on the consent screen. Or pin one with a header: ```toml [mcp_servers.reloop] url = "https://reloop.sh/mcp" http_headers = { "x-organization-id" = "YOUR_ORGANIZATION_ID" } ``` Use the [local server](/docs/integrations/ai-tools/mcp-server#local-server-stdio): ```toml [mcp_servers.reloop] command = "npx" args = ["-y", "reloop-mcp"] env = { RELOOP_API_KEY = "rl_prod_your_api_key" } ``` --- # integrations/ai-tools/mcp/cursor.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp/cursor Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp/cursor.md Add Reloop to [Cursor](https://cursor.com) so the agent can send test emails, look up contacts, check domains, and manage templates while you build. ## Prerequisites - Cursor installed - A [Reloop account](https://reloop.sh/dashboard) - A [verified domain](https://reloop.sh/dashboard/domain) if you want to send email ## Setup The quickest way is the **Add to Cursor** button on the [Cursor page in your dashboard](https://reloop.sh/dashboard/integrations/mcp/cursor). It opens Cursor with Reloop filled in. Or add this to `.cursor/mcp.json` in your project. Use `~/.cursor/mcp.json` to make it available in every project. ```json { "mcpServers": { "reloop": { "url": "https://reloop.sh/mcp" } } } ``` Open **Cursor Settings → MCP & Integrations** and click **Connect** next to **reloop**. Your browser opens the Reloop sign-in page. Sign in, choose the organization Cursor can access, and approve. ## Verify it works In **Cursor Settings → MCP & Integrations**, reloop shows a green status dot and lists its tools. Then ask the agent something read-only: > "List my Reloop domains and tell me which ones are verified." ## Example prompts - "Send a test email to me@example.com with the HTML from emails/welcome.html." - "Create a webhook that notifies https://example.com/hooks/reloop when an email bounces." - "Verify my domain and check the DNS configuration status." See [more example prompts](/docs/integrations/ai-tools/mcp-prompts). ## Troubleshooting In **Cursor Settings → MCP & Integrations**, turn reloop off and on, then click **Connect**. If that doesn't help, remove the server, add it again, and sign in. Sign in again and pick the right organization on the consent screen, or pin one with a header: ```json { "mcpServers": { "reloop": { "url": "https://reloop.sh/mcp", "headers": { "x-organization-id": "YOUR_ORGANIZATION_ID" } } } } ``` Check that reloop is turned on in settings, and mention Reloop in your prompt. Use the [local server](/docs/integrations/ai-tools/mcp-server#local-server-stdio) with `npx -y reloop-mcp` and `RELOOP_API_KEY`. --- # integrations/ai-tools/mcp/vscode.mdx Source: https://reloop.sh/docs/integrations/ai-tools/mcp/vscode Markdown: https://reloop.sh/docs/integrations/ai-tools/mcp/vscode.md Add Reloop to [Visual Studio Code](https://code.visualstudio.com) so GitHub Copilot in agent mode can send email, manage contacts, and check your Reloop setup from the editor. ## Prerequisites - VS Code with GitHub Copilot and agent mode available - A [Reloop account](https://reloop.sh/dashboard) - A [verified domain](https://reloop.sh/dashboard/domain) if you want to send email ## Setup The quickest way is the **Install in VS Code** button on the [VS Code page in your dashboard](https://reloop.sh/dashboard/integrations/mcp/vscode). Or add this to `.vscode/mcp.json` in your workspace: ```json { "servers": { "reloop": { "type": "http", "url": "https://reloop.sh/mcp" } } } ``` Click **Start** above the server in `mcp.json`, or run **MCP: List Servers** from the Command Palette and start **reloop**. VS Code asks you to sign in to Reloop in the browser. Sign in, choose the organization VS Code can access, and approve. ## Verify it works Run **MCP: List Servers**. reloop shows as running. Open Copilot Chat in **Agent** mode and click the tools icon; the Reloop tools are listed. Then ask something read-only: > "List my Reloop domains and tell me which ones are verified." ## Example prompts - "Add a new contact with their name and email." - "Send a batch of onboarding emails to these five new users." - "List all emails I sent today and show their delivery status." See [more example prompts](/docs/integrations/ai-tools/mcp-prompts). ## Troubleshooting Run **MCP: List Servers**, pick reloop, and restart it. To sign in from scratch, sign out of Reloop from the **Accounts** menu in the activity bar, then start the server again. Sign out, start the server again, and pick the right organization on the consent screen. Or pin one with a header: ```json { "servers": { "reloop": { "type": "http", "url": "https://reloop.sh/mcp", "headers": { "x-organization-id": "YOUR_ORGANIZATION_ID" } } } } ``` Make sure Chat is in **Agent** mode and the Reloop tools are checked in the tools picker. Use the [local server](/docs/integrations/ai-tools/mcp-server#local-server-stdio) with `npx -y reloop-mcp` and `RELOOP_API_KEY`. --- # integrations/ai-tools/openclaw-guide.mdx Source: https://reloop.sh/docs/integrations/ai-tools/openclaw-guide Markdown: https://reloop.sh/docs/integrations/ai-tools/openclaw-guide.md Learn how to give your AI agent a dedicated email inbox using Reloop, so it can send and receive emails autonomously. ## Why should I give my agent an inbox? An AI agent with its own email address can: - Sign up for its own accounts to GitHub, hosting platforms, and more, so you don't need to share your own credentials. - Process attachments (like receipts, invoices, etc.) and act accordingly - Receive newsletters, parse them, and send the most important information to you - Send you daily reports and digests - Send and receive emails on your behalf ## How to set up an inbox for OpenClaw ### Step 1: Install the skill Tell your agent: ``` Let's get you set up with an email inbox! Install the reloop-skills from https://github.com/reloop-labs/reloop-skills and review them before continuing. ``` Alternatively, you can install the skills using the [Reloop CLI](/docs/resources/cli): ```bash npx skills add reloop/reloop-skills ``` ### Step 2: Get an API key 1. Open the [API Keys](https://reloop.sh/dashboard/api-keys) page 2. Choose a name and permission scope 3. Create the API key Store the key securely: 1. SSH into your agent's machine and store the API key in an `.env` file. 2. Store the API key in a password manager like 1Password, and give your agent access to its own vault. This can be done using a [1Password Service Account](https://developer.1password.com/docs/service-accounts/) on team plans. ### Step 3: Verify a domain We [strongly recommend](/docs/learn/domain) using a subdomain like `agent.example.com` rather than your root `example.com` domain. This keeps your agent's email reputation separate. You can set up a domain using the [Reloop CLI](/docs/resources/cli) or through the dashboard: 1. Go to the [Domains tab](https://reloop.sh/dashboard/domain) and add a domain 2. Select a subdomain and region 3. Configure DNS: - **Auto Configure**: Automatically configure DNS records if your provider supports it. - **Go to (provider)**: Open your provider's website to add records manually. - **I've added the records**: Manually add DNS records via your DNS provider. 4. Enable receiving on the domain 5. Add the DNS records and wait for verification See our [guide on verifying a domain](/docs/learn/domain) for detailed instructions. ### Step 4: Use webhooks to receive emails **Ask your agent to set up a webhook server:** ``` I want you to be able to receive emails, too. Let's set up a webhook to receive emails at my domain using the agent email inbox agent skill. ``` **Set up a tunnel:** Use [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) to expose your agent's webhook server: ```bash tailscale funnel 3000 ``` This gives you a public URL like `https://hostname.tailnet-name.ts.net`. **Give your agent access to secrets securely** (see [Step 2](#step-2-get-an-api-key)). **Add the webhook to Reloop:** ``` Let's add a webhook to Reloop using the server you just built. Use the email.received event, as instructed by the reloop-skills agent skill, and securely save the returned webhook signing secret. ``` **Test receiving an email:** ```typescript const reloop = new Reloop(process.env.RELOOP_API_KEY); // Security: strict allowlist const ALLOWED_SENDERS = ['your@email.com']; async function handler(req) { const payload = await req.text(); const id = req.headers.get('svix-id'); const timestamp = req.headers.get('svix-timestamp'); const signature = req.headers.get('svix-signature'); if (!id || !timestamp || !signature) { return new Response('Missing headers', { status: 400 }); } const event = reloop.webhooks.verify({ payload, headers: { id, timestamp, signature }, webhookSecret: process.env.RELOOP_WEBHOOK_SECRET, }); if (event.type === 'email.received') { // Security check if (!ALLOWED_SENDERS.includes(event.data.from.toLowerCase())) { return new Response('OK', { status: 200 }); } // Get full email const { data: email } = await reloop.emails.receiving.get( event.data.email_id, ); // Notify user instantly await notifyUser(email); } return new Response('OK', { status: 200 }); } ``` See our [guide on receiving emails with Reloop](/docs/guides/receiving-emails) for more details. ### Step 5: Hook into OpenClaw's APIs for instant notifications **Ask your agent to hook into the Gateway API:** ``` Use the OpenClaw Gateway API to notify me instantly when you receive an email webhook call from Reloop. Use the reloop-skills agent skill for guidance. ``` **Test instant notifications** by sending an email to your agent's address and verifying it receives it. ## Security considerations The [Agent Email Inbox skill](https://github.com/reloop-labs/reloop-skills/blob/main/skills/agent-email-inbox/SKILL.md#security-levels) includes security guidelines with five levels: 1. **Strict Allowlist**: Only allow emails from specific senders. Recommended for most use cases. 2. **Domain Allowlist**: Allow emails from any sender from a given domain (e.g. anyone from `example.com`). 3. **Content Filtering with Sanitization**: Accept emails from anyone, but sanitize content to remove potential injection attempts. 4. **Sandboxed Processing**: Process all emails but in a restricted context where the agent has limited capabilities. 5. **Human-in-the-Loop**: Process all emails but require human approval for each email. Review the [Reloop Skill](https://github.com/reloop-labs/reloop-skills/blob/main/skills/agent-email-inbox/SKILL.md#security-levels) for detailed security guidance. If you have questions, [contact support](https://reloop.sh/help). --- # integrations/agent-skills/agent-email-inbox.mdx Source: https://reloop.sh/docs/integrations/agent-skills/agent-email-inbox Markdown: https://reloop.sh/docs/integrations/agent-skills/agent-email-inbox.md Give your AI agent a secure email inbox to receive and act on inbound emails. This skill provides your agent with the knowledge to set up, secure, and manage an email inbox for receiving messages. ## Installation The Agent Email Inbox skill is part of the [reloop-skills](https://github.com/reloop-labs/reloop-skills) collection: ```bash npx skills add reloop/reloop-skills ``` ## Features - **Webhook-based receiving**: Set up webhooks to receive inbound emails in real-time - **Email parsing**: Extract structured data from incoming emails (sender, subject, body, attachments) - **Attachment handling**: Download and process email attachments securely - **Security guidelines**: Built-in security levels from strict allowlists to sandboxed processing - **Event-driven architecture**: React to incoming emails with automated workflows ## Security Levels The skill includes five security levels you can implement based on your needs: 1. **Strict Allowlist**: Only process emails from pre-approved senders. Recommended for most use cases. 2. **Domain Allowlist**: Allow emails from any sender at approved domains. 3. **Content Filtering**: Accept all emails but sanitize content before processing. 4. **Sandboxed Processing**: Process in a restricted context with limited agent capabilities. 5. **Human-in-the-Loop**: Queue all emails for human approval before agent processing. ## How it works Your agent needs a verified domain with receiving enabled. See [Domains](/docs/learn/domain). Your agent creates an HTTP endpoint to receive webhook events from Reloop. Subscribe to `email.received` events via the Reloop API or CLI. When an email arrives, Reloop sends a webhook to your endpoint. Your agent verifies the signature, fetches the full email, and processes it. ## Learn More - [OpenClaw Guide](/docs/integrations/ai-tools/openclaw-guide): Step-by-step guide for setting up an agent inbox - [Receiving Emails](/docs/guides/receiving-emails): General guide on receiving emails with Reloop - [Webhooks](/docs/webhooks): Webhook setup and event types - [View on GitHub](https://github.com/reloop-labs/reloop-skills/tree/main/skills/agent-email-inbox) --- # integrations/agent-skills/email-best-practices.mdx Source: https://reloop.sh/docs/integrations/agent-skills/email-best-practices Markdown: https://reloop.sh/docs/integrations/agent-skills/email-best-practices.md A comprehensive skill that teaches your AI agent best practices for building production-ready email systems. This skill covers deliverability, authentication, content guidelines, and compliance. ## Installation ```bash npx skills add reloop/email-best-practices ``` ## What's covered ### Deliverability - SPF, DKIM, and DMARC authentication setup - Domain warmup strategies for new sending domains - Reputation management and monitoring - Bounce handling and suppression lists - Throttling and rate limiting best practices ### Content Guidelines - Subject line optimization for engagement - Plain text vs HTML email best practices - Image-to-text ratio recommendations - Call-to-action (CTA) best practices - Mobile-responsive email design ### Compliance - CAN-SPAM Act requirements - GDPR considerations for email - Unsubscribe link requirements - Physical address requirements - Consent management (opt-in/opt-out) ### Transactional vs Marketing - When to use transactional emails vs marketing emails - Separate sending domains and IP pools - Appropriate content for each type - Legal distinctions and requirements ### Error Handling - Retry strategies for failed sends - Handling bounces (hard vs soft) - Managing complaint feedback loops - Monitoring delivery rates and engagement metrics ## Rules The skill provides your agent with actionable rules such as: - Always include a plain text version alongside HTML - Never use a no-reply sender address for marketing emails - Always verify your sending domain before sending at scale - Use dedicated subdomains for transactional and marketing email - Implement exponential backoff for API retries - Always include an unsubscribe mechanism in marketing emails ## Learn More - [Sending Emails](/docs/learn/emails): Guide to sending emails with Reloop - [Domains](/docs/learn/domain): Domain verification and DNS configuration - [View on GitHub](https://github.com/reloop-labs/reloop-skills/tree/main/skills/email-best-practices) --- # integrations/agent-skills/reloop-skill.mdx Source: https://reloop.sh/docs/integrations/agent-skills/reloop-skill Markdown: https://reloop.sh/docs/integrations/agent-skills/reloop-skill.md Send emails through the Reloop API with AI agents. This skill gives your agent comprehensive knowledge of the Reloop API, including sending, receiving, and managing emails. ## Installation The Reloop skill is part of the [reloop-skills](https://github.com/reloop-labs/reloop-skills) collection. It also includes the [Agent Email Inbox](/docs/integrations/agent-skills/agent-email-inbox) skill. ```bash npx skills add reloop/reloop-skills ``` ## Advantages - **Single and batch email sending**: Send individual emails or batch up to 100 emails per request. - **Built-in error handling and retry logic**: Automatic retries with exponential backoff for transient failures. - **Idempotency key support**: Prevent duplicate sends with idempotency keys for safe retries. - **Multi-language SDK support**: Works with Node.js, Python, Ruby, Go, and other supported SDKs. - **Automatic activation for email tasks**: AI agents automatically use this skill when email sending is needed. ## What's included The skill teaches your agent: - How to authenticate with the Reloop API - Sending emails (single and batch) - Managing contacts and audiences - Working with templates - Handling webhooks and events - Domain verification and configuration - Best practices for deliverability ## Learn More - [Reloop API Reference](/docs/api) - [SDKs](/docs/resources/sdks) - [View on GitHub](https://github.com/reloop-labs/reloop-skills/tree/main/skills/reloop) --- # resources/cli.mdx Source: https://reloop.sh/docs/resources/cli Markdown: https://reloop.sh/docs/resources/cli.md # CLI Coming soon. --- # resources/sdks.mdx Source: https://reloop.sh/docs/resources/sdks Markdown: https://reloop.sh/docs/resources/sdks.md Official Node.js SDK for server-side JavaScript and TypeScript. Official Python client library for the Reloop API. Official Go client library for the Reloop API. Official Java SDK for JVM-based applications. Official PHP library for integrating with Reloop. Official Ruby gem for integrating with Reloop. Official Rust crate for integrating with Reloop. Official Elixir library for integrating with Reloop. Official .NET library for C# and other JVM/.NET applications. ## Missing your language? We're actively expanding our SDK coverage. If your language or framework isn't listed above, you can: Integrate directly with our raw REST API endpoints. Open an issue on GitHub to request or upvote a new SDK. All SDKs are open-source and contributions are welcome. --- # resources/security.mdx Source: https://reloop.sh/docs/resources/security Markdown: https://reloop.sh/docs/resources/security.md # Security Coming soon. --- # examples/go/gin.mdx Source: https://reloop.sh/docs/examples/go/gin Markdown: https://reloop.sh/docs/examples/go/gin.md ## Installation Install the official Go SDK: ```bash go get github.com/reloop-labs/reloop-go ``` ## Sending an Email ```go package main "context" "net/http" "os" "github.com/gin-gonic/gin" "github.com/reloop-labs/reloop-go" ) func main() { r := gin.Default() client := reloop.NewClient(os.Getenv("RELOOP_API_KEY")) r.POST("/send-email", func(c *gin.Context) { params := &reloop.SendEmailParams{ From: "onboarding@reloop.sh", To: []string{"delivered@resend.dev"}, Subject: "Hello from Gin", HTML: "

Congrats on sending your first email via Reloop from Go Gin!

", } res, err := client.Emails.Send(context.Background(), params) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, res) }) r.Run(":8080") } ``` --- # examples/nodejs/express.mdx Source: https://reloop.sh/docs/examples/nodejs/express Markdown: https://reloop.sh/docs/examples/nodejs/express.md ## Installation Install the official Node.js SDK: ```bash npm install reloop-email ``` ## Setup Environment Variables Add your Reloop API key to your environment variables or a `.env` file: ```env RELOOP_API_KEY=rl_your_api_key_here ``` ## Sending an Email Here is an example of an Express route that sends an email: ```typescript const app = express(); const reloop = new Reloop({ apiKey: process.env.RELOOP_API_KEY }); app.post('/send-email', async (req, res) => { try { const data = await reloop.mail.send({ from: 'onboarding@reloop.sh', to: 'delivered@resend.dev', subject: 'Hello from Express', html: '

Congrats on sending your first email via Reloop from an Express app!

', }); res.json(data); } catch (error) { res.status(500).json({ error }); } }); app.listen(3000, () => { console.log('App listening on port 3000'); }); ``` --- # examples/nodejs/nextjs.mdx Source: https://reloop.sh/docs/examples/nodejs/nextjs Markdown: https://reloop.sh/docs/examples/nodejs/nextjs.md ## Installation ```bash npm install reloop-email ``` ## Setup environment variables Add your Reloop API key to `.env.local` (server-only; do not use `NEXT_PUBLIC_`): ```env RELOOP_API_KEY=rl_your_api_key_here ``` ## Shared client ```typescript // lib/reloop.ts apiKey: process.env.RELOOP_API_KEY!, }); ``` ## Route Handler ```typescript // app/api/send-email/route.ts try { const { to, subject, html, text } = await request.json(); const { response, emailError } = await reloop.mail.send({ from: "Acme ", to, subject: subject ?? "Hello from Next.js", html: html ?? "

Congrats on sending your first email via Reloop!

", text: text ?? "Congrats on sending your first email via Reloop!", }); if (emailError) { return NextResponse.json({ error: emailError }, { status: 502 }); } return NextResponse.json(response); } catch (error) { return NextResponse.json({ error }, { status: 500 }); } } ``` ## Server Action ```typescript // app/actions/send-email.ts "use server"; const { response, emailError } = await reloop.mail.send({ from: "Acme ", to: email, subject: "Welcome", html: "

Thanks for signing up.

", text: "Thanks for signing up.", }); if (emailError) { return { ok: false as const, error: "Failed to send" }; } return { ok: true as const, messageId: response?.messageId }; } ``` ## Full guide For forms, auth, Edge runtime, Vercel deploy, and a full comparison of Server Actions vs Route Handlers, see the blog post: [How to Send Email in Next.js App Router](/blog/send-email-nextjs-app-router). --- # examples/php/laravel.mdx Source: https://reloop.sh/docs/examples/php/laravel Markdown: https://reloop.sh/docs/examples/php/laravel.md ## Installation Install the official PHP SDK via Composer: ```bash composer require reloop-labs/reloop-php ``` ## Setup Configuration Add the API key to your `.env` file: ```env RELOOP_API_KEY=rl_your_api_key_here ``` ## Sending an Email Here is an example of sending an email from a controller: ```php mail->send([ 'from' => 'onboarding@reloop.sh', 'to' => 'delivered@resend.dev', 'subject' => 'Hello from Laravel', 'html' => '

Congrats on sending your first email via Reloop from Laravel!

', ]); return response()->json($response); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } } } ``` --- # examples/python/django.mdx Source: https://reloop.sh/docs/examples/python/django Markdown: https://reloop.sh/docs/examples/python/django.md ## Installation ```bash pip install reloop-email ``` ## Setup settings.py Add your API key to your settings: ```python # settings.py RELOOP_API_KEY = os.environ.get("RELOOP_API_KEY", "rl_your_api_key_here") ``` ## Sending an Email Use the client within your Django views: ```python from django.http import JsonResponse from django.conf import settings from reloop_email import Reloop reloop = Reloop(api_key=settings.RELOOP_API_KEY) def send_email_view(request): if request.method == 'POST': try: result = reloop.mail.send({ "from": "onboarding@reloop.sh", "to": "delivered@resend.dev", "subject": "Hello from Django", "html": "

Congrats on sending your first email via Reloop from Django!

", }) if result.email_error: raise result.email_error return JsonResponse(result.response) except Exception as e: return JsonResponse({"error": str(e)}, status=500) ``` --- # examples/python/fastapi.mdx Source: https://reloop.sh/docs/examples/python/fastapi Markdown: https://reloop.sh/docs/examples/python/fastapi.md ## Installation Install the official Python SDK: ```bash pip install reloop-email ``` ## Setup Environment Variables Add your API key to your environment: ```bash ``` ## Sending an Email Here is a simple FastAPI route that sends an email: ```python from fastapi import FastAPI, HTTPException from reloop_email import Reloop app = FastAPI() reloop = Reloop(api_key=os.environ.get("RELOOP_API_KEY")) # The SDK is synchronous. Use `def`, not `async def`, so FastAPI runs the # route in its thread pool instead of blocking the event loop. @app.post("/send-email") def send_email(): try: result = reloop.mail.send({ "from": "onboarding@reloop.sh", "to": "delivered@resend.dev", "subject": "Hello from FastAPI", "html": "

Congrats on sending your first email via Reloop from FastAPI!

", }) if result.email_error: raise result.email_error return result.response except Exception as e: raise HTTPException(status_code=500, detail=str(e)) ``` --- # examples/python/flask.mdx Source: https://reloop.sh/docs/examples/python/flask Markdown: https://reloop.sh/docs/examples/python/flask.md ## Installation ```bash pip install reloop-email ``` ## Sending an Email ```python from flask import Flask, jsonify from reloop_email import Reloop app = Flask(__name__) reloop = Reloop(api_key=os.environ.get("RELOOP_API_KEY")) @app.route("/send-email", methods=["POST"]) def send_email(): try: result = reloop.mail.send({ "from": "onboarding@reloop.sh", "to": "delivered@resend.dev", "subject": "Hello from Flask", "html": "

Congrats on sending your first email via Reloop from Flask!

", }) if result.email_error: raise result.email_error return jsonify(result.response) except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == "__main__": app.run(port=5000) ``` --- # examples/ruby/rails.mdx Source: https://reloop.sh/docs/examples/ruby/rails Markdown: https://reloop.sh/docs/examples/ruby/rails.md ## Installation Add the official Ruby SDK to your Gemfile: ```ruby gem 'reloop-ruby' ``` And run: ```bash bundle install ``` ## Setup Credentials Set your API key in your environment variables: ```bash ``` ## Sending an Email Define a controller or a mailer to send emails: ```ruby class EmailsController < ApplicationController def create reloop = Reloop::Client.new(api_key: ENV['RELOOP_API_KEY']) begin response = reloop.mail.send( from: 'onboarding@reloop.sh', to: 'delivered@resend.dev', subject: 'Hello from Rails', html: '

Congrats on sending your first email via Reloop from Ruby on Rails!

' ) render json: response rescue => e render json: { error: e.message }, status: :internal_server_error end end end ``` --- # examples/rust/axum.mdx Source: https://reloop.sh/docs/examples/rust/axum Markdown: https://reloop.sh/docs/examples/rust/axum.md ## Installation Add the official Rust SDK and standard tokio dependencies to your `Cargo.toml`: ```toml [dependencies] axum = "0.7" tokio = { version = "1", features = ["full"] } reloop = "0.1" serde_json = "1" ``` ## Sending an Email ```rust use axum::{routing::post, Json, Router}; use reloop::ReloopClient; use serde_json::json; use std::env; #[tokio::main] async fn main() { let app = Router::new().route("/send-email", post(send_email)); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } async fn send_email() -> Result, (axum::http::StatusCode, String)> { let api_key = env::var("RELOOP_API_KEY").map_err(|_| { (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Missing API Key".to_string()) })?; let client = ReloopClient::new(api_key, None); match client.mail().send(json!({ "from": "onboarding@reloop.sh", "to": "delivered@resend.dev", "subject": "Hello from Axum", "html": "

Congrats on sending your first email via Reloop from Rust Axum!

", })).await { Ok(res) => Ok(Json(res)), Err(e) => Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), } } ``` --- # examples/smtp/go.mdx Source: https://reloop.sh/docs/examples/smtp/go Markdown: https://reloop.sh/docs/examples/smtp/go.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-go) ## Code Example ```go package main "crypto/tls" "fmt" "net/smtp" "os" ) func main() { apiKey := os.Getenv("RELOOP_API_KEY") // SMTP server configuration smtpHost := "smtp.reloop.sh" smtpPort := "587" auth := smtp.PlainAuth("", apiKey, apiKey, smtpHost) to := []string{"delivered@resend.dev"} msg := []byte("To: delivered@resend.dev\r\n" + "Subject: Hello from Go net/smtp\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "\r\n" + "

Congrats on sending your first email via Reloop SMTP using net/smtp!

\r\n") err := smtp.SendMail(smtpHost+":"+smtpPort, auth, "onboarding@reloop.sh", to, msg) if err != nil { fmt.Println("Error connecting:", err) return } defer conn.Close() client, err := smtp.NewClient(conn, host) if err != nil { fmt.Println("Error creating client:", err) return } defer client.Close() if err = client.Auth(auth); err != nil { fmt.Println("Auth failed:", err) return } if err = client.Mail(from); err != nil { fmt.Println("MAIL FROM failed:", err) return } for _, addr := range to { if err = client.Rcpt(addr); err != nil { fmt.Println("RCPT TO failed:", err) return } } w, err := client.Data() if err != nil { fmt.Println("DATA failed:", err) return } if _, err = w.Write(msg); err != nil { fmt.Println("Write failed:", err) return } if err = w.Close(); err != nil { fmt.Println("Close failed:", err) return } _ = client.Quit() fmt.Println("Email sent successfully!") } ``` --- # examples/smtp/introduction.mdx Source: https://reloop.sh/docs/examples/smtp/introduction Markdown: https://reloop.sh/docs/examples/smtp/introduction.md ## SMTP Credentials To connect to the Reloop SMTP server, use the following configuration settings: | Setting | Value | | :--- | :--- | | **Host** | `smtp.reloop.sh` | | **Port** | `587`, or `2587` / `2025` if 587 is blocked | | **Username** | Any string, for example `reloop` | | **Password** | Your Reloop API Key | | **Encryption** | STARTTLS on every port. Implicit TLS (port `465`) is not offered. | ## Code Examples Configure standard SMTP library examples or explore step-by-step guides for your language: Standard Node SMTP using Nodemailer. Read Guide → GitHub ↗ Built-in smtplib Python SMTP. Read Guide → GitHub ↗ Built-in net/smtp Go implementation. Read Guide → GitHub ↗ Standard PHP SMTP connection. Read Guide → GitHub ↗ Standard Ruby Net::SMTP integration. Read Guide → GitHub ↗ Lettre SMTP library for Rust. Read Guide → GitHub ↗ --- # examples/smtp/nodemailer.mdx Source: https://reloop.sh/docs/examples/smtp/nodemailer Markdown: https://reloop.sh/docs/examples/smtp/nodemailer.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-nodemailer) ## Installation Install Nodemailer: ```bash npm install nodemailer ``` ## Code Example ```javascript const nodemailer = require('nodemailer'); const transporter = nodemailer.createTransport({ host: 'smtp.reloop.sh', port: 587, secure: false, // Reloop uses STARTTLS; keep false on every port auth: { user: 'reloop', pass: process.env.RELOOP_API_KEY, }, }); async function main() { const info = await transporter.sendMail({ from: '"Onboarding" ', to: 'delivered@resend.dev', subject: 'Hello from Nodemailer', html: '

Congrats on sending your first email via Reloop SMTP using Nodemailer!

', }); console.log('Message sent: %s', info.messageId); } main().catch(console.error); ``` --- # examples/smtp/php.mdx Source: https://reloop.sh/docs/examples/smtp/php Markdown: https://reloop.sh/docs/examples/smtp/php.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-php) ## Installation Install PHPMailer via Composer: ```bash composer require phpmailer/phpmailer ``` ## Code Example ```php isSMTP(); $mail->Host = 'smtp.reloop.sh'; $mail->SMTPAuth = true; $mail->Username = 'reloop'; $mail->Password = getenv('RELOOP_API_KEY'); $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; $mail->Port = 587; // Recipients $mail->setFrom('onboarding@reloop.sh', 'Onboarding'); $mail->addAddress('delivered@resend.dev'); // Content $mail->isHTML(true); $mail->Subject = 'Hello from PHPMailer'; $mail->Body = '

Congrats on sending your first email via Reloop SMTP using PHPMailer!

'; $mail->send(); echo 'Message has been sent'; } catch (Exception $e) { echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; } ``` --- # examples/smtp/python.mdx Source: https://reloop.sh/docs/examples/smtp/python Markdown: https://reloop.sh/docs/examples/smtp/python.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-python) ## Code Example ```python from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText api_key = os.environ.get("RELOOP_API_KEY") # Create message msg = MIMEMultipart() msg['From'] = 'onboarding@reloop.sh' msg['To'] = 'delivered@resend.dev' msg['Subject'] = 'Hello from Python smtplib' body = '

Congrats on sending your first email via Reloop SMTP using smtplib!

' msg.attach(MIMEText(body, 'html')) try: # Connect to the server server = smtplib.SMTP('smtp.reloop.sh', 587) server.starttls() # Upgrade connection to secure server.login(api_key, api_key) # Send email server.sendmail(msg['From'], msg['To'], msg.as_string()) server.quit() print("Email sent successfully!") except Exception as e: print(f"Error: {e}") ``` --- # examples/smtp/ruby.mdx Source: https://reloop.sh/docs/examples/smtp/ruby Markdown: https://reloop.sh/docs/examples/smtp/ruby.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-ruby) ## Code Example ```ruby require 'net/smtp' require 'openssl' api_key = ENV['RELOOP_API_KEY'] message = <Congrats on sending your first email via Reloop SMTP using net/smtp!

MESSAGE_END begin Net::SMTP.start('smtp.reloop.sh', 587, 'localhost', api_key, api_key, :plain) do |smtp| smtp.send_message message, 'onboarding@reloop.sh', 'delivered@resend.dev' end puts "Email sent successfully!" rescue => e puts "Error: #{e.message}" end ``` --- # examples/smtp/rust.mdx Source: https://reloop.sh/docs/examples/smtp/rust Markdown: https://reloop.sh/docs/examples/smtp/rust.md [View complete runnable example on GitHub ↗](https://github.com/reloop-labs/reloop-examples/tree/main/smtp-rust) ## Installation Add `lettre` to your `Cargo.toml`: ```toml [dependencies] lettre = "0.11" ``` ## Code Example ```rust use lettre::transport::smtp::authentication::Credentials; use lettre::{Message, SmtpTransport, Transport}; use std::env; fn main() -> Result<(), Box> { let api_key = env::var("RELOOP_API_KEY")?; let email = Message::builder() .from("onboarding@reloop.sh".parse()?) .to("delivered@resend.dev".parse()?) .subject("Hello from Lettre") .header(lettre::message::header::ContentType::TEXT_HTML) .body(String::from("

Congrats on sending your first email via Reloop SMTP using Lettre!

"))?; let creds = Credentials::new("reloop".to_string(), api_key); // Open a local connection on port 587 let mailer = SmtpTransport::starttls_relay("smtp.reloop.sh")? .credentials(creds) .build(); // Send the email match mailer.send(&email) { Ok(_) => println!("Email sent successfully!"), Err(e) => panic!("Could not send email: {:?}", e), } Ok(()) } ``` --- # guides/account-quotas-limits.mdx Source: https://reloop.sh/docs/guides/account-quotas-limits Markdown: https://reloop.sh/docs/guides/account-quotas-limits.md Understanding Reloop sending quotas and rate limits. ## Overview This guide helps you understand and configure this sending feature in Reloop. ## Details Understanding your sending configuration is key to successful email delivery. Review the [Reloop dashboard](https://reloop.sh/dashboard) for your current settings and limits. ## Configuration Check your current plan and usage in the [Reloop dashboard](https://reloop.sh/dashboard/settings). Adjust your sending configuration based on your requirements and plan limits. Keep track of your sending volume and adjust as needed to stay within limits. ## Learn More - [Account Quotas and Limits](/docs/guides/account-quotas-limits) - [Sending Emails](/docs/learn/emails) - [Email Best Practices](/docs/integrations/agent-skills/email-best-practices) --- # guides/apple-branded-mail.mdx Source: https://reloop.sh/docs/guides/apple-branded-mail Markdown: https://reloop.sh/docs/guides/apple-branded-mail.md Apple Mail supports Brand Indicators for Message Identification (BIMI), which lets you display your brand logo next to your emails in the recipient's inbox. ## Prerequisites - A verified domain with DMARC set to `quarantine` or `reject` - A square SVG logo in Tiny PS format - A VMC (Verified Mark Certificate) from a trusted authority ## Setup Your DMARC policy must be set to at least `p=quarantine`: ``` v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com ``` Create a square SVG logo that meets BIMI requirements. The logo must be in Tiny PS format. Add a TXT record at `default._bimi.yourdomain.com`: ``` v=BIMI1; l=https://example.com/logo.svg; a=https://example.com/vmc.pem ``` BIMI adoption is growing but not yet universal. Apple Mail, Gmail, and Yahoo support it. Outlook does not yet. --- # guides/apple-private-relay.mdx Source: https://reloop.sh/docs/guides/apple-private-relay Markdown: https://reloop.sh/docs/guides/apple-private-relay.md How to send emails to Apple Private Relay addresses. ## Overview Understanding this topic is important for maintaining good email deliverability with Reloop. ## Key Points - Follow email sending best practices to maintain a strong sender reputation - Monitor your delivery rates in the [Reloop dashboard](https://reloop.sh/dashboard) - Keep your email lists clean and up-to-date ## Best Practices Regularly check your delivery, bounce, and complaint rates in the Reloop dashboard. Ensure SPF, DKIM, and DMARC are properly configured for your domain. Always include an unsubscribe mechanism in marketing emails and honor opt-out requests promptly. For detailed guidance on email deliverability, see our [Email Best Practices](/docs/integrations/agent-skills/email-best-practices) skill documentation. ## Learn More - [Emails Going to Spam](/docs/guides/emails-going-to-spam) - [Warm-up Guide](/docs/guides/warmup-guide) - [Audience Hygiene](/docs/guides/audience-hygiene) - [Domains Guide](/docs/learn/domain) --- # guides/audience-hygiene.mdx Source: https://reloop.sh/docs/guides/audience-hygiene Markdown: https://reloop.sh/docs/guides/audience-hygiene.md Best practices for maintaining a clean email list. ## Overview Understanding this topic is important for maintaining good email deliverability with Reloop. ## Key Points - Follow email sending best practices to maintain a strong sender reputation - Monitor your delivery rates in the [Reloop dashboard](https://reloop.sh/dashboard) - Keep your email lists clean and up-to-date ## Best Practices Regularly check your delivery, bounce, and complaint rates in the Reloop dashboard. Ensure SPF, DKIM, and DMARC are properly configured for your domain. Always include an unsubscribe mechanism in marketing emails and honor opt-out requests promptly. For detailed guidance on email deliverability, see our [Email Best Practices](/docs/integrations/agent-skills/email-best-practices) skill documentation. ## Learn More - [Emails Going to Spam](/docs/guides/emails-going-to-spam) - [Warm-up Guide](/docs/guides/warmup-guide) - [Audience Hygiene](/docs/guides/audience-hygiene) - [Domains Guide](/docs/learn/domain) --- # guides/avoid-gmail-spam.mdx Source: https://reloop.sh/docs/guides/avoid-gmail-spam Markdown: https://reloop.sh/docs/guides/avoid-gmail-spam.md Tips to prevent your emails from landing in Gmail spam. ## Overview This article covers common causes and solutions for this issue when using Reloop. ## Common Causes - Misconfigured DNS records - API key permissions not matching the domain - Rate limiting or account restrictions ## Resolution Steps Review your domain settings in the [Reloop dashboard](https://reloop.sh/dashboard/domain) and ensure all DNS records are correctly configured. Ensure your API key has the correct permissions for the operation you're performing. Go to [API Keys](https://reloop.sh/dashboard/api-keys) to review. If the issue persists, reach out to [Reloop support](https://reloop.sh/help) with your error details and domain information. ## Learn More - [Domain Not Verifying](/docs/guides/domain-not-verifying) - [Avoid MX Conflicts](/docs/guides/avoid-mx-conflicts) - [Emails Going to Spam](/docs/guides/emails-going-to-spam) --- # guides/avoid-mx-conflicts.mdx Source: https://reloop.sh/docs/guides/avoid-mx-conflicts Markdown: https://reloop.sh/docs/guides/avoid-mx-conflicts.md This guide moved to **[MX conflicts](/docs/guides/connect-domain/troubleshoot/mx-conflicts)** under Connect a domain. Also see [Sending vs receiving](/docs/guides/connect-domain/sending-vs-receiving) and [Root vs subdomain](/docs/guides/connect-domain/what-is-a-domain). --- # guides/avoid-outlook-spam.mdx Source: https://reloop.sh/docs/guides/avoid-outlook-spam Markdown: https://reloop.sh/docs/guides/avoid-outlook-spam.md Tips to prevent your emails from landing in Outlook spam. ## Overview This article covers common causes and solutions for this issue when using Reloop. ## Common Causes - Misconfigured DNS records - API key permissions not matching the domain - Rate limiting or account restrictions ## Resolution Steps Review your domain settings in the [Reloop dashboard](https://reloop.sh/dashboard/domain) and ensure all DNS records are correctly configured. Ensure your API key has the correct permissions for the operation you're performing. Go to [API Keys](https://reloop.sh/dashboard/api-keys) to review. If the issue persists, reach out to [Reloop support](https://reloop.sh/help) with your error details and domain information. ## Learn More - [Domain Not Verifying](/docs/guides/domain-not-verifying) - [Avoid MX Conflicts](/docs/guides/avoid-mx-conflicts) - [Emails Going to Spam](/docs/guides/emails-going-to-spam) --- # guides/change-email-address.mdx Source: https://reloop.sh/docs/guides/change-email-address Markdown: https://reloop.sh/docs/guides/change-email-address.md How to change the email address associated with your Reloop account. ## Overview Manage your Reloop account settings effectively to ensure smooth operation. ## Steps Navigate to the [Account Settings](https://reloop.sh/dashboard/settings) page in your Reloop dashboard. Follow the on-screen instructions to update your account configuration. Review and confirm your changes. Some changes may require email verification. If you need assistance with account changes, contact [Reloop support](https://reloop.sh/help). ## Learn More - [Handling API Keys](/docs/guides/handling-api-keys) - [Account Quotas and Limits](/docs/guides/account-quotas-limits) --- # guides/configuring-tls.mdx Source: https://reloop.sh/docs/guides/configuring-tls Markdown: https://reloop.sh/docs/guides/configuring-tls.md The difference between opportunistic and enforced TLS. ## Overview Manage your Reloop account settings effectively to ensure smooth operation. ## Steps Navigate to the [Account Settings](https://reloop.sh/dashboard/settings) page in your Reloop dashboard. Follow the on-screen instructions to update your account configuration. Review and confirm your changes. Some changes may require email verification. If you need assistance with account changes, contact [Reloop support](https://reloop.sh/help). ## Learn More - [Handling API Keys](/docs/guides/handling-api-keys) - [Account Quotas and Limits](/docs/guides/account-quotas-limits) --- # guides/connect-domain/add-domain.mdx Source: https://reloop.sh/docs/guides/connect-domain/add-domain Markdown: https://reloop.sh/docs/guides/connect-domain/add-domain.md This is the main path from “I have a domain” to “I can send as `@mydomain.com`.” ## Before you start - You can log in to the place that manages **nameservers** for the domain ([find your DNS provider](/docs/guides/connect-domain/find-dns-provider)). - Decide [root vs subdomain](/docs/guides/connect-domain/what-is-a-domain). Reloop supports either, or both. - Decide if you need [receiving](/docs/guides/connect-domain/sending-vs-receiving) or only sending. New domains start with Email Receiving on. Turn it off before publishing DNS if this hostname must keep Google Workspace / Microsoft 365. ## Steps ![Domains list in the Reloop dashboard](/docs/images/docs/guides/connect-domain/overview/domains-list.png) Go to [Domains](https://reloop.sh/dashboard/domain) in the Reloop dashboard (or finish the onboarding domain step). Click **Add domain**. Enter the hostname you want to send from, for example: - `acme.com` (root) - `mail.acme.com` or `notifications.acme.com` (subdomain) Confirm organization and create the domain. Reloop generates DNS records and DKIM keys. Email Sending, Email Receiving, and click/open tracking start on by default. On the domain page, Reloop shows a provider when nameservers are recognized. - **Auto-populate available** → continue with [Auto-populate](/docs/guides/connect-domain/auto-populate) - **Provider known, manual only** → [Manual setup](/docs/guides/connect-domain/manual-setup) + that host’s [guide](/docs/guides/connect-domain/providers) - **Unknown** → [Unknown provider](/docs/guides/connect-domain/providers/unknown-provider) Either complete the Domain Connect consent flow, or copy every required record into your DNS panel. Do not skip DKIM, SPF, or DMARC when Email Sending is on. Click **Verify & finish** on the setup page, or **Verify Domain** on the domain detail page, to start checks. Reloop may retry that verification job; it does not verify on its own while status stays pending. Status moves through [pending → verifying → active](/docs/guides/connect-domain/verification/statuses) when public DNS matches. Use [Test your domain](/docs/guides/connect-domain/after/test-domain) to confirm SPF/DKIM/DMARC in a real inbox. ## Domain already in use If another Reloop organization already verified the domain, see [Domain already registered](/docs/guides/connect-domain/troubleshoot/domain-already-registered). --- # guides/connect-domain/after/change-dns.mdx Source: https://reloop.sh/docs/guides/connect-domain/after/change-dns Markdown: https://reloop.sh/docs/guides/connect-domain/after/change-dns.md ## Safe migration checklist From the old DNS host (or Reloop dashboard), note SPF, DKIM, DMARC, MX, and tracking values. Add the domain/zone at Cloudflare, Route 53, etc., and recreate **all** important records (website + mail), including Reloop’s. A day before, set TTL to 300s on old NS-related records if your registrar allows. Point NS to the new host. Keep the old zone until dig shows the new NS everywhere. After NS propagate, open the domain → **Verify**. Auto populate may appear if the new host supports Reloop’s Domain Connect template. ## Avoid - Deleting Reloop records on the old host **before** the new host is live - Switching NS without recreating MX if you still need inbound mail ## Related - [Wrong nameservers](/docs/guides/connect-domain/troubleshoot/wrong-nameservers) - [Find your DNS provider](/docs/guides/connect-domain/find-dns-provider) --- # guides/connect-domain/after/index.mdx Source: https://reloop.sh/docs/guides/connect-domain/after Markdown: https://reloop.sh/docs/guides/connect-domain/after.md Once status is **active**: - [Test your domain](/docs/guides/connect-domain/after/test-domain) - [Change DNS later](/docs/guides/connect-domain/after/change-dns) - [Remove a domain](/docs/guides/connect-domain/after/remove-domain) For inbox placement and reputation, continue with deliverability guides under [Guides](/docs/guides) (warmup, spam folders, suppression list). --- # guides/connect-domain/after/remove-domain.mdx Source: https://reloop.sh/docs/guides/connect-domain/after/remove-domain Markdown: https://reloop.sh/docs/guides/connect-domain/after/remove-domain.md ## In Reloop From [Domains](https://reloop.sh/dashboard/domain), open the domain and use the delete/remove action. After removal: - You can no longer send with that domain’s identities in Reloop - API/SMTP From addresses on that domain will fail authentication checks ## DNS cleanup (optional but recommended) At your DNS host, remove Reloop-specific rows you no longer need: - `reloop._domainkey` TXT - Reloop SPF include (or whole SPF if unused) - Reloop DMARC if you added it only for Reloop - Reloop MX / tracking CNAME Do **not** remove Google/Microsoft records if you still use those services. ## Re-adding later You can add the same domain again later and re-verify, subject to [already registered](/docs/guides/connect-domain/troubleshoot/domain-already-registered) rules if another org holds it. --- # guides/connect-domain/after/test-domain.mdx Source: https://reloop.sh/docs/guides/connect-domain/after/test-domain Markdown: https://reloop.sh/docs/guides/connect-domain/after/test-domain.md ## In Reloop 1. Confirm domain status is **active**. 2. Send a test message using the dashboard send flow or your API/SMTP integration with a `From` on the verified domain. 3. Open the message in Gmail/Outlook and check authentication results. ## What “good” looks like In Gmail → Show original (or equivalent): - `SPF: PASS` - `DKIM: PASS` (selector often `reloop`) - `DMARC: PASS` (when aligned) ## If authentication fails but Reloop says active - Wait for propagation on the receiver’s resolvers - Re-check public DNS with `dig` - Confirm you did not remove records after verify - See [Domain not verifying](/docs/guides/connect-domain/troubleshoot/not-verifying) if Reloop flipped back to failed ## Related - [Warm-up guide](/docs/guides/warmup-guide) - [Emails going to spam](/docs/guides/emails-going-to-spam) --- # guides/connect-domain/auto-populate.mdx Source: https://reloop.sh/docs/guides/connect-domain/auto-populate Markdown: https://reloop.sh/docs/guides/connect-domain/auto-populate.md **Auto-populate** uses the open [Domain Connect](https://www.domainconnect.org/) standard. Reloop opens your DNS provider, you approve a template, and the provider writes SPF, DKIM, DMARC, MX, and tracking records for you. Auto-populate only works on Reloop Cloud. The Domain Connect template is registered to `reloop.sh` and its records point at Reloop Cloud, so a [self-hosted](/docs/self-host) instance can't use it. Add the records by hand with [manual setup](/docs/guides/connect-domain/manual-setup). ## When you see the button Reloop shows **Auto populate** when nameserver detection matches a host where Reloop’s Domain Connect template is known to work today: - [Cloudflare](/docs/guides/connect-domain/providers/cloudflare) - [Vercel](/docs/guides/connect-domain/providers/vercel) - [Domain Chief](/docs/guides/connect-domain/providers/domain-chief) - [NameSilo](/docs/guides/connect-domain/providers/namesilo) On the domain page (and during onboarding DNS step): - **Auto populate** → use this flow - **Open DNS settings** (or no Auto populate) → use [manual setup](/docs/guides/connect-domain/manual-setup) When you click **Auto populate**, Reloop also runs live Domain Connect discovery as a safety check (`_domainconnect` DNS + provider API) to confirm the Reloop template (`reloop.sh` / `email-setup`) is still available. A host that supports Domain Connect in general may still fail that check if Reloop’s template is not onboarded there. Trust the dashboard for your domain: if the button is missing, use manual setup even if the provider advertises Domain Connect elsewhere. ## Defaults before you approve New domains start with **Email Receiving** and **click/open tracking** on. Auto-populate therefore typically includes inbound MX (`inbound.reloop.sh`) and the tracking CNAME unless you turn those off first. If this hostname already uses Google Workspace, Microsoft 365, or another inbox, turn **Email Receiving** off on the Reloop domain page before Auto-populate, or use a [separate subdomain](/docs/guides/connect-domain/what-is-a-domain) for Reloop. See [Sending vs receiving](/docs/guides/connect-domain/sending-vs-receiving). ## How the flow works Reloop runs Domain Connect discovery and builds a signed apply URL when the Reloop template is available. A new tab opens on your DNS host. Sign in if needed, review the records, and confirm. After success you are redirected back (or you can return manually). Click **Verify & finish** on the setup page, or **Verify Domain** on the domain detail page, if status is still pending. If discovery fails or the provider dropped the template, you get an error toast and should continue manually. ## What gets applied The Reloop Domain Connect template typically applies groups based on your domain toggles: - DKIM TXT on `reloop._domainkey` - SPF via SPFM / merge (`include:reloop.sh`) - DMARC on `_dmarc` (required for verification when Email Sending is on) - **Receiving MX** → `inbound.reloop.sh` when Email Receiving is on (default for new domains) - Tracking CNAME → `link.reloop.sh` when tracking is on (host is usually `link` on apex, or `link.` when the Reloop domain is a subdomain) Exact groups depend on your toggles and template version; the consent screen lists what will change. Always confirm hosts and values in the Reloop dashboard. ## If Auto-populate fails See [Auto-populate failed](/docs/guides/connect-domain/troubleshoot/auto-populate-failed) for: - Popup blocked - User cancelled consent - Template not found at provider - Domain Connect not supported on that zone You can always fall back to [manual setup](/docs/guides/connect-domain/manual-setup) using the same dashboard records. ## Security notes - Only approve the flow while logged into **your** DNS account for that domain. - Reloop signs Domain Connect apply URLs; do not reuse apply links from untrusted sources. - After you approve, check the records in your DNS panel, then click **Verify Domain** (or **Verify & finish**) in Reloop. --- # guides/connect-domain/dns-records-explained.mdx Source: https://reloop.sh/docs/guides/connect-domain/dns-records-explained Markdown: https://reloop.sh/docs/guides/connect-domain/dns-records-explained.md When you add a domain, Reloop generates the exact records to publish. Always prefer the dashboard values over examples here. Your **DKIM key is unique**. ## Quick map | Purpose | Typical type | Example host | What it does | | --- | --- | --- | --- | | **SPF** | TXT | `@` | Authorizes Reloop to send for your domain | | **DKIM** | TXT | `reloop._domainkey` | Cryptographic signature so receivers trust the message | | **DMARC** | TXT | `_dmarc` | Policy if SPF/DKIM fail; required for verification when Email Sending is on | | **Receiving MX** | MX | `@` or subdomain host | Routes **inbound** mail to Reloop (`inbound.reloop.sh`) when Email Receiving is on (default for new domains) | | **Tracking** | CNAME | `link` or `link.` | Click/open tracking → `link.reloop.sh` (on by default for new domains) | Hosts and whether MX/tracking appear depend on your domain settings (apex vs subdomain, sending/receiving/tracking toggles). Always copy the host from the Reloop dashboard. ## SPF (Sender Policy Framework) **Type:** TXT **Example value:** ``` v=spf1 include:reloop.sh -all ``` SPF lists which servers may send mail as your domain. Reloop’s include is `include:reloop.sh` (hosted product). You may only have **one SPF TXT** on a given host. If you already have SPF (Google, Microsoft, Mailchimp, …), **merge** Reloop’s `include:reloop.sh` into that record. Do not add a second SPF. See [SPF conflicts](/docs/guides/connect-domain/troubleshoot/spf-conflicts). ## DKIM (DomainKeys Identified Mail) **Type:** TXT **Host:** usually `reloop._domainkey` (selector `reloop`) **Value:** looks like: ``` v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A... ``` The long `p=` string is your public key. Reloop signs outbound mail with the matching private key. If the TXT is truncated, quoted wrongly, or split incorrectly, verification fails. See [DKIM / DMARC typos](/docs/guides/connect-domain/troubleshoot/dkim-dmarc-typos). ## DMARC **Type:** TXT **Host:** `_dmarc` **Example value:** ``` v=DMARC1; p=reject; ``` DMARC tells receivers what to do when authentication fails. Reloop’s default template uses `p=reject`. When **Email Sending** is on, a matching DMARC record is required for Reloop verification. If you already have DMARC for Google/Microsoft, you can often keep your existing policy. Reloop checks that a valid DMARC record is present; review carefully before overwriting a production policy. ## MX (mail exchange) MX is **not** required to send mail. Reloop only shows MX when **Email Receiving** is on: - **Receiving MX** → typically `inbound.reloop.sh`. Used when Email Receiving is on (default for new domains). On the same hostname as Google Workspace / Microsoft 365, this can divert or split inbound delivery. Turn Receiving off for send-only, or use a [separate hostname](/docs/guides/connect-domain/what-is-a-domain). See [MX conflicts](/docs/guides/connect-domain/troubleshoot/mx-conflicts). ## Tracking CNAME **Type:** CNAME **Host:** `link` on an apex domain; for a Reloop subdomain such as `mail.example.com`, the host is often `link.mail` (always copy from the dashboard) **Value:** `link.reloop.sh` Used for branded click/open tracking links. Click and open tracking are **on by default** for new domains; disable tracking in Reloop if you do not want this CNAME. Conflicts if that host already points elsewhere, or if your provider disallows CNAMEs in that spot. See [Tracking CNAME issues](/docs/guides/connect-domain/troubleshoot/tracking-cname). ## How to read a Reloop dashboard row For each row, match three things in your DNS UI: 1. **Type** (TXT / MX / CNAME) 2. **Name / Host**: some UIs want `@` for the root; others want a blank host or the bare domain 3. **Value**: paste exactly; for MX also set **Priority** Provider-specific quirks (Cloudflare proxy, GoDaddy host field, Route 53 hosted zone) are in each [provider guide](/docs/guides/connect-domain/providers). ## Related - [Manual setup](/docs/guides/connect-domain/manual-setup) - [Auto-populate](/docs/guides/connect-domain/auto-populate) - [Sending vs receiving](/docs/guides/connect-domain/sending-vs-receiving) --- # guides/connect-domain/find-dns-provider.mdx Source: https://reloop.sh/docs/guides/connect-domain/find-dns-provider Markdown: https://reloop.sh/docs/guides/connect-domain/find-dns-provider.md You need to know **who hosts your nameservers** before you can publish Reloop records (or before Auto-populate can work). ## Method 1: Reloop dashboard (easiest) 1. Add the domain at [Domains](https://reloop.sh/dashboard/domain) if you have not already. 2. Open the domain detail page. 3. Reloop reads public nameservers and shows a **Provider** chip when it recognizes the host (Cloudflare, GoDaddy, Vercel, Namecheap, …). If Reloop shows a provider: - **Auto populate** on the domain page → use [Auto-populate](/docs/guides/connect-domain/auto-populate) - **Otherwise** → open that host’s guide under [Provider guides](/docs/guides/connect-domain/providers) and add records manually If Reloop cannot detect a provider, use Method 2 or 3. ## Method 2: Registrar control panel 1. Log in where you **bought / renew** the domain. 2. Open the domain → **Nameservers** / **DNS** / **Manage DNS**. 3. Note the nameserver hostnames, for example: | Nameserver contains | Likely DNS host | | --- | --- | | `cloudflare.com` | Cloudflare | | `vercel-dns.com` | Vercel | | `domaincontrol.com` | GoDaddy | | `registrar-servers.com` | Namecheap | | `awsdns-` | AWS Route 53 | | `domainchief.` | Domain Chief | | `dnsowl.com` / `hostsilo.com` | NameSilo | | `ui-dns.` | IONOS | | `squarespace.com` / `googledomains.com` | Squarespace | | `hetzner.com` / `hetzner.de` | Hetzner | | `hostinger.` / `dns-parking.com` | Hostinger | | `gandi.net` | Gandi | | `porkbun.com` | Porkbun | | `strato.de` / `strato-hosting.eu` | Strato | | `dreamhost.com` / `dreamhosters.com` | DreamHost | | `as207960.net` / `glauca.digital` | Glauca Digital | | `wordpress.com` / `wp.com` | WordPress.com | | `plesk` | Plesk (or your host’s Plesk panel) | Then open the matching [provider guide](/docs/guides/connect-domain/providers). ## Method 3: Public lookup ### In the browser Use any “DNS lookup” / “whois” tool and query **NS** records for your domain (e.g. `acme.com`). ### On your computer ```bash dig NS acme.com +short # or nslookup -type=NS acme.com ``` Example output: ``` ada.ns.cloudflare.com. bob.ns.cloudflare.com. ``` → DNS is on **Cloudflare**. ## Bought at A, DNS at B If the registrar is GoDaddy but NS are Cloudflare, **ignore GoDaddy’s DNS record editor** for Reloop. Edit Cloudflare. Full explanation: [Registrar vs DNS host](/docs/guides/connect-domain/registrar-vs-dns-host). ## Still stuck? Use the [Unknown provider](/docs/guides/connect-domain/providers/unknown-provider) guide: open whatever panel controls the NS hosts you found, and paste the records from Reloop. --- # guides/connect-domain/index.mdx Source: https://reloop.sh/docs/guides/connect-domain Markdown: https://reloop.sh/docs/guides/connect-domain.md Reloop can send and receive email for **any domain you control** (receiving and tracking are on by default for new domains; turn receiving off if you keep another inbox on that hostname). You do not need to buy the domain from Reloop. Register it anywhere, point DNS at the records Reloop shows you, and verify. The [Domains](https://reloop.sh/dashboard/domain) page lists every sending domain in your organization, with status and shortcuts to DNS guides. ![Domains list in the Reloop dashboard](/docs/images/docs/guides/connect-domain/overview/domains-list.png) ## What you will do Open [Domains](https://reloop.sh/dashboard/domain) and click **Add domain**. Enter `example.com` or a subdomain like `mail.example.com`. Reloop detects your nameservers when possible. If you are unsure who hosts DNS, see [Find your DNS provider](/docs/guides/connect-domain/find-dns-provider). On the domain page, follow the banner Reloop shows for your DNS host (see below). Click **Verify & finish** (setup) or **Verify Domain** (domain detail) to start checks. When status is **active**, you can send from that domain. ## Configuring DNS from the banner After you add a domain, Reloop shows a banner based on the nameservers it detected. - If the banner names your DNS host and offers **Auto populate**, click it. Approve the consent screen at your provider and Reloop’s records are written for you. More detail: [Auto populate](/docs/guides/connect-domain/auto-populate). - If the banner does not offer Auto populate (or you prefer not to use it), copy the Type / Host / Value rows from the dashboard into your DNS panel. More detail: [Manual setup](/docs/guides/connect-domain/manual-setup) and the [DNS guides](/docs/guides/connect-domain/providers). Either way, Reloop checks public DNS afterward and marks the domain verified when the records match. ## New to DNS? Start here. You do not need to be a network engineer: 1. [What is a domain?](/docs/guides/connect-domain/what-is-a-domain) 2. [What is DNS?](/docs/guides/connect-domain/what-is-dns) 3. [Registrar vs DNS host](/docs/guides/connect-domain/registrar-vs-dns-host) 4. [Find your DNS provider](/docs/guides/connect-domain/find-dns-provider) 5. [DNS records Reloop needs](/docs/guides/connect-domain/dns-records-explained) ## Full map - **Setup**: [Add a domain](/docs/guides/connect-domain/add-domain) · [What is a domain?](/docs/guides/connect-domain/what-is-a-domain) · [Sending vs receiving](/docs/guides/connect-domain/sending-vs-receiving) - **DNS guides**: [All DNS hosts](/docs/guides/connect-domain/providers) - **Verification**: [Statuses](/docs/guides/connect-domain/verification/statuses) · [Propagation](/docs/guides/connect-domain/verification/propagation) - **Troubleshoot**: [Domain not verifying](/docs/guides/connect-domain/troubleshoot/not-verifying) and more - **After connect**: [Test](/docs/guides/connect-domain/after/test-domain) · [Change DNS](/docs/guides/connect-domain/after/change-dns) · [Remove](/docs/guides/connect-domain/after/remove-domain) Always copy values from your domain’s page in the Reloop dashboard. Examples in docs use placeholders; your DKIM key and hosts are unique to your domain. --- # guides/connect-domain/manual-setup.mdx Source: https://reloop.sh/docs/guides/connect-domain/manual-setup Markdown: https://reloop.sh/docs/guides/connect-domain/manual-setup.md ## Prerequisites - Domain added in [Reloop](https://reloop.sh/dashboard/domain) - Access to the DNS panel for the **nameserver** host ([find your provider](/docs/guides/connect-domain/find-dns-provider)) ## Steps Go to [Domains](https://reloop.sh/dashboard/domain) → select the domain. Keep this tab open; you will copy each row. Use **Open DNS settings** in Reloop if shown, or jump to the matching [provider guide](/docs/guides/connect-domain/providers). For every required row in Reloop: 1. Choose the same **type** (TXT, MX, CNAME) 2. Set **name/host** as shown (`@`, `reloop._domainkey`, `_dmarc`, `link` or `link.`, …) 3. Paste **value** exactly 4. For MX, set **priority** (often `10`); add **Receiving MX** (`inbound.reloop.sh`) only when Email Receiving is on 5. Save Provider UIs differ: - Some want blank host instead of `@` - Some auto-append your domain, so do not enter `reloop._domainkey.acme.com` if the UI already adds `acme.com` - Cloudflare: keep records **DNS only** (grey cloud) unless you know you need a proxy (almost never for mail TXT/MX) If an SPF TXT already exists, merge `include:reloop.sh` into it. Do not create a second SPF. See [SPF conflicts](/docs/guides/connect-domain/troubleshoot/spf-conflicts). Return to Reloop → **Verify & finish** (setup) or **Verify Domain** (detail). Allow time for [propagation](/docs/guides/connect-domain/verification/propagation) if checks fail immediately. ## Checklist before you verify - [ ] Edited the DNS host that matches your **NS** records (not only the registrar invoice) - [ ] DKIM TXT is complete (long `p=` value not cut off) - [ ] Only one SPF record on that host - [ ] DMARC published when Email Sending is on - [ ] Email Receiving toggled to match intent ([sending vs receiving](/docs/guides/connect-domain/sending-vs-receiving)); default is on for new domains - [ ] Tracking CNAME host matches the dashboard (`link` or `link.`) and is free or intentionally replaced ## Provider-specific guides See the full list: [Provider guides](/docs/guides/connect-domain/providers). ## Related - [DNS records explained](/docs/guides/connect-domain/dns-records-explained) - [Domain not verifying](/docs/guides/connect-domain/troubleshoot/not-verifying) --- # guides/connect-domain/providers/cloudflare.mdx Source: https://reloop.sh/docs/guides/connect-domain/providers/cloudflare Markdown: https://reloop.sh/docs/guides/connect-domain/providers/cloudflare.md ## Add domain to Reloop First, log in to [Reloop](https://reloop.sh/dashboard/domain) and [add a domain](/docs/guides/connect-domain/add-domain). It is [best practice to use a subdomain](/docs/guides/connect-domain/what-is-a-domain) (for example `mail.example.com` or `team.example.com`) instead of the root domain (`example.com`). A subdomain keeps sending reputation separate and is especially important if you use Reloop receiving (on by default for new domains). ## Automatic setup (recommended) The fastest way to verify your domain on Cloudflare is **Auto populate** in Reloop. This uses [Domain Connect](https://www.domainconnect.org/) to configure your DNS records. Auto populate is only on Reloop Cloud. On a [self-hosted](/docs/self-host) instance, skip to [Manual setup](#manual-setup). 1. Go to your [Domains](https://reloop.sh/dashboard/domain) page in Reloop and open the domain. 2. Check **Email Receiving** before Auto-populate. New domains start with Receiving on, so inbound MX (`inbound.reloop.sh`) is usually included. Turn **Email Receiving** off first if this hostname must keep Google Workspace / Microsoft 365, or use a [subdomain](/docs/guides/connect-domain/what-is-a-domain). [Learn more below](#receiving-emails). 3. Click **Auto populate**. 4. Authorize Reloop in Cloudflare and approve the records. 5. Return to Reloop and click **Verify & finish** (setup) or **Verify Domain** (detail) to start checks.