# Integrate AutoSend with Bolt
Source: https://docs.autosend.com/ai/integrations/bolt
Send emails from your Bolt app using AutoSend's email API.
Bolt is an AI-powered platform that lets you build full-stack web apps from natural language prompts. You
can integrate AutoSend into any Bolt project to send transactional emails like welcome emails,
password resets, order confirmations, and notifications.
## Prerequisites
Sign up for an AutoSend account to get started with sending emails.
Make sure you have a verified domain added in AutoSend.
Create a new API key from your AutoSend dashboard.
Sign up for Bolt to start building apps.
## Integration
Copy and paste this prompt into Bolt's chat:
```text theme={null}
Integrate AutoSend email API for sending transactional emails.
API Details:
- Base URL: https://api.autosend.com/v1
- Auth: Bearer token using API key stored in environment variable AUTOSEND_API_KEY
- Endpoint: POST /mails/send
- Content-Type: application/json
Request body format:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@mail.yourdomain.com", "name": "Your App Name" },
"subject": "Your subject line",
"html": "Hello!
Your email content here.
"
}
Response format:
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": "Email queued successfully.",
"totalRecipients": 1
}
}
Create a server-side API route or function that:
1. Reads AUTOSEND_API_KEY from environment variables
2. Accepts to, from, subject, and html in the request body
3. Calls the AutoSend API with Bearer token auth
4. Returns the response to the client
The "from" email must use a domain verified in AutoSend.
Docs: https://docs.autosend.com/quickstart/email-using-api
```
Bolt will generate the backend code and wire it into your app automatically.
Add your AutoSend API key as an environment variable in your Bolt project settings with the name `AUTOSEND_API_KEY`.
Get your API key from the API Keys page in your AutoSend dashboard.
Trigger the email flow in your app (for example, submitting a contact form). Check your Email Activity in the AutoSend dashboard to confirm the email was delivered.
## Using Email Templates
If you have created email templates in AutoSend, you can send emails using a `templateId` instead of inline HTML. Update the prompt to include:
```text theme={null}
Also support sending emails with a templateId and variables.
Example request body with template:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@yourdomain.com", "name": "Your App Name" },
"subject": "Welcome!",
"templateId": "your_template_id",
"dynamicData": {
"firstName": "Jane",
"loginLink": "https://yourapp.com/login"
}
}
```
## Troubleshooting
Make sure your `AUTOSEND_API_KEY` is correctly added in your project's environment variables. Also verify that your sending domain is verified in AutoSend by checking **Settings > Domains** in your dashboard.
Double-check that your code is reading the API key from environment variables correctly. The Authorization header should be `Bearer YOUR_API_KEY` with no extra spaces or quotes.
Make sure you have completed domain verification including SPF, DKIM, and DMARC records. We recommend using a subdomain like `mail.yourdomain.com` for sending. See our domain setup guide for details.
## Next Steps
Create reusable email templates with dynamic variables.
Monitor delivery status and engagement for all emails.
Full API documentation with all endpoints and parameters.
Personalize emails with dynamic variables.
# Integrate AutoSend with Lovable
Source: https://docs.autosend.com/ai/integrations/lovable
Add email sending to your Lovable app using AutoSend's email API.
Lovable is an AI-powered platform that lets you build full-stack web apps from natural language prompts. You can integrate AutoSend into any Lovable project to send transactional emails like welcome emails,
password resets, order confirmations, and notifications.
AutoSend works with Lovable through its REST API. Since the API requires an API key, Lovable will use **Supabase Edge Functions** to keep your credentials secure.
## Prerequisites
Sign up for an AutoSend account to get started with sending emails.
Make sure you have a verified domain added in AutoSend to send emails from.
Create a new API key from your AutoSend dashboard for authentication.
Connect Supabase to your Lovable project to use Edge Functions.
## Integration
Lovable uses Supabase Edge Functions to securely handle API keys. If you haven't connected Supabase yet, click the **Supabase** icon in your Lovable project and follow the authorization steps.
1. In your Lovable project, go to **Supabase > Secrets**
2. Add a new secret with the name `AUTOSEND_API_KEY`
3. Paste your AutoSend API key as the value
Get your API key from the API Keys page in your AutoSend dashboard.
Copy and paste this prompt into Lovable's chat:
```text theme={null}
Integrate AutoSend email API for sending transactional emails.
API Details:
- Base URL: https://api.autosend.com/v1
- Auth: Bearer token using the AUTOSEND_API_KEY secret
- Endpoint: POST /mails/send
- Content-Type: application/json
Request body format:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@mail.yourdomain.com", "name": "Your App Name" },
"subject": "Your subject line",
"html": "Hello!
Your email content here.
"
}
Response format:
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": "Email queued successfully.",
"totalRecipients": 1
}
}
Create a Supabase Edge Function called "send-email" that:
1. Reads AUTOSEND_API_KEY from environment secrets
2. Accepts to, from, subject, and html in the request body
3. Calls the AutoSend API with Bearer token auth
4. Returns the response to the client
The "from" email must use a domain verified in AutoSend.
Docs: https://docs.autosend.com/quickstart/email-using-api
```
Lovable will generate a Supabase Edge Function and wire it into your app automatically.
Once Lovable finishes generating the code, trigger the email flow in your app (for example, submitting a contact form). Check your Email Activity in the AutoSend dashboard to confirm the email was delivered.
## Using Email Templates
If you have created email templates in AutoSend, you can send emails using a `templateId` instead of inline HTML. Update the prompt to include:
```text theme={null}
Also support sending emails with a templateId and variables.
Example request body with template:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@yourdomain.com", "name": "Your App Name" },
"subject": "Welcome!",
"templateId": "your_template_id",
"dynamicData": {
"firstName": "Jane",
"loginLink": "https://yourapp.com/login"
}
}
```
## Example Use Cases
Here are some common ways to use AutoSend in your Lovable apps:
* **Contact forms** that send a confirmation email to the user and a notification to your team
* **User signup flows** with welcome emails or email verification
* **Order confirmations** and shipping notifications for e-commerce apps
* **Password reset emails** triggered from your auth flow
* **Newsletter signups** that send a double opt-in confirmation
## Troubleshooting
Make sure your `AUTOSEND_API_KEY` is correctly added in Supabase Secrets. Also verify that your sending domain is verified in AutoSend by checking **Settings > Domains** in your dashboard.
Double-check that the Edge Function is reading the secret correctly. The Authorization header should be `Bearer YOUR_API_KEY` with no extra spaces or quotes.
Make sure you have completed domain verification including SPF, DKIM, and DMARC records. We recommend using a subdomain like `mail.yourdomain.com` for sending. See our domain setup guide for details.
Lovable may sometimes show false positive build errors. Always test your app live by triggering the actual email flow. If the Edge Function deploys successfully in Supabase, the integration is likely working.
## Next Steps
Create reusable email templates with dynamic variables.
Monitor delivery status and engagement for all emails.
Full API documentation with all endpoints and parameters.
Personalize emails with dynamic variables.
# Integrate AutoSend with Replit
Source: https://docs.autosend.com/ai/integrations/replit
Add email capabilities to your Replit project using AutoSend.
Replit is a collaborative, AI-powered development platform that lets you build and deploy apps from your browser. You can integrate AutoSend into any Replit project to send transactional emails like welcome emails, password resets, order confirmations, and notifications.
## Prerequisites
Sign up for an AutoSend account to get started with sending emails.
Make sure you have a verified domain added in AutoSend.
Create a new API key from your AutoSend dashboard.
Sign up for Replit to start building.
## Integration
1. In your Replit project, open the **Secrets** tab (lock icon in the sidebar)
2. Add a new secret with the key `AUTOSEND_API_KEY`
3. Paste your AutoSend API key as the value
Get your API key from the API Keys page in your AutoSend dashboard.
Copy and paste this prompt into Replit's AI chat:
```text theme={null}
Integrate AutoSend email API for sending transactional emails.
API Details:
- Base URL: https://api.autosend.com/v1
- Auth: Bearer token using the AUTOSEND_API_KEY secret
- Endpoint: POST /mails/send
- Content-Type: application/json
Request body format:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@mail.yourdomain.com", "name": "Your App Name" },
"subject": "Your subject line",
"html": "Hello!
Your email content here.
"
}
Response format:
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": "Email queued successfully.",
"totalRecipients": 1
}
}
Create a server-side endpoint that:
1. Reads AUTOSEND_API_KEY from environment secrets
2. Accepts to, from, subject, and html in the request body
3. Calls the AutoSend API with Bearer token auth
4. Returns the response to the client
The "from" email must use a domain verified in AutoSend.
Docs: https://docs.autosend.com/quickstart/email-using-api
```
Replit Agent will generate the backend code and connect it to your app.
Trigger the email flow in your app (for example, submitting a contact form). Check your Email Activity in the AutoSend dashboard to confirm the email was delivered.
## Using Email Templates
If you have created email templates in AutoSend, you can send emails using a `templateId` instead of inline HTML. Update the prompt to include:
```text theme={null}
Also support sending emails with a templateId and variables.
Example request body with template:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@yourdomain.com", "name": "Your App Name" },
"subject": "Welcome!",
"templateId": "your_template_id",
"dynamicData": {
"firstName": "Jane",
"loginLink": "https://yourapp.com/login"
}
}
```
## Troubleshooting
Make sure your `AUTOSEND_API_KEY` is correctly added in Replit Secrets. Also verify that your sending domain is verified in AutoSend by checking **Settings > Domains** in your dashboard.
Double-check that your code is reading the secret correctly. The Authorization header should be `Bearer YOUR_API_KEY` with no extra spaces or quotes.
Make sure you have completed domain verification including SPF, DKIM, and DMARC records. We recommend using a subdomain like `mail.yourdomain.com` for sending. See our domain setup guide for details.
## Next Steps
Create reusable email templates with dynamic variables.
Monitor delivery status and engagement for all emails.
Full API documentation with all endpoints and parameters.
Personalize emails with dynamic variables.
# Integrate AutoSend with v0
Source: https://docs.autosend.com/ai/integrations/v0
Integrate AutoSend email sending into your v0 project.
v0 is Vercel's AI-powered tool for generating UI components and full-stack applications. You can integrate AutoSend into your v0 project to send transactional emails like welcome emails, password resets, order confirmations, and notifications.
## Prerequisites
Sign up for an AutoSend account to get started with sending emails.
Make sure you have a verified domain added in AutoSend.
Create a new API key from your AutoSend dashboard.
Sign up for v0 to start building.
## Integration
Copy and paste this prompt into v0's chat:
```text theme={null}
Integrate AutoSend email API for sending transactional emails.
API Details:
- Base URL: https://api.autosend.com/v1
- Auth: Bearer token using API key stored in environment variable AUTOSEND_API_KEY
- Endpoint: POST /mails/send
- Content-Type: application/json
Request body format:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@mail.yourdomain.com", "name": "Your App Name" },
"subject": "Your subject line",
"html": "Hello!
Your email content here.
"
}
Response format:
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": "Email queued successfully.",
"totalRecipients": 1
}
}
Create a Next.js API route (app/api/send-email/route.ts) that:
1. Reads AUTOSEND_API_KEY from process.env
2. Accepts to, from, subject, and html in the request body
3. Calls the AutoSend API with Bearer token auth
4. Returns the response to the client
The "from" email must use a domain verified in AutoSend.
Docs: https://docs.autosend.com/quickstart/email-using-api
```
v0 will generate the API route and any UI components needed for your email flow.
When you deploy your v0 project to Vercel, add `AUTOSEND_API_KEY` as an environment variable in your Vercel project settings.
For local development, add it to your `.env.local` file:
```bash .env.local theme={null}
AUTOSEND_API_KEY=your_api_key_here
```
Get your API key from the API Keys page in your AutoSend dashboard.
Trigger the email flow in your app (for example, submitting a contact form). Check your Email Activity in the AutoSend dashboard to confirm the email was delivered.
## Using Email Templates
If you have created email templates in AutoSend, you can send emails using a `templateId` instead of inline HTML. Update the prompt to include:
```text theme={null}
Also support sending emails with a templateId and variables.
Example request body with template:
{
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"from": { "email": "hello@yourdomain.com", "name": "Your App Name" },
"subject": "Welcome!",
"templateId": "your_template_id",
"dynamicData": {
"firstName": "Jane",
"loginLink": "https://yourapp.com/login"
}
}
```
## Troubleshooting
Make sure your `AUTOSEND_API_KEY` is set in your Vercel environment variables (for production)
or `.env.local` (for local development). Also verify that your sending domain is verified in
AutoSend.
Double-check that the API route is reading the API key correctly from
`process.env.AUTOSEND_API_KEY`. The Authorization header should be `Bearer YOUR_API_KEY`.
Make sure you have completed domain verification including SPF, DKIM, and DMARC records. We
recommend using a subdomain like `mail.yourdomain.com` for sending. See our
domain setup guide for details.
## Next Steps
Create reusable email templates with dynamic variables.
Monitor delivery status and engagement for all emails.
Full API documentation with all endpoints and parameters.
Personalize emails with dynamic variables.
# Agentic Integrations
Source: https://docs.autosend.com/ai/introduction
Enable AI agents to work seamlessly with AutoSend. Our MCP server and skills integrate with coding agents, MCP clients, and no-code platforms.
AutoSend integrates with AI coding agents and no-code AI tools so you can add email capabilities to any AI-built application. Whether you're using a coding agent like Cursor or a no-code tool like Lovable, AutoSend makes it easy to send transactional emails, save contacts for marketing campaigns, trigger email automations, and more.
## Developer Tools
Connect AutoSend's MCP server to build email campaigns and templates with AI tools using natural
language.
Install the AutoSend skill so AI agents can seamlessly integrate AutoSend's email API into your
code.
## AI Coding Agents
Connect AutoSend MCP server with Claude Desktop and Claude Code.
Connect AutoSend MCP server with Cursor.
Connect AutoSend MCP server with GitHub Copilot.
Connect AutoSend MCP server with Windsurf.
Connect AutoSend MCP server with OpenAI Codex.
Connect AutoSend MCP server with Antigravity.
Connect AutoSend MCP server with ChatGPT.
## AI App Builders
Integrate AutoSend with your Lovable app.
Integrate AutoSend with your Bolt app.
Integrate AutoSend with your v0 project.
Integrate AutoSend with your Replit project.
# Connect AutoSend MCP to Antigravity
Source: https://docs.autosend.com/ai/mcp-clients/antigravity
Set up AutoSend MCP server in Google Antigravity.
## Setup
Antigravity
is Google's AI-powered coding environment. To connect AutoSend MCP, add the following configuration
to your `~/.gemini/settings.json` file:
```json theme={null}
{
"mcpServers": {
"autosend": {
"command": "npx",
"args": ["mcp-remote", "https://mcp.autosend.com/"]
}
}
}
```
Restart your environment to apply the configuration. When prompted, follow the OAuth flow to authorize access to your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to ChatGPT
Source: https://docs.autosend.com/ai/mcp-clients/chatgpt
Set up AutoSend MCP server as a connector in ChatGPT.
## Setup
Go to **Settings > Connectors > Advanced settings** and enable **Developer mode**.
Open **Settings**, go to the **Connectors** tab, and click **Create** to add a new connector.
Enter the following details:
* **Name:** `AutoSend`
* **MCP server URL:** `https://mcp.autosend.com/`
* **Authentication:** `OAuth`
Click **Create**.
The AutoSend connector will appear in the composer's Developer mode tool. Follow the OAuth prompt to log in to your AutoSend account.
MCP connectors in ChatGPT are available for Pro and Plus accounts on the web.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to Claude
Source: https://docs.autosend.com/ai/mcp-clients/claude
Set up AutoSend MCP server in Claude Desktop and Claude Code.
## Claude Desktop
We'll start by connecting AutoSend MCP to Claude Desktop, which will allow you to use AutoSend tools and workflows directly within the Claude interface.
Open Claude Desktop and click **Customize** in the top-left corner, then click **Connectors**.
Click the **+** button and select **Add custom connector**.
Enter the name as `AutoSend` and the URL as `https://mcp.autosend.com/`.
Click on the **Connect** and follow the OAuth prompt to log in to your AutoSend account.
Claude Desktop does not support remote MCP servers via the `claude_desktop_config.json` file. You
must add remote servers through **Connectors**.
## Claude Code
Run the following command in your terminal:
```bash theme={null}
claude mcp add autosend --transport http https://mcp.autosend.com/
```
When you first use the MCP tools, Claude Code will open your browser to authorize the connection with your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to Codex CLI
Source: https://docs.autosend.com/ai/mcp-clients/codex
Set up AutoSend MCP server in OpenAI Codex CLI.
## Setup
Codex CLI
is OpenAI's local coding agent that runs directly from your terminal.
Run the following command to add AutoSend MCP:
```bash theme={null}
codex mcp add autosend --url https://mcp.autosend.com/
```
When adding the MCP server, Codex will detect OAuth support and open your browser to authorize the connection with your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to VS Code (Copilot)
Source: https://docs.autosend.com/ai/mcp-clients/copilot
Set up AutoSend MCP server in VS Code with GitHub Copilot.
## Option 1: Using Command Palette
Press `Ctrl+Shift+P` on Windows/Linux or `Cmd+Shift+P` on macOS to open the Command Palette.
Run **MCP: Add Server** and select **HTTP**.
Enter the URL as `https://mcp.autosend.com/` and the name as `AutoSend`. Select **Global** or
**Workspace** depending on your needs.
Start the server and follow the OAuth prompt to log in to your AutoSend account.
## Option 2: Using config file
Add the following to your `.vscode/mcp.json` file:
```json theme={null}
{
"servers": {
"autosend": {
"url": "https://mcp.autosend.com/"
}
}
}
```
Start the server and follow the OAuth prompt to log in to your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to Cursor
Source: https://docs.autosend.com/ai/mcp-clients/cursor
Set up AutoSend MCP server in Cursor to manage emails with AI.
## Option 1: Using Settings UI
## Option 2: Using config file
Add the following to your project-specific or global `.cursor/mcp.json` file:
```json theme={null}
{
"mcpServers": {
"autosend": {
"url": "https://mcp.autosend.com/"
}
}
}
```
Once the server is added, Cursor will display a **Needs login** prompt. Click on it to authorize Cursor to access your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to Raycast
Source: https://docs.autosend.com/ai/mcp-clients/raycast
Set up AutoSend MCP server in Raycast.
## Setup
Open Raycast and run the **Install Server** command.
Enter the following details:
* **Name:** `AutoSend`
* **Transport:** HTTP
* **URL:** `https://mcp.autosend.com/`
Click **Install**.
Follow the OAuth prompt to log in to your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# Connect AutoSend MCP to Windsurf
Source: https://docs.autosend.com/ai/mcp-clients/windsurf
Set up AutoSend MCP server in Windsurf to manage emails with AI.
## Setup
Add the following to your `mcp_config.json` file. For more details, see the Windsurf documentation.
```json theme={null}
{
"mcpServers": {
"autosend": {
"serverUrl": "https://mcp.autosend.com/"
}
}
}
```
When you first use the MCP tools, Windsurf will open your browser to authorize the connection with your AutoSend account.
## Next Steps
See all available tools, guided workflows, and examples.
Install skill files for additional AI agent context.
# AutoSend MCP Server
Source: https://docs.autosend.com/ai/mcp-server
Manage AutoSend campaigns, automations, templates, contacts, senders, and analytics from any MCP-compatible AI assistant.
The AutoSend MCP (Model Context Protocol) server lets you manage email campaigns, automations, templates, contacts, senders, and analytics directly from AI assistants like Claude, Cursor, Codex, Antigravity, and other MCP-compatible clients.
It's a remote MCP server with OAuth that gives AI tools secure access to your AutoSend project, available at:
```html theme={null}
https://mcp.autosend.com/
```
Instead of switching between your AI assistant and the AutoSend dashboard, you can use natural language to:
* Create, schedule, and send email campaigns
* Design and update email templates
* Build and activate multi-step email automations
* Browse your contact lists, segments, contact properties, and event definitions
* Look up verified senders and suppression groups
* Pull campaign and email activity analytics
* Switch between AutoSend projects on the fly
## Prerequisites
Sign up for an AutoSend account to get started.
Add and verify a domain in AutoSend to send emails from.
Set up at least one verified sender for sending emails.
## Connecting to AutoSend MCP
AutoSend MCP uses a streamable HTTP transport with OAuth 2.0 authentication. When you add the server to your MCP client, the client handles the OAuth authorization flow automatically. You just need to log in to your AutoSend account when prompted.
Choose your AI client to get started:
Claude Desktop and Claude Code
Settings UI or config file setup
VS Code with GitHub Copilot
Windsurf MCP configuration
OpenAI Codex command line
Google Antigravity setup
ChatGPT connector setup
Raycast MCP extension
## Authentication
AutoSend MCP uses **OAuth 2.0** to securely connect to your account. You don't need to manage any API keys or tokens yourself. Your MCP client handles everything automatically.
When you first connect, you'll be redirected to AutoSend to log in and authorize the connection. Once authorized, your client stores the credentials and refreshes them as needed. Each connection is scoped to a single AutoSend project, so your data stays isolated and secure.
## Available Tools
AutoSend MCP exposes 34 tools across 10 categories. Your AI assistant automatically uses the right tools based on your requests.
### Projects
| Tool | Description |
| --------------------- | ----------------------------------------------------------- |
| `get_current_project` | Get the active project's name, domain, and ID |
| `list_projects` | List all projects you have access to and see the active one |
| `switch_project` | Switch the active project for the current MCP session |
### Lists & Segments
| Tool | Description |
| ------------------------ | --------------------------------------------------- |
| `get_lists_and_segments` | Fetch your contact lists and segments for targeting |
| `create_contact_list` | Create a new contact list |
### Contact Properties
| Tool | Description |
| ------------------------- | ------------------------------------------------------------------------------ |
| `list_contact_properties` | List contact properties with their names and types |
| `list_custom_fields` | Deprecated alias of `list_contact_properties`, kept for backward compatibility |
### Templates
| Tool | Description |
| ------------------ | ------------------------------------------------- |
| `list_templates` | List all email templates in your account |
| `search_templates` | Search templates by name, content, or type |
| `get_template` | Get a specific template by ID |
| `create_template` | Create a new email template using HTML |
| `update_template` | Update an existing template's content or settings |
| `delete_template` | Permanently delete a template |
### Senders
| Tool | Description |
| --------------- | ----------------------------------------- |
| `list_senders` | List all verified senders in your account |
| `get_sender` | Get details of a specific verified sender |
| `create_sender` | Add a new verified sender |
### Suppression Groups
| Tool | Description |
| -------------------------- | ------------------------------------------- |
| `list_suppression_groups` | List suppression (unsubscribe) groups |
| `get_suppression_group` | Get details of a specific suppression group |
| `create_suppression_group` | Create a new suppression group |
### Campaigns
| Tool | Description |
| -------------------- | ----------------------------------------------------------------------- |
| `list_campaigns` | List campaigns with optional filters by status, name, or date |
| `get_campaign` | Get details of a specific campaign including metrics |
| `create_campaign` | Create a draft campaign with HTML content in one call |
| `update_campaign` | Update an existing draft campaign's settings or content |
| `delete_campaign` | Permanently delete a campaign |
| `duplicate_campaign` | Duplicate an existing campaign as a new draft |
| `send_campaign` | Send a draft campaign now or schedule it for later (confirmation gate) |
| `send_test_email` | Send a test email to preview a template or campaign (confirmation gate) |
### Events
| Tool | Description |
| ------------------------ | -------------------------------------------------------- |
| `list_event_definitions` | List custom event types defined for the project |
| `get_event_definition` | Get full details of an event, including known properties |
### Automations
| Tool | Description |
| --------------------- | ------------------------------------------------------------------------------- |
| `list_automations` | List automations with optional filters for status, tags, and pagination |
| `get_automation` | Get full details of an automation, including entry criteria and steps |
| `create_automation` | Create a draft automation (workflow) with triggers, waits, emails, and branches |
| `activate_automation` | Activate a draft automation so it starts enrolling contacts (confirmation gate) |
### Analytics
| Tool | Description |
| ------------------------------ | --------------------------------------------------------------------------------------- |
| `get_campaign_analytics` | Get delivery analytics for a campaign (sent, opened, clicked, bounced, unsubscribed) |
| `get_email_activity_analytics` | Get aggregated email activity, optionally filtered by date range, template, or campaign |
## Building Automations
Email automations are multi-step workflows that send sequences of emails triggered by contact actions. You build them through MCP in two stages:
1. **Create a draft** with `create_automation`. You define the entry criteria (contact created, property matches, property changes, or event received), optional exit criteria, and a flat list of steps made up of `wait`, `email`, and `branch` blocks. Drafts never enrol contacts or send anything.
2. **Activate the draft** with `activate_automation`. The tool has a confirmation gate, call it first with `confirmed: false` to preview the workflow, then again with `confirmed: true` to start enrolling contacts and sending emails.
A few preconditions to keep in mind:
* Every email step must reference an automation-type template. Call `create_template` with `templateType: "automation"` first, then pass the returned `templateId` into the step.
* Before referencing a contact field in a filter, call `list_contact_properties` so you use the exact bare property name.
* For event-triggered automations or event-property branches, call `list_event_definitions` (or `get_event_definition` for a single event) to discover the available property keys.
Learn more about Email Automations, Events, and Contact Properties.
## Guided Workflows
AutoSend MCP includes three guided workflow prompts that walk your AI assistant through multi-step processes. These prompts provide structured instructions so the assistant gathers the right information and calls the right tools in sequence.
### `create-campaign`
A step-by-step workflow to create a draft email campaign:
1. **Gathers data** - Fetches your lists, segments, senders, and suppression groups
2. **Asks for details** - Campaign purpose, target audience, sender, unsubscribe group, subject line, and preview text
3. **Designs the email** - Proposes a template structure and generates responsive HTML
4. **Creates the draft** - Calls `create_campaign` to create the template and campaign together
5. **Reviews** - Shows a summary with the campaign ID and next steps
| Argument | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------ |
| `context` | string | No | Initial context about the campaign purpose |
### `create-template`
A step-by-step workflow to create a reusable email template:
1. **Understands purpose** - Asks about template type (marketing or transactional), audience, brand style, and sections
2. **Designs HTML** - Generates responsive email HTML following email client compatibility best practices
3. **Creates the template** - Calls `create_template` with all metadata
4. **Reviews** - Shows a summary with the template ID and next steps
| Argument | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------ |
| `context` | string | No | Initial context about the template purpose |
### `create-automation`
A step-by-step workflow to build a draft email automation:
1. **Gathers data** - Fetches your lists, segments, senders, suppression groups, contact properties, and event definitions
2. **Asks for details** - Automation name, trigger type, exit conditions, number of emails, timing between them, and any branch logic
3. **Designs the workflow** - Creates the automation-type templates each step needs and proposes the wait, email, and branch sequence
4. **Creates the draft** - Calls `create_automation` to assemble the workflow as a draft
5. **Reviews** - Shows a summary with the automation ID and how to go live with `activate_automation`
| Argument | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------------------- |
| `context` | string | No | Initial context about the automation purpose |
All three prompts support an **auto mode**. Say "auto", "take over", or "just do it" at any
point, and the assistant will make reasonable decisions for all remaining steps without asking
for confirmation.
## Example Conversations
Here are some things you can ask your AI assistant once AutoSend MCP is connected:
**Campaign management:**
* "Create a newsletter campaign for our March product updates"
* "Show me all draft campaigns"
* "Duplicate the Black Friday campaign and update it for our spring sale"
* "Delete the test campaign I created yesterday"
**Template management:**
* "Create a transactional template for order confirmation emails"
* "Search for templates related to onboarding"
* "Update the welcome email template with a new header design"
**Automation workflows:**
* "Build a 3-email onboarding automation for new Pro plan signups"
* "Show me all my active automations"
* "Activate the welcome series draft we built yesterday"
* "Create an automation that fires when the `order_placed` event is logged"
**Analytics:**
* "What were the open and click rates on last week's newsletter?"
* "Show me email activity for the past 30 days"
**Projects:**
* "Which AutoSend project am I currently on?"
* "Switch me to the staging project"
**Looking up data:**
* "List all my contact segments and their sizes"
* "Show me my verified senders"
* "What suppression groups and contact properties do I have?"
**End-to-end workflows:**
* "I want to send a product launch announcement to our Premium Users segment. Walk me through it."
* "Help me create a reusable welcome email template with the recipient's first name and company"
## Important Notes
All campaigns created through MCP are saved as drafts. You must publish or send them from the
AutoSend dashboard. This is a safety measure to prevent accidental sends.
* **Confirmation gates** - `send_campaign`, `send_test_email`, and `activate_automation` all require `confirmed: true` to actually execute. Call them first with `confirmed: false` to preview what will happen before committing.
* **Project scope** - Each MCP session has one active project at a time. Use `list_projects` to see all projects you can access and `switch_project` to change focus mid-session.
* **Template variables** - Use Handlebars syntax (`{{variableName}}`) for dynamic content. Variable names must start with a letter and can include letters, numbers, and underscores.
* **Character limits** - Campaign name: 200 characters, subject line: 988 characters, preview text: 140 characters.
* **Email HTML** - The MCP server's guided workflows generate email-safe HTML using table-based layouts, inline CSS, and responsive design patterns that work across all major email clients.
* **Scoped access** - Each MCP connection is scoped to a single AutoSend organization. The server cannot access data from organizations you don't belong to.
## Next Steps
Install skill files for additional AI agent context.
Full API documentation with all endpoints and parameters.
Learn more about creating and managing email campaigns.
Create reusable email templates with dynamic variables.
# AutoSend Skill
Source: https://docs.autosend.com/ai/skills
Install the AutoSend skill to give AI coding agents context about the AutoSend email API.
## Overview
The AutoSend skill gives AI coding agents the context they need to send transactional emails, manage contacts, and use templates via the AutoSend REST API. Once installed, your AI agent can generate accurate integration code without any additional prompting.
The skill is distributed through skills.sh and can be installed with a single command.
## Installation
Run the following command in your project root:
```bash theme={null}
npx skills add https://github.com/autosendhq/skills --skill AutoSend
```
This automatically detects your AI coding agent and installs the skill file in the correct location.
Set your AutoSend API key as an environment variable:
```bash theme={null}
export AUTOSEND_API_KEY=as_your_key_here
```
Or add it to your `.env` file:
```bash .env theme={null}
AUTOSEND_API_KEY=as_your_key_here
```
Get your API key from the API Keys page in your AutoSend dashboard under **Settings > API Keys > Generate API Key**.
Ask your AI agent to send an email using AutoSend. It should generate code that uses the correct API endpoint, authentication, and request format without any additional prompting.
**Example prompt:**
```text theme={null}
Send a welcome email to a new user using AutoSend. The user's email is in the `user.email` variable.
```
## Supported Platforms
The AutoSend skill works with the following AI coding agents:
| Platform | Skill File Location |
| ------------------ | ---------------------------- |
| **Cursor** | `.cursor/rules/autosend.mdc` |
| **Claude Code** | `.claude/CLAUDE.md` |
| **Codex** | `.codex/skills/` |
| **Antigravity** | `.agent/skills/` |
| **Windsurf** | `.windsurfrules` |
| **OpenCode** | Agent-specific config |
| **Gemini CLI** | Agent-specific config |
| **GitHub Copilot** | Agent-specific config |
| **Amp** | Agent-specific config |
| **Kimi CLI** | Agent-specific config |
The `npx skills add` command automatically detects which agent you're using and places the skill file in the right location.
## Sample Prompts
After installing the skill, your AI coding agent has full context about the AutoSend API. Here are some prompts to get started:
```text theme={null}
Send a welcome email to new users after signup using AutoSend.
```
```text theme={null}
When a user registers, save their contact info to AutoSend with their name and email
so we can send them marketing campaigns later.
```
```text theme={null}
Create an API route that sends an order confirmation email using an AutoSend template
with dynamic variables for orderNumber, customerName, and orderTotal.
```
```text theme={null}
Add a function to upsert a contact in AutoSend whenever a user updates their profile,
and add them to the "newsletter" list.
```
## Prerequisites
Before using the AutoSend API in your project, make sure you have:
Sign up at autosend.com to get started.
Go to **Settings > Domains > Add Domain** and select your AutoSend region.
Copy the generated DNS records (DKIM, SPF, DMARC) and add them to your DNS provider.
Click **Verify Ownership** in the AutoSend dashboard. Wait 5–30 minutes for the status to turn green. See the domain setup guide for details.
Go to **Settings > API Keys > Generate API Key** in your AutoSend dashboard.
```bash theme={null}
export AUTOSEND_API_KEY=as_your_key_here
```
## What's Included
The skill provides your AI agent with full context about the AutoSend API, including authentication, endpoints, request/response formats, and error handling.
### Authentication
| Detail | Value |
| ---------------- | ------------------------------------------------------- |
| **Base URL** | `https://api.autosend.com/v1` |
| **Auth Header** | `Authorization: Bearer YOUR_API_KEY` |
| **Content-Type** | `application/json` (required for all POST/PUT requests) |
### Email Operations
#### Send Email — `POST /v1/mails/send`
Send a single transactional email.
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------------------------------- |
| `from` | object | Yes | Sender — `{ "email": "...", "name": "..." }` |
| `to` | object | Yes | Recipient — `{ "email": "...", "name": "..." }` |
| `subject` | string | Yes | Email subject line |
| `html` | string | No | HTML body |
| `text` | string | No | Plain text body |
| `templateId` | string | No | Template ID (replaces html/text) |
| `dynamicData` | object | No | Template variable substitutions |
| `cc` | array | No | CC recipients — `[{ "email": "...", "name": "..." }]` |
| `bcc` | array | No | BCC recipients — `[{ "email": "...", "name": "..." }]` |
| `replyTo` | object | No | Reply-to address — `{ "email": "...", "name": "..." }` |
| `attachments` | array | No | File attachments — `[{ "filename": "...", "content": "..." }]` |
**Response:**
```json theme={null}
{
"success": true,
"data": { "emailId": "email_abc123" }
}
```
#### Send with Template — `POST /v1/mails/send` with `templateId`
Use a template instead of inline HTML, with dynamic variables for personalization.
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | -------------------------------------- |
| `templateId` | string | Yes | Template identifier |
| `dynamicData` | object | No | Key-value pairs for template variables |
**Common template IDs:**
| Template | ID | Typical Variables |
| ------------------ | ------------------------- | ---------------------------------------------------------------- |
| Order Confirmation | `tmpl_order_confirmation` | `orderNumber`, `customerName`, `orderTotal`, `estimatedDelivery` |
| Welcome Email | `tmpl_welcome` | `firstName`, `activationLink`, `supportEmail` |
| Password Reset | `tmpl_password_reset` | `resetLink`, `expiresIn` |
**Example request:**
```json theme={null}
{
"from": { "email": "hello@mail.yourdomain.com", "name": "Your App" },
"to": { "email": "recipient@example.com", "name": "Jane Doe" },
"subject": "Welcome!",
"templateId": "tmpl_welcome",
"dynamicData": {
"firstName": "Jane",
"activationLink": "https://yourapp.com/activate"
}
}
```
#### Bulk Send — `POST /v1/mails/bulk`
Send to up to 100 recipients in a single API call.
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ----------------------------------------------------- |
| `from` | object | Yes | Shared sender — `{ "email": "...", "name": "..." }` |
| `subject` | string | No | Shared subject (required unless template provides it) |
| `html` | string | No | Shared HTML body |
| `text` | string | No | Shared plain text body |
| `templateId` | string | No | Template ID for templated emails |
| `dynamicData` | object | No | Shared default template variables |
| `recipients` | array | Yes | Array of recipient objects (max 100) |
**Recipient object:**
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------ |
| `email` | string | Yes | Recipient email address |
| `name` | string | No | Recipient display name |
| `dynamicData` | object | No | Per-recipient variables (overrides shared) |
| `cc` | array | No | Per-recipient CC |
| `bcc` | array | No | Per-recipient BCC |
**Response:**
```json theme={null}
{
"success": true,
"data": {
"batchId": "batch_abc123",
"totalRecipients": 2,
"successCount": 2,
"failedCount": 0
}
}
```
### Contact Management
#### Create Contact — `POST /v1/contacts`
| Parameter | Type | Required | Description |
| ------------------- | ------ | -------- | ---------------------------------------------------- |
| `email` | string | Yes | Contact email address |
| `firstName` | string | No | Contact first name |
| `lastName` | string | No | Contact last name |
| `userId` | string | No | External user ID |
| `listIds` | array | No | Lists to add contact to — `["list_abc", "list_xyz"]` |
| `contactProperties` | object | No | Contact property values |
**Response:**
```json theme={null}
{
"success": true,
"data": {
"id": "contact_abc123",
"email": "user@example.com",
"firstName": "Jane",
"lastName": "Smith",
"listIds": ["list_abc"],
"contactProperties": {},
"createdAt": "2025-01-15T00:00:00Z",
"updatedAt": "2025-01-15T00:00:00Z"
}
}
```
#### Get Contact — `GET /v1/contacts/:id`
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------- |
| `id` | string | Yes | Contact ID (path parameter) |
**Response:** Returns the full contact object (same shape as Create Contact response).
#### Upsert Contact — `POST /v1/contacts/email`
Creates a new contact or updates an existing one by email address. If the contact exists, it updates the record; otherwise it creates a new contact.
| Parameter | Type | Required | Description |
| ------------------- | ------ | -------- | ----------------------- |
| `email` | string | Yes | Contact email address |
| `firstName` | string | No | Contact first name |
| `lastName` | string | No | Contact last name |
| `userId` | string | No | External user ID |
| `listIds` | array | No | Lists to add contact to |
| `contactProperties` | object | No | Contact property values |
**Response:** Returns the full contact object (same shape as Create Contact response).
#### Delete Contact — `DELETE /v1/contacts/:id`
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------- |
| `id` | string | Yes | Contact ID (path parameter) |
**Response:**
```json theme={null}
{
"success": true
}
```
### Error Codes
| Status | Code | Description |
| ------ | --------------------- | ------------------------------------------- |
| 400 | `VALIDATION_FAILED` | Bad request — missing or invalid parameters |
| 401 | `UNAUTHORIZED` | Invalid or missing API key |
| 402 | `PAYMENT_REQUIRED` | Plan upgrade needed |
| 403 | `FORBIDDEN` | Insufficient permissions |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMIT_EXCEEDED` | Too many requests — retry with backoff |
| 500 | `SERVER_ERROR` | Internal server error |
## Next Steps
Connect AutoSend's MCP server for direct API access from your AI tools.
Full API documentation with all endpoints and parameters.
Add and verify your sending domain.
Generate and manage your API keys.
# API Keys
Source: https://docs.autosend.com/api-keys
All API requests to AutoSend must be authenticated using an API key. This guide covers the two types of API keys, how to create them, and how to use them.
## Overview
AutoSend uses **API Key authentication** with Bearer tokens. Every API request must include your API key in the `Authorization` header. AutoSend has two types of API keys, each with different scope and purpose.
## Key Types
* **Project API Key** (`AS_...`): Scoped to a single project. Use this for sending emails, managing contacts, templates, campaigns, and all day-to-day operations. This is the right choice for most integrations.
* **Account API Key** (`ASA_...`): Cross-project scope. Use this when you need to programmatically create, update, or delete projects - for example, when building a multi-tenant platform. All requests made with an Account API Key require an `x-project-id` header to specify the target project, except when creating a new project.
## Creating an API Key
Go to **Account > API Keys** from the sidebar. Click the **"Generate Key"** button.
A modal will open where you:
1. Enter a descriptive name (e.g. `Production`, `Staging`, `Multi-tenant Service`)
2. Select the key type: **Account API Key** or **Project API Key**
3. If you selected **Project API Key**: choose which project this key belongs to
Click **"Generate"**.
After generation, your **API Key Secret** is shown once:
For security reasons, the API key secret is only shown once during creation. You will NOT be able to view it again.
* Copy the key immediately to your clipboard
* Store it securely in a password manager or secrets vault
* Download the .txt file as a backup
* Never commit API keys to version control
* Never share your API key publicly or in client-side code
## API Key Formats
AutoSend API keys follow these formats depending on the type:
```
ASA_[secret_string] ← Account API Key
AS_[secret_string] ← Project API Key
```
* **`ASA`**: Prefix for Account-scoped keys
* **`AS`**: Prefix for Project-scoped keys
* **`secret_string`**: Cryptographically secure alphanumeric characters
## Making Authenticated Requests
Include your API key in the `Authorization` header of every request.
### Project API Key
```
Authorization: Bearer AS_your_project_api_key
```
```bash cURL expandable theme={null}
curl -X POST https://api.autosend.com/v1/mails/send \
-H "Authorization: Bearer AS_your_project_api_key" \
-H "Content-Type: application/json" \
-d '{
"to": {
"email": "customer@example.com"
},
"from": {
"email": "hello@mail.yourdomain.com"
},
"subject": "Test Email",
"html": "Hello World!
"
}'
```
```javascript NodeJS expandable theme={null}
const fetch = require('node-fetch');
const API_KEY = process.env.AUTOSEND_API_KEY;
async function sendEmail() {
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: {
email: 'customer@example.com',
},
from: {
email: 'hello@mail.yourdomain.com',
},
subject: 'Test Email',
html: 'Hello World!
',
}),
});
const data = await response.json();
console.log(data);
}
sendEmail();
```
```python Python expandable theme={null}
import requests
import os
API_KEY = os.environ.get('AUTOSEND_API_KEY')
def send_email():
url = 'https://api.autosend.com/v1/mails/send'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'to': {
'email': 'customer@example.com'
},
'from': {
'email': 'hello@mail.yourdomain.com'
},
'subject': 'Test Email',
'html': 'Hello World!
'
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
send_email()
```
### Account API Key
Account API Keys require an additional `x-project-id` header on all requests, so AutoSend knows which project to operate on.
```
Authorization: Bearer ASA_your_account_api_key
x-project-id: your_project_id
```
The `x-project-id` header is **not required** when creating a new project, since no project exists
yet to target.
To find your Project ID, go to [Project Settings > General](https://autosend.com/settings/project) and copy it from the **Project Info** section.
```bash cURL expandable theme={null}
curl -X POST https://api.autosend.com/v1/mails/send \
-H "Authorization: Bearer ASA_your_account_api_key" \
-H "x-project-id: your_project_id" \
-H "Content-Type: application/json" \
-d '{
"to": {
"email": "customer@example.com"
},
"from": {
"email": "hello@mail.yourdomain.com"
},
"subject": "Test Email",
"html": "Hello World!
"
}'
```
```javascript NodeJS expandable theme={null}
const fetch = require('node-fetch');
const API_KEY = process.env.AUTOSEND_ACCOUNT_API_KEY;
const PROJECT_ID = process.env.AUTOSEND_PROJECT_ID;
async function sendEmail() {
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'x-project-id': PROJECT_ID,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: {
email: 'customer@example.com',
},
from: {
email: 'hello@mail.yourdomain.com',
},
subject: 'Test Email',
html: 'Hello World!
',
}),
});
const data = await response.json();
console.log(data);
}
sendEmail();
```
```python Python expandable theme={null}
import requests
import os
API_KEY = os.environ.get('AUTOSEND_ACCOUNT_API_KEY')
PROJECT_ID = os.environ.get('AUTOSEND_PROJECT_ID')
def send_email():
url = 'https://api.autosend.com/v1/mails/send'
headers = {
'Authorization': f'Bearer {API_KEY}',
'x-project-id': PROJECT_ID,
'Content-Type': 'application/json'
}
payload = {
'to': {
'email': 'customer@example.com'
},
'from': {
'email': 'hello@mail.yourdomain.com'
},
'subject': 'Test Email',
'html': 'Hello World!
'
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
send_email()
```
## Choosing the Right API Key
* Use a **Project API Key** for everyday operations: sending emails, managing contacts, templates, campaigns, and webhooks within a specific project. This is the right key for most integrations.
* Use an **Account API Key** only when managing projects themselves - for example, when building a multi-tenant platform that automatically provisions new projects for each of your customers.
## Managing API Keys
### Viewing API Keys
In your dashboard under **Account > API Keys**, you can see:
* **API Key Name**: the label you assigned
* **Type**: Account or Project
* **Project**: the associated project (Project API Keys only)
* **Generated On**: creation date
Note: You cannot view the secret after creation. Only the key ID is visible.
### Deleting API Keys
To delete an API key:
1. Go to **Account > API Keys**
2. Find the key you want to delete
3. Click the trash icon
4. Confirm the deletion
When you delete an API key, it stops working immediately. Any applications or services using that
key will start receiving authentication errors. Make sure to update your applications before
deleting keys.
### Best Practices for API Key Management
1. **Use Environment Variables**
```bash theme={null}
# .env file
AUTOSEND_API_KEY=AS_your_project_api_key
AUTOSEND_ACCOUNT_API_KEY=ASA_your_account_api_key
AUTOSEND_PROJECT_ID=your_project_id
```
Never hardcode API keys in your source code.
2. **Separate Keys for Different Environments**
* Create separate Project API Keys for development, staging, and production
* Use descriptive names: `Production`, `Staging`, `Development`
* This allows you to rotate keys without affecting other environments
3. **Rotate Keys Regularly**
* Generate new keys periodically (every 90 days recommended)
* Update your applications with the new key
* Delete the old key after confirming the new one works
4. **Limit Key Exposure**
* Never commit keys to version control
* Don't include keys in client-side JavaScript
* Use secrets management services (AWS Secrets Manager, HashiCorp Vault, etc.)
* Add API keys to `.gitignore`:
```
.env
.env.local
config/secrets.json
```
## Authentication Errors
**Error Response:**
```json theme={null}
{
"success": false,
"message": "Unauthorized"
}
```
**Common Causes:**
* Missing `Authorization` header
* Invalid API key format
* Expired or deleted API key
* API key not properly prefixed with "Bearer "
**Solutions:**
* Verify the `Authorization` header is present
* Check that your API key is correct and hasn't been deleted
* Ensure the format is: `Authorization: Bearer YOUR_API_KEY`
* Confirm there's a space after "Bearer"
**Error Response:**
```json theme={null}
{
"success": false,
"message": "Forbidden"
}
```
**Common Causes:**
* API key doesn't have access to the requested resource
* Using a Project API Key to access a different project's resources
* Missing `x-project-id` header when using an Account API Key
**Solutions:**
* Verify you're using the correct API key for the project
* If using an Account API Key, ensure the `x-project-id` header is present and correct
* Check that the resource (domain, template, etc.) exists in the target project
### Keep Your API Keys Secret
API keys provide full access to your AutoSend account and should be treated like passwords:
* Never share API keys in public forums, support tickets, or chat
* Don't include keys in screenshots or screen recordings
* Revoke keys immediately if exposed
**HTTPS Only**
Always use HTTPS when making API requests. AutoSend APIs reject non-HTTPS requests to protect your API keys from interception.
### Rate Limiting
API keys are subject to rate limits:
* **2 requests per second** per API key
* **50 requests per minute** per API key
Exceeding these limits returns a `429 Too Many Requests` error. See the [API
Reference](/api-reference) for more details.
# Create Automation
Source: https://docs.autosend.com/api-reference/automations/create-automation
POST /automations
Creates a new workflow automation. Pass `active: true` to activate the workflow as part of the create call.
## Quick note before you begin
* Creates a new workflow automation. By default the workflow is created in `draft` status. Pass `active: true` to validate and activate it as part of the create call.
* A workflow runs `steps` in order. Without `branch` steps, `steps` must strictly alternate `wait → email`, starting with `wait` and ending with `email`. Workflows containing `branch` steps use a more flexible graph (each step references the next via `nextStepId`).
* Every `email` step must be preceded by a `wait` step, including email steps inside a branch. Set the wait delay to `{ "value": 0, "unit": "mins" }` if you want the email to send immediately.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/automations \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"name": "Branch with guard - pro vs other",
"description": "Pro plan gets a 2-email sequence with a guard that routes to a merged email if plan changes mid-flight. Other plans get a single welcome email.",
"active": true,
"entryCriteria": {
"type": "contact_property_matches_with",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{ "field": "contactProperties.plan", "operator": "is_not_empty", "id": "filter_1", "fieldType": "string" }
]
}
},
"unsubscribeGroupId": "60d5ec49f1b2c72d9c8b4444",
"trackingOpen": true,
"trackingClick": true,
"exitCriteria": { "type": "workflow_complete" },
"tags": ["onboarding", "branch-guard"],
"steps": [
{
"type": "wait",
"delay": { "value": 2, "unit": "mins" },
"checkExitCriteria": true,
"stepId": "step_wait_intro",
"nextStepId": "step_email_intro"
},
{
"type": "email",
"email": {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"templateName": "Welcome - intro email",
"subject": "Welcome to Example!",
"previewText": "Glad to have you with us.",
"htmlTemplate": "Welcome {{firstName}}!
Thanks for joining.
"
},
"checkExitCriteria": true,
"stepId": "step_email_intro",
"nextStepId": "step_branch"
},
{
"stepId": "step_branch",
"type": "branch",
"branches": [
{
"branchId": "br_pro",
"label": "Pro plan",
"triggerType": "contact_property_matches_with",
"appliesTo": "all_branch_steps",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{ "field": "contactProperties.plan", "operator": "equals", "value": "pro", "id": "filter_2", "fieldType": "string" }
]
},
"nextStepId": "step_wait_pro",
"mergeStepId": "step_wait_merged"
},
{
"branchId": "br_other",
"label": "Catch-all",
"triggerType": "contact_property_matches_with",
"appliesTo": "branch_entry",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{ "field": "contactProperties.plan", "operator": "is_not_empty", "id": "filter_3", "fieldType": "string" }
]
},
"nextStepId": "step_wait_other"
}
]
},
{
"type": "wait",
"delay": { "value": 2, "unit": "mins" },
"checkExitCriteria": true,
"stepId": "step_wait_pro",
"parentBranchStepId": "step_branch",
"parentBranchId": "br_pro",
"nextStepId": "step_email_pro"
},
{
"stepId": "step_email_pro",
"type": "email",
"email": {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"templateName": "Welcome - pro email",
"subject": "Welcome to Pro, {{firstName}}",
"previewText": "Your Pro perks are ready.",
"htmlTemplate": "Welcome to Pro, {{firstName}}!
Your perks are ready.
"
},
"parentBranchStepId": "step_branch",
"parentBranchId": "br_pro",
"nextStepId": "step_wait_pro_followup"
},
{
"stepId": "step_wait_pro_followup",
"type": "wait",
"delay": { "value": 1, "unit": "minutes" },
"parentBranchStepId": "step_branch",
"parentBranchId": "br_pro",
"nextStepId": "step_email_pro_followup"
},
{
"stepId": "step_email_pro_followup",
"type": "email",
"email": {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"templateName": "Welcome - pro follow-up",
"subject": "A quick follow-up on your Pro plan",
"previewText": "Tips to get the most out of Pro.",
"htmlTemplate": "Hi {{firstName}}
Here are a few Pro tips.
"
},
"parentBranchStepId": "step_branch",
"parentBranchId": "br_pro"
},
{
"stepId": "step_wait_other",
"type": "wait",
"delay": { "value": 2, "unit": "mins" },
"checkExitCriteria": true,
"parentBranchStepId": "step_branch",
"parentBranchId": "br_other",
"nextStepId": "step_email_other"
},
{
"stepId": "step_email_other",
"type": "email",
"email": {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"templateName": "Welcome - standard email",
"subject": "Welcome to Example!",
"previewText": "Glad to have you with us.",
"htmlTemplate": "Welcome {{firstName}}!
Thanks for joining.
"
},
"parentBranchStepId": "step_branch",
"parentBranchId": "br_other"
},
{
"stepId": "step_wait_merged",
"type": "wait",
"delay": { "value": 2, "unit": "mins" },
"checkExitCriteria": true,
"nextStepId": "step_email_merged"
},
{
"stepId": "step_email_merged",
"type": "email",
"email": {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"templateName": "Welcome - merged email",
"subject": "Thanks for sticking with us, {{firstName}}",
"previewText": "A wrap-up note from the team.",
"htmlTemplate": "Thanks {{firstName}}!
Here is your wrap-up.
"
}
}
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations"
headers = {
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json"
}
email_defaults = {
"senderEmail": "team@example.com",
"senderName": "Example Team",
"projectId": "60d5ec49f1b2c72d9c8b1111"
}
payload = {
"name": "Branch with guard - pro vs other",
"description": "Pro plan gets a 2-email sequence with a guard. Other plans get a single welcome email.",
"active": True,
"entryCriteria": {
"type": "contact_property_matches_with",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{"field": "contactProperties.plan", "operator": "is_not_empty", "id": "filter_1", "fieldType": "string"}
]
}
},
"unsubscribeGroupId": "60d5ec49f1b2c72d9c8b4444",
"trackingOpen": True,
"trackingClick": True,
"exitCriteria": {"type": "workflow_complete"},
"tags": ["onboarding", "branch-guard"],
"steps": [
{"type": "wait", "delay": {"value": 2, "unit": "mins"}, "checkExitCriteria": True,
"stepId": "step_wait_intro", "nextStepId": "step_email_intro"},
{"type": "email",
"email": {**email_defaults, "templateName": "Welcome - intro email",
"subject": "Welcome to Example!", "previewText": "Glad to have you with us.",
"htmlTemplate": "Welcome {{firstName}}!
"},
"checkExitCriteria": True, "stepId": "step_email_intro", "nextStepId": "step_branch"},
{"stepId": "step_branch", "type": "branch", "branches": [
{"branchId": "br_pro", "label": "Pro plan",
"triggerType": "contact_property_matches_with", "appliesTo": "all_branch_steps",
"filterCriteria": {"logicalOperator": "AND", "groups": [
{"field": "contactProperties.plan", "operator": "equals", "value": "pro", "id": "filter_2", "fieldType": "string"}]},
"nextStepId": "step_wait_pro", "mergeStepId": "step_wait_merged"},
{"branchId": "br_other", "label": "Catch-all",
"triggerType": "contact_property_matches_with", "appliesTo": "branch_entry",
"filterCriteria": {"logicalOperator": "AND", "groups": [
{"field": "contactProperties.plan", "operator": "is_not_empty", "id": "filter_3", "fieldType": "string"}]},
"nextStepId": "step_wait_other"}
]},
{"type": "wait", "delay": {"value": 2, "unit": "mins"}, "checkExitCriteria": True,
"stepId": "step_wait_pro", "parentBranchStepId": "step_branch", "parentBranchId": "br_pro",
"nextStepId": "step_email_pro"},
{"stepId": "step_email_pro", "type": "email",
"email": {**email_defaults, "templateName": "Welcome - pro email",
"subject": "Welcome to Pro, {{firstName}}", "previewText": "Your Pro perks are ready.",
"htmlTemplate": "Welcome to Pro!
"},
"parentBranchStepId": "step_branch", "parentBranchId": "br_pro",
"nextStepId": "step_wait_pro_followup"},
{"stepId": "step_wait_pro_followup", "type": "wait",
"delay": {"value": 1, "unit": "minutes"},
"parentBranchStepId": "step_branch", "parentBranchId": "br_pro",
"nextStepId": "step_email_pro_followup"},
{"stepId": "step_email_pro_followup", "type": "email",
"email": {**email_defaults, "templateName": "Welcome - pro follow-up",
"subject": "A quick follow-up on your Pro plan", "previewText": "Tips to get the most out of Pro.",
"htmlTemplate": "Hi {{firstName}}
"},
"parentBranchStepId": "step_branch", "parentBranchId": "br_pro"},
{"stepId": "step_wait_other", "type": "wait", "delay": {"value": 2, "unit": "mins"},
"checkExitCriteria": True, "parentBranchStepId": "step_branch",
"parentBranchId": "br_other", "nextStepId": "step_email_other"},
{"stepId": "step_email_other", "type": "email",
"email": {**email_defaults, "templateName": "Welcome - standard email",
"subject": "Welcome to Example!", "previewText": "Glad to have you with us.",
"htmlTemplate": "Welcome {{firstName}}!
"},
"parentBranchStepId": "step_branch", "parentBranchId": "br_other"},
{"stepId": "step_wait_merged", "type": "wait", "delay": {"value": 2, "unit": "mins"},
"checkExitCriteria": True, "nextStepId": "step_email_merged"},
{"stepId": "step_email_merged", "type": "email",
"email": {**email_defaults, "templateName": "Welcome - merged email",
"subject": "Thanks for sticking with us, {{firstName}}",
"previewText": "A wrap-up note from the team.",
"htmlTemplate": "Thanks {{firstName}}!
"}}
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const emailDefaults = {
senderEmail: 'team@example.com',
senderName: 'Example Team',
projectId: '60d5ec49f1b2c72d9c8b1111'
};
const payload = {
name: 'Branch with guard - pro vs other',
description: 'Pro plan gets a 2-email sequence with a guard. Other plans get a single welcome email.',
active: true,
entryCriteria: {
type: 'contact_property_matches_with',
filterCriteria: {
logicalOperator: 'AND',
groups: [
{ field: 'contactProperties.plan', operator: 'is_not_empty', id: 'filter_1', fieldType: 'string' }
]
}
},
unsubscribeGroupId: '60d5ec49f1b2c72d9c8b4444',
trackingOpen: true,
trackingClick: true,
exitCriteria: { type: 'workflow_complete' },
tags: ['onboarding', 'branch-guard'],
steps: [
{ type: 'wait', delay: { value: 2, unit: 'mins' }, checkExitCriteria: true,
stepId: 'step_wait_intro', nextStepId: 'step_email_intro' },
{ type: 'email',
email: { ...emailDefaults, templateName: 'Welcome - intro email',
subject: 'Welcome to Example!', previewText: 'Glad to have you with us.',
htmlTemplate: 'Welcome {{firstName}}!
' },
checkExitCriteria: true, stepId: 'step_email_intro', nextStepId: 'step_branch' },
{ stepId: 'step_branch', type: 'branch', branches: [
{ branchId: 'br_pro', label: 'Pro plan',
triggerType: 'contact_property_matches_with', appliesTo: 'all_branch_steps',
filterCriteria: { logicalOperator: 'AND', groups: [
{ field: 'contactProperties.plan', operator: 'equals', value: 'pro', id: 'filter_2', fieldType: 'string' }
]},
nextStepId: 'step_wait_pro', mergeStepId: 'step_wait_merged' },
{ branchId: 'br_other', label: 'Catch-all',
triggerType: 'contact_property_matches_with', appliesTo: 'branch_entry',
filterCriteria: { logicalOperator: 'AND', groups: [
{ field: 'contactProperties.plan', operator: 'is_not_empty', id: 'filter_3', fieldType: 'string' }
]},
nextStepId: 'step_wait_other' }
]},
{ type: 'wait', delay: { value: 2, unit: 'mins' }, checkExitCriteria: true,
stepId: 'step_wait_pro', parentBranchStepId: 'step_branch', parentBranchId: 'br_pro',
nextStepId: 'step_email_pro' },
{ stepId: 'step_email_pro', type: 'email',
email: { ...emailDefaults, templateName: 'Welcome - pro email',
subject: 'Welcome to Pro, {{firstName}}', previewText: 'Your Pro perks are ready.',
htmlTemplate: 'Welcome to Pro!
' },
parentBranchStepId: 'step_branch', parentBranchId: 'br_pro',
nextStepId: 'step_wait_pro_followup' },
{ stepId: 'step_wait_pro_followup', type: 'wait', delay: { value: 1, unit: 'minutes' },
parentBranchStepId: 'step_branch', parentBranchId: 'br_pro',
nextStepId: 'step_email_pro_followup' },
{ stepId: 'step_email_pro_followup', type: 'email',
email: { ...emailDefaults, templateName: 'Welcome - pro follow-up',
subject: 'A quick follow-up on your Pro plan', previewText: 'Tips to get the most out of Pro.',
htmlTemplate: 'Hi {{firstName}}
' },
parentBranchStepId: 'step_branch', parentBranchId: 'br_pro' },
{ stepId: 'step_wait_other', type: 'wait', delay: { value: 2, unit: 'mins' },
checkExitCriteria: true, parentBranchStepId: 'step_branch',
parentBranchId: 'br_other', nextStepId: 'step_email_other' },
{ stepId: 'step_email_other', type: 'email',
email: { ...emailDefaults, templateName: 'Welcome - standard email',
subject: 'Welcome to Example!', previewText: 'Glad to have you with us.',
htmlTemplate: 'Welcome {{firstName}}!
' },
parentBranchStepId: 'step_branch', parentBranchId: 'br_other' },
{ stepId: 'step_wait_merged', type: 'wait', delay: { value: 2, unit: 'mins' },
checkExitCriteria: true, nextStepId: 'step_email_merged' },
{ stepId: 'step_email_merged', type: 'email',
email: { ...emailDefaults, templateName: 'Welcome - merged email',
subject: 'Thanks for sticking with us, {{firstName}}',
previewText: 'A wrap-up note from the team.',
htmlTemplate: 'Thanks {{firstName}}!
' }}
]
};
fetch('https://api.autosend.com/v1/automations', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
// Load the workflow payload (see the cURL or JavaScript example for the full structure).
body, _ := os.ReadFile("automation.json")
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/automations", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
req.Header.Set("Content-Type", "application/json")
resp, err := (&http.Client{}).Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(string(out))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CreateWorkflow {
public static void main(String[] args) throws Exception {
// Load the workflow payload (see the cURL or JavaScript example for the full structure).
byte[] body = Files.readAllBytes(Paths.get("automation.json"));
HttpURLConnection con = (HttpURLConnection) new URL("https://api.autosend.com/v1/automations").openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
try (OutputStream os = con.getOutputStream()) {
os.write(body);
}
System.out.println("Response Status: " + con.getResponseCode());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
request['Content-Type'] = 'application/json'
# Load the workflow payload (see the cURL or JavaScript example for the full structure).
request.body = File.read('automation.json')
puts http.request(request).body
```
```json 201 Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9abc",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"organizationId": "60d5ec49f1b2c72d9c8b2222",
"name": "Branch with guard - pro vs other",
"description": "Pro plan gets a 2-email sequence with a guard. Other plans get a single welcome email.",
"status": "active",
"entryCriteria": {
"type": "contact_property_matches_with",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{ "field": "contactProperties.plan", "operator": "is_not_empty", "id": "filter_1", "fieldType": "string" }
]
}
},
"exitCriteria": { "type": "workflow_complete" },
"steps": [
{ "stepId": "step_wait_intro", "type": "wait", "delay": { "value": 2, "unit": "mins" }, "nextStepId": "step_email_intro" },
{ "stepId": "step_email_intro", "type": "email", "nextStepId": "step_branch" },
{ "stepId": "step_branch", "type": "branch", "branches": [
{ "branchId": "br_pro", "nextStepId": "step_wait_pro", "mergeStepId": "step_wait_merged" },
{ "branchId": "br_other", "nextStepId": "step_wait_other" }
]},
{ "stepId": "step_wait_pro", "type": "wait", "parentBranchId": "br_pro", "nextStepId": "step_email_pro" },
{ "stepId": "step_email_pro", "type": "email", "parentBranchId": "br_pro", "nextStepId": "step_wait_pro_followup" },
{ "stepId": "step_wait_pro_followup", "type": "wait", "parentBranchId": "br_pro", "nextStepId": "step_email_pro_followup" },
{ "stepId": "step_email_pro_followup", "type": "email", "parentBranchId": "br_pro" },
{ "stepId": "step_wait_other", "type": "wait", "parentBranchId": "br_other", "nextStepId": "step_email_other" },
{ "stepId": "step_email_other", "type": "email", "parentBranchId": "br_other" },
{ "stepId": "step_wait_merged", "type": "wait", "nextStepId": "step_email_merged" },
{ "stepId": "step_email_merged", "type": "email" }
],
"tags": ["onboarding", "branch-guard"],
"unsubscribeGroupId": "60d5ec49f1b2c72d9c8b4444",
"trackingOpen": true,
"trackingClick": true,
"activeAt": "2026-05-08T10:00:00.000Z",
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Body
Display name for the workflow automation.
Maximum length: `255`
Example: `"Welcome Series"`
Optional description.
Maximum length: `1000`
Defines which contacts enter the workflow.
One of `contact_created`, `contact_added_to_list`, `contact_property_matches_with`, `contact_property_changes_from_to`, or `event_trigger`. Defaults to `contact_created`.
ID of the contact list or segment.
Required when `type` is `event_trigger`. Name of the event that triggers the workflow.
Optional filter tree applied on top of the base `type`. Use `event.name` and `event.properties.*` paths in `event_trigger` workflows; otherwise reference contact properties.
```json theme={null}
{
"logicalOperator": "AND",
"groups": [
{
"field": "contactProperties.plan",
"operator": "equals",
"value": "pro",
"id": "filter_1"
}
]
}
```
Defines when contacts leave the workflow before completion.
One of `workflow_complete` (default), `entry_criteria_no_longer_met`.
Ordered steps that contacts walk through. Each step must include a `type`. Linear workflows alternate `wait` and `email`; workflows containing a `branch` step form a graph where each step references the next via `nextStepId`. Every `email` step must be preceded by a `wait` step (use a delay of `0` to send immediately), including email steps inside a branch.
One of `wait`, `email`, or `branch`.
Required for `wait` steps. `delay.value` is a non-negative integer; `delay.unit` is one of `minutes`, `mins`, `hours`, or `days`.
Required for `email` steps. Provide either `templateId` (existing template) or `htmlTemplate` (inline HTML, `subject` is required when using `htmlTemplate`). Optional fields: `subject`, `previewText`, `senderEmail`, `senderName`, `senderReplyToEmail`, `senderReplyToName`.
Required for `branch` steps. Each branch defines a `triggerType` (`contact_property_matches_with` or `event_property`), a `filterCriteria` tree, an `appliesTo` scope (`branch_entry` or `all_branch_steps`), a `nextStepId` that points to the first step on that branch, and an optional `mergeStepId` where the branch rejoins the main flow.
ID of the next step in graph-style workflows (those containing a `branch`). Steps inside a branch must also set `parentBranchStepId` and `parentBranchId`.
Whether to re-evaluate exit criteria before this step. Defaults to `true`.
#### Branch step example
A `branch` step splits the workflow into multiple paths based on `filterCriteria`. Each branch points at its first step via `nextStepId`, and steps inside the branch reference back via `parentBranchStepId` and `parentBranchId`. Branches can rejoin the main flow at a shared `mergeStepId`.
```json Branch step theme={null}
{
"stepId": "step_branch",
"type": "branch",
"branches": [
{
"branchId": "br_pro",
"label": "Pro plan",
"triggerType": "contact_property_matches_with",
"appliesTo": "all_branch_steps",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{
"field": "contactProperties.plan",
"operator": "equals",
"value": "pro",
"id": "filter_2",
"fieldType": "string"
}
]
},
"nextStepId": "step_wait_pro",
"mergeStepId": "step_wait_merged"
},
{
"branchId": "br_other",
"label": "Catch-all",
"triggerType": "contact_property_matches_with",
"appliesTo": "branch_entry",
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{
"field": "contactProperties.plan",
"operator": "is_not_empty",
"id": "filter_3",
"fieldType": "string"
}
]
},
"nextStepId": "step_wait_other"
}
]
},
```
```json Wait step inside a branch theme={null}
{
"stepId": "step_wait_vip",
"type": "wait",
"delay": { "value": 2, "unit": "mins" },
"checkExitCriteria": true,
"parentBranchStepId": "step_branch",
"parentBranchId": "br_vip",
"nextStepId": "step_email_vip"
}
```
```json Email step inside a branch theme={null}
{
"stepId": "step_email_vip",
"type": "email",
"email": {
"senderEmail": "ankban@mail4.ankitbansal.co.in",
"senderName": "Example Team",
"projectId": "68b7f9dfb6924068b8ecda65",
"templateName": "VIP thank you",
"subject": "A personal thank-you, {{firstName}}",
"previewText": "VIP perks inside.",
"htmlTemplate": "
Thanks {{firstName}}!
Your VIP perks are on the way.
"
},
"parentBranchStepId": "step_branch",
"parentBranchId": "br_vip"
}
```
In an `email` step you can pass `templateId` to reference an existing template instead of inlining `htmlTemplate` and `subject`.
Optional tags for filtering and organization.
Suppression group applied to all email steps in the workflow.
Whether to insert open-tracking pixels into emails sent by this workflow.
Whether to rewrite links for click tracking in emails sent by this workflow.
When `true`, the workflow is validated and activated as part of the create call. When `false` or omitted, the workflow is created in `draft` status and must be activated separately.
#### Response
Workflow automation created successfully (201)
Example: `true`
The created workflow automation.
Unique workflow identifier.
Project the workflow belongs to.
Display name of the workflow.
Optional description.
Current status: `draft`, `active`, `paused`, or `archived`.
Conditions that determine which contacts enter the workflow.
Conditions that cause contacts to exit the workflow early.
Resolved steps with auto-generated `stepId` values.
Tags for filtering and organization.
Suppression group applied to all email steps in the workflow.
Whether open tracking is enabled.
Whether click tracking is enabled.
ISO 8601 timestamp when the workflow was last activated, or `null` if never activated.
ISO 8601 creation timestamp.
Organization the workflow belongs to.
ISO 8601 last-updated timestamp.
#### Error Responses
Returned when the request body fails validation. Common codes include `NAME_REQUIRED`, `INVALID_STEP_SEQUENCE`, `EMAIL_TEMPLATE_REQUIRED`, `BRANCH_BRANCHES_REQUIRED`, `EVENT_NAME_REQUIRED`.
```json theme={null}
{
"success": false,
"error": {
"message": "Steps must strictly alternate wait → email starting with wait and ending with email",
"code": "INVALID_STEP_SEQUENCE"
}
}
```
Returned when `active: true` is sent but the workflow is missing required fields for activation.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot activate workflow. Please check all required fields are filled",
"code": "CANNOT_ACTIVATE_AUTOMATION"
}
}
```
# Delete Automation
Source: https://docs.autosend.com/api-reference/automations/delete-automation
DELETE /automations/{id}
Soft-deletes a workflow automation. Active executions are stopped.
Soft-deletes a workflow automation. In-flight executions are stopped and no new contacts will enter the workflow.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteWorkflow {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Workflow automation deleted successfully"
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Workflow automation ID.
#### Response
Workflow automation deleted successfully
Example: `true`
Confirmation message.
Example: `"Workflow automation deleted successfully"`
#### Error Responses
Returned when no automation with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Automation not found",
"code": "AUTOMATION_NOT_FOUND",
"status": 404
}
}
```
# Get Automation
Source: https://docs.autosend.com/api-reference/automations/get-automation
GET /automations/{id}
Retrieves a single workflow automation by its ID.
Retrieves a single workflow automation by its ID, including its full `steps` graph and live analytics.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc', {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetWorkflow {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9abc",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"name": "Welcome Series",
"description": "Sends a 2-email welcome flow when a contact is created",
"status": "active",
"entryCriteria": { "type": "contact_created" },
"exitCriteria": { "type": "workflow_complete" },
"trackingOpen": true,
"trackingClick": true,
"settings": {
"maxExecutionDays": 60,
"allowReentry": false,
"timezone": "UTC",
"priority": 1
},
"steps": [
{ "stepId": "step_aa11", "type": "wait", "delay": { "value": 0, "unit": "minutes" } },
{ "stepId": "step_bb22", "type": "email", "email": { "templateId": "60d5ec49f1b2c72d9c8b1234" } }
],
"tags": ["onboarding"],
"analytics": {
"totalEntered": 1250,
"totalCompleted": 980,
"totalExited": 40,
"totalActive": 230
},
"activeAt": "2026-05-08T10:00:00.000Z",
"createdAt": "2026-05-01T08:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Workflow automation ID.
Example: `"60d5ec49f1b2c72d9c8b9abc"`
#### Response
Workflow automation retrieved successfully
Example: `true`
The workflow automation, with email-step `templateId` references enriched with template metadata where applicable.
Unique workflow identifier.
Project the workflow belongs to.
Display name of the workflow.
Optional description.
Current status: `draft`, `active`, `paused`, or `archived`.
Conditions that determine which contacts enter the workflow.
Conditions that cause contacts to exit the workflow early.
Whether open tracking is enabled.
Whether click tracking is enabled.
Automation configuration settings.
Maximum number of days a contact can remain active in the workflow.
Whether contacts can re-enter the workflow after completing it.
Timezone used for scheduling steps (e.g., `UTC`).
Execution priority when multiple automations are triggered simultaneously.
Full ordered step graph, with email steps enriched with template metadata.
Tags for filtering and organization.
Live engagement metrics (`totalEntered`, `totalCompleted`, `totalExited`, `totalActive`).
ISO 8601 timestamp when the workflow was last activated.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
#### Error Responses
```json theme={null}
{
"success": false,
"error": {
"message": "Workflow automation not found",
"code": "WORKFLOW_NOT_FOUND"
}
}
```
# List Automations
Source: https://docs.autosend.com/api-reference/automations/list-automations
GET /automations
Retrieves workflow automations for the project, with optional filtering by status and tags.
Returns a paginated list of workflow automations for the project, with optional filtering by `status` and comma-separated `tags`.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/automations?status=active&tags=onboarding&page=1&limit=20' \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
params = {"status": "active", "tags": "onboarding", "page": 1, "limit": 20}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({
status: 'active',
tags: 'onboarding',
page: '1',
limit: '20'
});
fetch(`https://api.autosend.com/v1/automations?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations?status=active&tags=onboarding&page=1&limit=20"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListWorkflows {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations?status=active&page=1&limit=20");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations')
uri.query = URI.encode_www_form(status: 'active', tags: 'onboarding', page: 1, limit: 20)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"workflowAutomations": [
{
"id": "60d5ec49f1b2c72d9c8b9abc",
"name": "Welcome Series",
"status": "active",
"entryCriteria": { "type": "contact_created" },
"exitCriteria": { "type": "workflow_complete" },
"steps": [],
"tags": ["onboarding"],
"trackingOpen": true,
"trackingClick": true,
"createdBy": {
"id": "60d5ec49f1b2c72d9c8b3333",
"firstName": "Jane",
"lastName": "Doe"
},
"analytics": {
"totalEntered": 1250,
"totalCompleted": 980,
"totalExited": 40,
"totalActive": 230,
"sent": 2100,
"delivered": 2050,
"opened": 1320,
"clicked": 410,
"bounced": 12
},
"activeAt": "2026-05-08T10:00:00.000Z",
"createdAt": "2026-05-01T08:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"pages": 1
}
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Query Parameters
Filter by workflow status. One of `draft`, `active`, `paused`, or `archived`.
Comma-separated list of tags to filter by.
Example: `"onboarding,trial"`
Page number (1-indexed). Default `1`.
Page size. Default `50`, maximum `100`.
#### Response
Workflow automations retrieved successfully
Example: `true`
Array of workflow automations. Each entry includes the resolved `createdBy` user and live `analytics` for active workflows.
Unique workflow identifier.
Display name of the workflow.
Current status: `draft`, `active`, `paused`, or `archived`.
Conditions that determine which contacts enter the workflow.
Conditions that cause contacts to exit the workflow early.
Ordered step definitions.
Tags for filtering and organization.
Whether open tracking is enabled.
Whether click tracking is enabled.
User who created the workflow (`id`, `firstName`, `lastName`).
Live send and engagement metrics (`totalEntered`, `totalCompleted`, `totalExited`, `totalActive`, `sent`, `delivered`, `opened`, `clicked`, `bounced`).
ISO 8601 timestamp when the workflow was last activated.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
Current page number.
Number of results per page.
Total number of workflows matching the filter.
Total number of pages.
# Pause Automation
Source: https://docs.autosend.com/api-reference/automations/pause-automation
POST /automations/{id}/pause
Pauses an active workflow automation. New contacts will not enter the workflow while paused.
Pauses an active workflow automation. New contacts stop entering the workflow; in-flight contacts remain in their current step until the workflow is resumed.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class PauseWorkflow {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/pause')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9abc",
"name": "Welcome Series",
"status": "paused",
"updatedAt": "2026-05-08T11:30:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Workflow automation ID.
#### Response
Workflow automation paused successfully
Example: `true`
The workflow automation, with `status` set to `paused`.
Unique workflow identifier.
Display name of the workflow.
Current status - will be `paused`.
ISO 8601 timestamp of when the workflow was paused.
#### Error Responses
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot pause a workflow that is not active",
"code": "CANNOT_PAUSE_INACTIVE_AUTOMATION"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Workflow automation not found",
"code": "WORKFLOW_NOT_FOUND"
}
}
```
# Resume Automation
Source: https://docs.autosend.com/api-reference/automations/resume-automation
POST /automations/{id}/resume
Resumes a paused workflow automation back to active state.
Resumes a paused workflow automation. New contacts can enter again, and in-flight contacts continue from their last completed step.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class ResumeWorkflow {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc/resume')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9abc",
"name": "Welcome Series",
"status": "active",
"activeAt": "2026-05-08T12:00:00.000Z",
"updatedAt": "2026-05-08T12:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Workflow automation ID.
#### Response
Workflow automation resumed successfully
Example: `true`
The workflow automation, with `status` set to `active`.
Unique workflow identifier.
Display name of the workflow.
Current status - will be `active`.
ISO 8601 timestamp of when the workflow was resumed.
ISO 8601 last-updated timestamp.
#### Error Responses
Returned when the workflow has missing required fields and cannot be activated.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot activate workflow. Please check all required fields are filled",
"code": "CANNOT_ACTIVATE_AUTOMATION"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Workflow automation not found",
"code": "WORKFLOW_NOT_FOUND"
}
}
```
# Update Automation
Source: https://docs.autosend.com/api-reference/automations/update-automation
PATCH /automations/{id}
Updates a workflow automation. Only draft and paused workflows can be edited. Pass `active: true` to activate after the update.
Updates a workflow automation. Only `draft` and `paused` workflows can be edited — active workflows must be paused first. Pass `active: true` to re-activate as part of the update.
When `steps` is provided, the supplied array fully replaces the existing step list. Steps not included will be removed and any in-flight executions on those steps will exit.
```bash cURL theme={null}
curl --request PATCH \
--url https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"name": "Welcome Series — v2",
"tags": ["onboarding", "v2"],
"trackingClick": true,
"active": true
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
headers = {
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json"
}
payload = {
"name": "Welcome Series — v2",
"tags": ["onboarding", "v2"],
"trackingClick": True,
"active": True
}
response = requests.patch(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Welcome Series — v2',
tags: ['onboarding', 'v2'],
trackingClick: true,
active: true
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'Welcome Series — v2',
'tags' => ['onboarding', 'v2'],
'trackingClick' => true,
'active' => true
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-project-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc"
payload := map[string]interface{}{
"name": "Welcome Series — v2",
"tags": []string{"onboarding", "v2"},
"trackingClick": true,
"active": true,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpdateWorkflow {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PATCH");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"name\": \"Welcome Series — v2\",\n" +
" \"active\": true\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/automations/60d5ec49f1b2c72d9c8b9abc')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
request['Content-Type'] = 'application/json'
request.body = {
name: 'Welcome Series — v2',
tags: ['onboarding', 'v2'],
trackingClick: true,
active: true
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9abc",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"name": "Welcome Series",
"description": "Sends a 2-email welcome flow when a contact is created",
"status": "active",
"entryCriteria": { "type": "contact_created" },
"exitCriteria": { "type": "workflow_complete" },
"steps": [
{ "stepId": "step_aa11", "type": "wait", "delay": { "value": 0, "unit": "minutes" } },
{ "stepId": "step_bb22", "type": "email", "email": { "templateId": "60d5ec49f1b2c72d9c8b1234" } }
],
"tags": ["onboarding"],
"trackingOpen": true,
"trackingClick": true,
"analytics": {
"totalEntered": 1250,
"totalCompleted": 980,
"totalExited": 40,
"totalActive": 230
},
"activeAt": "2026-05-08T10:00:00.000Z",
"createdAt": "2026-05-01T08:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Workflow automation ID.
### Body
All fields are optional. See [Create Automation](./create-workflow-automation) for the full shape of each field.
New workflow name. Maximum length `255`.
Maximum length `1000`.
Replacement entry criteria.
Replacement exit criteria.
Replacement step list. Fully replaces the existing steps. Every `email` step must be preceded by a `wait` step (use a delay of `0` to send immediately), including email steps inside a branch.
Replacement tag list.
Suppression group applied to all email steps.
When `true`, the workflow is validated and activated after the update. When `false`, the workflow is left in (or moved to) `draft` state.
#### Response
Workflow automation updated successfully
Example: `true`
The updated workflow automation.
Unique workflow identifier.
Project the workflow belongs to.
Display name of the workflow.
Optional description.
Current status: `draft`, `active`, `paused`, or `archived`.
Conditions that determine which contacts enter the workflow.
Conditions that cause contacts to exit the workflow early.
Full ordered step graph after the update.
Tags for filtering and organization.
Whether open tracking is enabled.
Whether click tracking is enabled.
Live engagement metrics (`totalEntered`, `totalCompleted`, `totalExited`, `totalActive`).
ISO 8601 timestamp when the workflow was last activated.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
#### Error Responses
Returned when the workflow is `active` or `archived` — pause it first.
```json theme={null}
{
"success": false,
"error": {
"message": "Only draft or paused workflows can be edited",
"code": "CANNOT_EDIT_AUTOMATION"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Workflow automation not found",
"code": "WORKFLOW_NOT_FOUND"
}
}
```
# API Best Practices
Source: https://docs.autosend.com/api-reference/best-practices
Security and development best practices for using the AutoSend email API.
### 1. Use Environment Variables
Never hardcode API keys in your source code:
```javascript theme={null}
const API_KEY = process.env.AUTOSEND_API_KEY;
```
### 2. Implement Error Handling
Always handle errors appropriately:
```javascript theme={null}
try {
const response = await sendEmail(data);
console.log("Email sent:", response.data.emailId);
} catch (error) {
if (error.response?.status === 429) {
// Rate limit - retry later
} else if (error.response?.status === 400) {
// Validation error - fix the data
} else {
// Other error - log and alert
}
}
```
### 3. Validate Email Addresses
Validate email addresses before sending to reduce bounces:
```javascript theme={null}
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
```
### 4. Use Verified Domains
Always send from verified domains to ensure deliverability. Learn how to verify domains →
### 5. Include Unsubscribe Links
For marketing emails, always include unsubscribe links to comply with regulations and maintain good sender reputation. You can use `{{unsubscribe}}` and `{{unsubscribe_preference}}` variable tags in your email template to dynamically insert unsubscribe link.
Many people confuse transactional and marketing emails, especially when it comes to unsubscribe links. To clear things up, we’ve covered [whether transactional emails need an unsubscribe link](https://autosend.com/blog/do-transactional-emails-need-an-unsubscribe-link) in detail.
# Abort Campaign
Source: https://docs.autosend.com/api-reference/campaigns/abort
POST /campaigns/{campaignId}/abort
Abort a running campaign to immediately stop all pending email sends using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort',
{
method: 'POST',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/abort')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"status": "aborted",
"sendMode": "immediate",
"trackingClick": true,
"trackingOpen": true,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:30:00.000Z"
}
}
```
**MCP Access Blocked** — This endpoint is not accessible via MCP (Model Context Protocol) integrations. It must be called directly using your API token from your own infrastructure.
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to abort.
### Response
Returns the updated campaign object with status set to `aborted`. Aborting a campaign immediately stops all pending sends. Emails already sent are not recalled.
Indicates whether the request was successful.
The aborted campaign object.
Unique identifier of the campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
Updated campaign status. Will be `aborted`.
Delivery mode of the campaign.
Whether click tracking is enabled.
Whether open tracking is enabled.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
# Create Campaign
Source: https://docs.autosend.com/api-reference/campaigns/create
POST /campaigns
Create a new email marketing campaign with sender, template, recipient lists, and scheduling options via the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/campaigns' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "Spring Sale Newsletter",
"subject": "Don'\''t miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"from": {
"email": "hello@example.com",
"name": "Example Team"
},
"replyTo": "support@example.com",
"templateId": "60d5ec49f1b2c72d9c8b1234",
"toLists": ["60d5ec49f1b2c72d9c8b0001"],
"sendMode": "immediate",
"publish": true,
"sendNow": true,
"trackingClick": true,
"trackingOpen": true
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"from": {
"email": "hello@example.com",
"name": "Example Team"
},
"replyTo": "support@example.com",
"templateId": "60d5ec49f1b2c72d9c8b1234",
"toLists": ["60d5ec49f1b2c72d9c8b0001"],
"sendMode": "immediate",
"publish": True,
"sendNow": True,
"trackingClick": True,
"trackingOpen": True
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.autosend.com/v1/campaigns', {
method: 'POST',
headers: {
Authorization: 'Bearer ',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Spring Sale Newsletter',
subject: "Don't miss our Spring Sale!",
previewText: 'Up to 50% off this weekend only',
from: {
email: 'hello@example.com',
name: 'Example Team',
},
replyTo: 'support@example.com',
templateId: '60d5ec49f1b2c72d9c8b1234',
toLists: ['60d5ec49f1b2c72d9c8b0001'],
sendMode: 'immediate',
publish: true,
sendNow: true,
trackingClick: true,
trackingOpen: true,
}),
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Spring Sale Newsletter',
'subject' => "Don't miss our Spring Sale!",
'previewText' => 'Up to 50% off this weekend only',
'from' => [
'email' => 'hello@example.com',
'name' => 'Example Team',
],
'replyTo' => 'support@example.com',
'templateId' => '60d5ec49f1b2c72d9c8b1234',
'toLists' => ['60d5ec49f1b2c72d9c8b0001'],
'sendMode' => 'immediate',
'publish' => true,
'sendNow' => true,
'trackingClick' => true,
'trackingOpen' => true,
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
'Content-Type: application/json',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload := map[string]interface{}{
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"from": map[string]string{
"email": "hello@example.com",
"name": "Example Team",
},
"replyTo": "support@example.com",
"templateId": "60d5ec49f1b2c72d9c8b1234",
"toLists": []string{"60d5ec49f1b2c72d9c8b0001"},
"sendMode": "immediate",
"publish": true,
"sendNow": true,
"trackingClick": true,
"trackingOpen": true,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/campaigns", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"from": { "email": "hello@example.com", "name": "Example Team" },
"replyTo": "support@example.com",
"templateId": "60d5ec49f1b2c72d9c8b1234",
"toLists": ["60d5ec49f1b2c72d9c8b0001"],
"sendMode": "immediate",
"publish": true,
"sendNow": true,
"trackingClick": true,
"trackingOpen": true
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
name: 'Spring Sale Newsletter',
subject: "Don't miss our Spring Sale!",
previewText: 'Up to 50% off this weekend only',
from: { email: 'hello@example.com', name: 'Example Team' },
replyTo: 'support@example.com',
templateId: '60d5ec49f1b2c72d9c8b1234',
toLists: ['60d5ec49f1b2c72d9c8b0001'],
sendMode: 'immediate',
publish: true,
sendNow: true,
trackingClick: true,
trackingOpen: true
}.to_json
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "69d348dd0351e0c32be90342",
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"status": "scheduled",
"sendMode": "immediate",
"from": {
"email": "hello@example.com",
"name": "Example Team"
},
"replyTo": "support@example.com",
"templateId": "A-280fa451a7ca5cc17513",
"toLists": [
{
"id": "696e1158fbfc515799175f02",
"name": "seg list",
"type": "list",
"contactCount": 3
}
],
"excludeLists": [],
"sendNow": true,
"sendToGlobalList": false,
"metrics": {
"sent": 0,
"delivered": 0,
"opened": 0,
"suppressed": 0,
"clicked": 0,
"bounced": 0,
"unsubscribed": 0,
"spamReported": 0,
"processedCount": 0,
"totalContacts": 0,
"failedCount": 0
},
"trackingClick": true,
"trackingOpen": true,
"source": "api",
"createdAt": "2026-04-06T05:47:09.523Z",
"updatedAt": "2026-04-06T05:52:38.949Z"
},
"message": "Campaign updated successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Display name of the campaign. Between 1 and 200 characters.
Email subject line. Between 1 and 998 characters.
Preview text shown in email clients before the message is opened. Maximum 200 characters.
Sender identity to use for this campaign.
Sender email address. Must be a valid email.
Sender display name. Maximum 200 characters.
id of a saved sender identity. Mutually usable with `from`.
Reply-to email address. Must be a valid email.
ID of the email template to use for this campaign. The template can be created via the [Template API](/api-reference/templates/create). Either `templateId` or `htmlTemplate` is required.
Raw HTML content to use as the campaign email body. Either `htmlTemplate` or `templateId` is required. If both are provided, `htmlTemplate` takes precedence and the template referenced by `templateId` will be updated with the provided HTML content.
Array of list or segment IDs to send the campaign to.
Array of list or segment IDs to exclude from sending.
ID of the unsubscribe group to associate with this campaign.
If `true`, the campaign will be scheduled to send after 120 seconds (2 minutes) from creation.
ISO 8601 date-time string at which to schedule the campaign for sending.
If `true`, the campaign is sent to all contacts in the global list.
If `true`, publishes and finalizes the campaign.
Enable or disable click tracking for this campaign.
Enable or disable open tracking for this campaign.
Delivery mode. One of: `immediate`, `scheduled`, `gradual`.
Growth strategy for gradual sending. One of: `fixed`, `1.25x`, `1.5x`, `1.75x`, `2x`. Required when `sendMode` is `gradual`.
Number of emails to send per day in gradual mode. Must be at least `1`.
Timezone string (e.g. `America/New_York`) for scheduled or gradual sending.
Mark this campaign as the default campaign. Applies to create only.
Identifies the campaign builder type used to create this campaign. Applies to create only.
### Response
Returns the newly created campaign object.
Indicates whether the request was successful.
A human-readable message describing the result.
The created campaign object.
Unique identifier of the new campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
Status of the campaign. When `sendNow` is `true`, this will be `scheduled`.
Delivery mode: `immediate`, `scheduled`, or `gradual`.
Sender identity used for this campaign.
Reply-to email address.
ID of the email template associated with this campaign.
Array of list objects the campaign is being sent to.
Array of excluded list or segment IDs.
Whether the campaign is set to send immediately.
Whether the campaign is sent to all contacts.
Campaign delivery and engagement metrics.
Whether click tracking is enabled.
Whether open tracking is enabled.
How the campaign was created (e.g. `api`).
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
# Delete Campaign
Source: https://docs.autosend.com/api-reference/campaigns/delete
DELETE /campaigns/{campaignId}
Permanently delete a campaign by ID using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"
headers = {"Authorization": "Bearer "}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
{
method: 'DELETE',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"))
.header("Authorization", "Bearer ")
.DELETE()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"message": "Campaign deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to delete.
### Response
Returns a confirmation that the campaign was deleted.
Indicates whether the request was successful.
Confirmation message.
Example: `"Campaign deleted successfully"`
#### Error Responses
Returned when the campaign is not in draft status.
```json theme={null}
{
"success": false,
"error": {
"message": "Only draft campaigns can be deleted",
"code": "ONLY_DRAFT_CAMPAIGNS_CAN_BE_DELETED",
"status": 400
}
}
```
Returned when no campaign with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Campaign not found",
"code": "CAMPAIGN_NOT_FOUND",
"status": 404
}
}
```
# Get Campaign
Source: https://docs.autosend.com/api-reference/campaigns/get
GET /campaigns/{campaignId}
Retrieve the details of a specific campaign by its ID using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"
headers = {"Authorization": "Bearer "}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
{
method: 'GET',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "69d348dd0351e0c32be90342",
"projectId": "68c7e05a0bd5787257e6ee3b",
"organizationId": "68c7e05a0bd5787257e6ee38",
"createdBy": 68c7e05a0bd5787257e7ec99,
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"replyTo": "support@example.com",
"toLists": [
{
"id": "696e1158fbfc515799175f02",
"name": "seg list",
"type": "list",
"contactCount": 3
}
],
"excludeLists": [],
"unsubscribeGroupId": null,
"sendNow": true,
"scheduledAt": "2026-04-06T05:56:02.850Z",
"templateId": "A-280fa451a7ca5cc17513",
"status": "sent",
"from": {
"email": "hello@yourdomain.com",
"name": "Example Team"
},
"metrics": {
"sent": 200,
"delivered": 200,
"opened": 160,
"suppressed": 0,
"clicked": 0,
"bounced": 0,
"unsubscribed": 0,
"spamReported": 0,
"processedCount": 200,
"totalContacts": 200,
"failedCount": 0
},
"sentAt": "2026-04-06T05:54:51.190Z",
"abortedAt": null,
"failureReason": null,
"createdAt": "2026-04-06T05:47:09.523Z",
"updatedAt": "2026-04-06T07:26:09.190Z",
"sendToGlobalList": false,
"template": {
"templateId": "A-280fa451a7ca5cc17513",
"projectId": "68c7e05a0bd5787257e6ee3b",
"templateName": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"emailTemplate": "Welcome, {{name}}!
Thanks for signing up.
",
"templateType": "marketing",
"builderType": "code",
"createdAt": "2026-04-06T05:47:09.516Z",
"updatedAt": "2026-04-06T05:47:09.516Z",
},
"trackingClick": true,
"trackingOpen": true,
"sendMode": "immediate",
"resumedAt": null,
"pausedReason": null,
"pausedAt": null,
"thresholdPreset": "balanced",
"source": "api",
},
"message": "Campaign retrieved successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to retrieve.
### Response
Returns the full campaign object for the given ID.
Indicates whether the request was successful.
The campaign object.
Unique identifier of the campaign.
ID of the project this campaign belongs to.
ID of the organization this campaign belongs to.
ID of the user who created the campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
ID of the sender used for this campaign.
Reply-to email address.
Lists or segments the campaign targets.
ID of the list or segment.
Name of the list or segment.
Type of the target. One of: `list`, `segment`.
Number of contacts in the list or segment.
Lists or segments excluded from this campaign.
ID of the unsubscribe group associated with this campaign.
Whether the campaign was sent immediately.
ISO 8601 date-time when the campaign is scheduled to send. Null if not scheduled.
ID of the email template used by this campaign.
Current status of the campaign. One of: `draft`, `scheduled`, `sending`, `sending_gradual`, `paused`, `sent`, `failed`, `aborted`.
Sender identity.
Sender email address.
Sender display name.
Campaign delivery and engagement metrics.
Number of emails sent.
Number of emails delivered.
Number of emails opened.
Number of emails suppressed.
Number of emails clicked.
Number of emails bounced.
Number of unsubscribes.
Number of spam reports.
Total number of emails processed.
Total number of contacts targeted.
Number of emails that failed to send.
Average sending rate per second.
Total time taken to send the campaign in seconds.
ISO 8601 timestamp when the campaign was sent.
ISO 8601 timestamp when the campaign was aborted. Null if not aborted.
Internal job ID for the campaign send process.
Reason for campaign failure. Null if the campaign did not fail.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
Whether the campaign was sent to the global contact list.
The email template associated with this campaign.
Unique identifier of the template.
ID of the project the template belongs to.
Display name of the template.
Email subject line from the template.
Preview text from the template.
HTML content of the email template.
Type of the template. One of: `marketing`, `transactional`.
How the template was built. One of: `code`, `editor`.
Editor JSON data if built with the visual editor.
Parent body properties for the template.
ISO 8601 timestamp when the template was created.
ISO 8601 timestamp when the template was last updated.
Whether the template has been flagged.
Source of the template. One of: `api`, `dashboard`.
Internal ID of the template.
Whether click tracking is enabled.
Whether open tracking is enabled.
How the campaign is delivered: `immediate`, `scheduled`, or `gradual`.
ISO 8601 timestamp when the campaign was resumed. Null if never paused.
Reason the campaign was paused. Null if not paused.
ISO 8601 timestamp when the campaign was paused. Null if not paused.
Sending threshold preset. One of: `balanced`, `aggressive`, `conservative`.
Source of the campaign. One of: `api`, `dashboard`.
Gradual send configuration. Null if not using gradual send mode.
A human-readable message describing the result.
# List Campaigns
Source: https://docs.autosend.com/api-reference/campaigns/list
GET /campaigns
List all campaigns in your account with optional filtering and pagination using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/campaigns?status=draft&page=1&limit=20' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns"
params = {
"status": "draft",
"page": 1,
"limit": 20
}
headers = {"Authorization": "Bearer "}
response = requests.get(url, params=params, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns?status=draft&page=1&limit=20',
{
method: 'GET',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns?status=draft&page=1&limit=20',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/campaigns?status=draft&page=1&limit=20", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns?status=draft&page=1&limit=20"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns')
uri.query = URI.encode_www_form(status: 'draft', page: 1, limit: 20)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"campaigns": [
{
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"status": "draft",
"sendMode": "immediate",
"trackingClick": true,
"trackingOpen": true,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:30:00.000Z"
},
{
"id": "60d5ec49f1b2c72d9c8b4568",
"name": "Welcome Series - Week 1",
"subject": "Welcome to Autosend!",
"previewText": "Get started in minutes",
"status": "sent",
"sendMode": "scheduled",
"trackingClick": true,
"trackingOpen": true,
"metrics": {
"sent": 200,
"delivered": 200,
"opened": 160,
"suppressed": 0,
"clicked": 0,
"bounced": 0,
"unsubscribed": 0,
"spamReported": 0,
"processedCount": 200,
"totalContacts": 200,
"failedCount": 0
},
"sentAt": "2026-04-06T05:54:51.190Z",
"createdAt": "2026-02-15T08:00:00.000Z",
"updatedAt": "2026-02-20T09:45:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"pages": 1
}
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Filter campaigns by status. One of: `draft`, `scheduled`, `sending`, `sending_gradual`, `paused`, `sent`, `failed`, `aborted`.
Filter campaigns by name. Supports partial matching.
Page number for pagination. Must be at least `1`. Defaults to `1`.
Number of results to return per page. Must be between `1` and `100`. Defaults to `20`.
ISO 8601 date-time string. Only campaigns created on or after this date are returned.
ISO 8601 date-time string. Only campaigns created on or before this date are returned.
Whether to include recipient and send counts in each campaign object. Pass `"true"` or `"false"`.
### Response
Returns a paginated list of campaign objects.
Indicates whether the request was successful.
Response payload.
Array of campaign objects.
Unique identifier of the campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
Current status of the campaign.
How the campaign is delivered: `immediate`, `scheduled`, or `gradual`.
Whether click tracking is enabled.
Whether open tracking is enabled.
Send and engagement metrics. Only present on campaigns that have been sent.
Total emails sent.
Total emails delivered.
Total emails opened.
Total link clicks.
Total bounced emails.
Total unsubscribes.
Total suppressed emails.
Total spam complaints.
Total emails processed.
Total contacts targeted.
Total failed sends.
ISO 8601 timestamp when the campaign was sent. Only present on sent campaigns.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
Pagination metadata.
Current page number.
Number of results per page.
Total number of matching campaigns.
Total number of pages.
# Pause Campaign
Source: https://docs.autosend.com/api-reference/campaigns/pause
POST /campaigns/{campaignId}/pause
Pause a running campaign to temporarily stop email sends using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause',
{
method: 'POST',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/pause')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"status": "paused",
"sendMode": "gradual",
"trackingClick": true,
"trackingOpen": true,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:30:00.000Z"
}
}
```
**MCP Access Blocked** — This endpoint is not accessible via MCP (Model Context Protocol) integrations. It must be called directly using your API token from your own infrastructure.
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to pause.
### Response
Returns the updated campaign object with status set to `paused`. A paused campaign can be resumed later. This is commonly used with gradual-send campaigns to temporarily stop delivery without losing progress.
Indicates whether the request was successful.
The paused campaign object.
Unique identifier of the campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
Updated campaign status. Will be `paused`.
Delivery mode of the campaign.
Whether click tracking is enabled.
Whether open tracking is enabled.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
# Resume Campaign
Source: https://docs.autosend.com/api-reference/campaigns/resume
POST /campaigns/{campaignId}/resume
Resume a paused campaign to continue sending emails using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume',
{
method: 'POST',
headers: {
Authorization: 'Bearer ',
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567/resume')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Spring Sale Newsletter",
"subject": "Don't miss our Spring Sale!",
"previewText": "Up to 50% off this weekend only",
"status": "sending_gradual",
"sendMode": "gradual",
"trackingClick": true,
"trackingOpen": true,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:30:00.000Z"
}
}
```
**MCP Access Blocked** — This endpoint is not accessible via MCP (Model Context Protocol) integrations. It must be called directly using your API token from your own infrastructure.
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to resume.
### Response
Returns the updated campaign object after resuming. Sending will continue from where it was paused. The status transitions back to `sending` or `sending_gradual` depending on the campaign's send mode.
Indicates whether the request was successful.
The resumed campaign object.
Unique identifier of the campaign.
Display name of the campaign.
Email subject line.
Preview text shown in email clients.
Updated campaign status. Will be `sending` or `sending_gradual` depending on the send mode.
Delivery mode of the campaign: `immediate`, `scheduled`, or `gradual`.
Whether click tracking is enabled.
Whether open tracking is enabled.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
# Update Campaign
Source: https://docs.autosend.com/api-reference/campaigns/update
PATCH /campaigns/{campaignId}
Update an existing campaign's settings, content, or scheduling using the AutoSend API.
```bash cURL theme={null}
curl --request PATCH \
--url 'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "Spring Sale Newsletter - Final",
"subject": "Last chance: Spring Sale ends tonight!",
"previewText": "Up to 50% off — ends at midnight",
"trackingClick": true,
"trackingOpen": true
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"name": "Spring Sale Newsletter - Final",
"subject": "Last chance: Spring Sale ends tonight!",
"previewText": "Up to 50% off — ends at midnight",
"trackingClick": True,
"trackingOpen": True
}
response = requests.patch(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
{
method: 'PATCH',
headers: {
Authorization: 'Bearer ',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Spring Sale Newsletter - Final',
subject: 'Last chance: Spring Sale ends tonight!',
previewText: 'Up to 50% off — ends at midnight',
trackingClick: true,
trackingOpen: true,
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
'https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Spring Sale Newsletter - Final',
'subject' => 'Last chance: Spring Sale ends tonight!',
'previewText' => 'Up to 50% off — ends at midnight',
'trackingClick' => true,
'trackingOpen' => true,
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ',
'Content-Type: application/json',
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload := map[string]interface{}{
"name": "Spring Sale Newsletter - Final",
"subject": "Last chance: Spring Sale ends tonight!",
"previewText": "Up to 50% off — ends at midnight",
"trackingClick": true,
"trackingOpen": true,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("PATCH", "https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"name": "Spring Sale Newsletter - Final",
"subject": "Last chance: Spring Sale ends tonight!",
"previewText": "Up to 50% off — ends at midnight",
"trackingClick": true,
"trackingOpen": true
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.autosend.com/v1/campaigns/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(uri)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
name: 'Spring Sale Newsletter - Final',
subject: 'Last chance: Spring Sale ends tonight!',
previewText: 'Up to 50% off — ends at midnight',
trackingClick: true,
trackingOpen: true
}.to_json
response = http.request(request)
puts JSON.parse(response.body)
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Spring Sale Newsletter - Final",
"subject": "Last chance: Spring Sale ends tonight!",
"previewText": "Up to 50% off — ends at midnight",
"status": "draft",
"sendMode": "immediate",
"trackingClick": true,
"trackingOpen": true,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-15T14:30:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The id of the campaign to update.
### Body
All body fields are optional. Only the fields provided will be updated.
Display name of the campaign. Between 1 and 200 characters.
Email subject line. Between 1 and 998 characters.
Preview text shown in email clients. Maximum 200 characters.
Sender identity to use for this campaign.
Sender email address. Must be a valid email.
Sender display name. Maximum 200 characters.
id of a saved sender identity.
Reply-to email address. Must be a valid email.
ID of the email template to use. The template can be created via the [Template API](/api-reference/templates/create). Either `templateId` or `htmlTemplate` is required.
Raw HTML content to use as the campaign email body. Either `htmlTemplate` or `templateId` is required. If both are provided, `htmlTemplate` takes precedence and the template referenced by `templateId` will be updated with the provided HTML content.
Array of list or segment IDs to send the campaign to.
Array of list or segment IDs to exclude from sending.
ID of the unsubscribe group to associate with this campaign.
If `true`, triggers sending immediately.
ISO 8601 date-time string at which to schedule the campaign for sending.
If `true`, send to all contacts in the global list.
If `true`, publishes and finalizes the campaign.
Enable or disable click tracking.
Enable or disable open tracking.
Delivery mode. One of: `immediate`, `scheduled`, `gradual`.
Growth strategy for gradual sending. One of: `fixed`, `1.25x`, `1.5x`, `1.75x`, `2x`.
Number of emails to send per day in gradual mode. Must be at least `1`.
Timezone string for scheduled or gradual sending.
### Response
Returns the updated campaign object.
Indicates whether the request was successful.
The updated campaign object.
Unique identifier of the campaign.
Updated display name.
Updated subject line.
Updated preview text.
Current status of the campaign.
Delivery mode: `immediate`, `scheduled`, or `gradual`.
Whether click tracking is enabled.
Whether open tracking is enabled.
ISO 8601 timestamp when the campaign was created.
ISO 8601 timestamp when the campaign was last updated.
# Bulk Add Contacts to List
Source: https://docs.autosend.com/api-reference/contact-lists/bulk-add
POST /contact-lists/contacts/bulk-add
Add multiple contacts to a contact list in a single request using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contact-lists/contacts/bulk-add \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"emails": [
"jane@example.com",
"john@example.com",
"alice@example.com"
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists/contacts/bulk-add"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"emails": [
"jane@example.com",
"john@example.com",
"alice@example.com"
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists/contacts/bulk-add', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contactListId: '60d5ec49f1b2c72d9c8b4567',
emails: [
'jane@example.com',
'john@example.com',
'alice@example.com'
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'60d5ec49f1b2c72d9c8b4567',
'emails' => [
'jane@example.com',
'john@example.com',
'alice@example.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists/contacts/bulk-add"
payload := map[string]interface{}{
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"emails": []string{
"jane@example.com",
"john@example.com",
"alice@example.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class BulkAddContacts {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists/contacts/bulk-add");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"contactListId\": \"60d5ec49f1b2c72d9c8b4567\",\n" +
" \"emails\": [\n" +
" \"jane@example.com\",\n" +
" \"john@example.com\",\n" +
" \"alice@example.com\"\n" +
" ]\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists/contacts/bulk-add')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
contactListId: '60d5ec49f1b2c72d9c8b4567',
emails: [
'jane@example.com',
'john@example.com',
'alice@example.com'
]
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"added": 2,
"created": 1,
"alreadyInList": 0,
"errors": [],
"validation": {
"valid": [
{ "email": "jane@example.com", "status": "valid" },
{ "email": "john@example.com", "status": "valid" },
{ "email": "alice@example.com", "status": "valid" }
],
"invalid": [],
"suppressed": []
},
"totalContactsInList": 343,
"contacts": [
{
"id": "6a27dd38aa5fe8d43df734d4",
"email": "jane@example.com",
"updatedAt": "2026-06-09T09:30:32.172Z",
"createdAt": "2026-06-09T09:30:32.172Z",
"projectId": "6a045963cbaa3dd6f0f7da61",
"listIds": ["60d5ec49f1b2c72d9c8b4567"],
"segmentIds": []
},
{
"id": "6a27dd38aa5fe8d43df734d5",
"email": "john@example.com",
"updatedAt": "2026-06-09T09:30:32.172Z",
"createdAt": "2026-06-09T09:30:32.172Z",
"projectId": "6a045963cbaa3dd6f0f7da61",
"listIds": ["60d5ec49f1b2c72d9c8b4567"],
"segmentIds": []
},
{
"id": "6a27dd38aa5fe8d43df734d6",
"email": "alice@example.com",
"updatedAt": "2026-06-09T09:30:32.172Z",
"createdAt": "2026-06-09T09:30:32.172Z",
"projectId": "6a045963cbaa3dd6f0f7da61",
"listIds": ["60d5ec49f1b2c72d9c8b4567"],
"segmentIds": []
}
]
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Add contacts to a list by email addresses or contact IDs. Provide either `emails` or `contactIds`, not both.
The ID of the contact list to add contacts to.
Example: `"60d5ec49f1b2c72d9c8b4567"`
Array of email addresses to add to the list. New contacts will be created for emails that don't already exist.
Maximum 500 emails per request. Provide either `emails` or `contactIds`, not both.
Example:
```json theme={null}
["jane@example.com", "john@example.com", "alice@example.com"]
```
Array of existing contact IDs to add to the list.
Maximum 500 contact IDs per request. Provide either `emails` or `contactIds`, not both.
Example:
```json theme={null}
["60d5ec49f1b2c72d9c8b1111", "60d5ec49f1b2c72d9c8b2222"]
```
### Response
Contacts added to list
Indicates if the request was successful
Example: `true`
Number of existing contacts added to the list
Example: `2`
Number of new contacts created and added to the list
Example: `1`
Number of contacts that were already in the list
Example: `0`
Array of errors for contacts that failed to be added
The email address that failed
Error description
Email validation results
Successfully validated emails
Email address
Validation status
Emails that failed validation
Email address
Validation status
Reason for invalidation
Emails that were suppressed
Total number of contacts now in the list
Example: `343`
Array of contact objects that were added or created
Unique identifier for the contact
Email address of the contact
ISO 8601 timestamp when the contact was created
ISO 8601 timestamp when the contact was last updated
ID of the project the contact belongs to
IDs of contact lists the contact belongs to
IDs of segments the contact belongs to
# Create Contact List
Source: https://docs.autosend.com/api-reference/contact-lists/create
POST /contact-lists
Create a new contact list to organize and segment your recipients using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contact-lists \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Newsletter Subscribers',
description: 'Users who signed up for the weekly newsletter',
type: 'list'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'Newsletter Subscribers',
'description' => 'Users who signed up for the weekly newsletter',
'type' => 'list'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists"
payload := map[string]interface{}{
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateContactList {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"name\": \"Newsletter Subscribers\",\n" +
" \"description\": \"Users who signed up for the weekly newsletter\",\n" +
" \"type\": \"list\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
name: 'Newsletter Subscribers',
description: 'Users who signed up for the weekly newsletter',
type: 'list'
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list",
"contactCount": 0,
"createdAt": "2026-03-17T10:30:00.000Z",
"updatedAt": "2026-03-17T10:30:00.000Z"
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Contact list or segment data
Name of the contact list (max 200 characters). Must be unique within the project.
Maximum length: `200`
Example: `"Newsletter Subscribers"`
Description of the contact list (max 500 characters).
Maximum length: `500`
Example: `"Users who signed up for the weekly newsletter"`
Type of contact list.
Allowed values: `list`, `segment`
Default: `"list"`
Example: `"list"`
Filter criteria for segments. Required when `type` is `segment`.
Logical operator for combining groups.
Allowed values: `AND`, `OR`
Example: `"AND"`
Array of filter conditions.
Contact field to filter on.
Example: `"email"`
Data type of the field.
Allowed values: `string`, `number`, `boolean`, `date`
Example: `"string"`
Filter operator. Available operators depend on the field type.
String operators: `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`
Number operators: `equals`, `not_equals`, `greater_than`, `less_than`, `between`
Boolean operators: `equals`, `not_equals`
Date operators: `equals`, `not_equals`, `before`, `after`, `between`, `is_empty`, `is_not_empty`
Example: `"contains"`
Value to compare against. Not required for unary operators like `is_empty` and `is_not_empty`.
Example: `"@gmail.com"`
ID of the parent contact list (for creating sub-segments).
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Contact list created successfully
Indicates if the request was successful
Example: `true`
The created contact list object
Unique contact list identifier
Example: `"60d5ec49f1b2c72d9c8b4567"`
Name of the contact list
Example: `"Newsletter Subscribers"`
Description of the contact list
Example: `"Users who signed up for the weekly newsletter"`
Type: `list` or `segment`
Example: `"list"`
Filter criteria (for segments only)
Number of contacts in the list
Example: `0`
Parent list ID (if sub-segment)
ISO 8601 timestamp of creation
Example: `"2026-03-17T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-03-17T10:30:00.000Z"`
# Delete Contact List
Source: https://docs.autosend.com/api-reference/contact-lists/delete
DELETE /contact-lists/{contactListId}
Permanently delete a contact list by ID using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteContactList {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact list deleted successfully",
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Newsletter Subscribers",
"description": "Weekly newsletter subscribers",
"type": "list",
"filterCriteria": null,
"contactCount": 150,
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-20T14:45:00.000Z",
"parentId": null
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the contact list to delete.
Example: `"60d5ec49f1b2c72d9c8b4567"`
You cannot delete a contact list that is currently used in active workflows. Remove the list from all workflows before deleting.
### Response
Contact list deleted successfully
Indicates if the request was successful
Example: `true`
Confirmation message
Example: `"Contact list deleted successfully"`
The deleted contact list object
Unique identifier of the contact list
Example: `"60d5ec49f1b2c72d9c8b4567"`
Name of the contact list
Example: `"Newsletter Subscribers"`
Description of the contact list
Example: `"Weekly newsletter subscribers"`
Type of the contact list - `list` or `segment`
Example: `"list"`
Filter criteria for segments. `null` for regular lists.
Number of contacts in the list at the time of deletion
Example: `150`
ISO 8601 timestamp of when the contact list was created
Example: `"2024-01-15T10:30:00.000Z"`
ISO 8601 timestamp of when the contact list was last updated
Example: `"2024-01-20T14:45:00.000Z"`
ID of the parent list if this is a sub-segment. `null` otherwise.
#### Error Responses
Returned when the contact list is used in active workflow automations.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot delete contact list as it is used in active resources.",
"code": "CANNOT_DELETE_CONTACT_LIST_IN_USE",
"status": 400
}
}
```
Returned when no contact list with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Contact list not found",
"code": "CONTACT_LIST_NOT_FOUND",
"status": 404
}
}
```
# Get Contact List
Source: https://docs.autosend.com/api-reference/contact-lists/get
GET /contact-lists/{contactListId}
Retrieve the details of a specific contact list by its ID using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetContactList {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list",
"contactCount": 340,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the contact list. Use `GLOBAL_CONTACT_LIST` to get the virtual "All Contacts" list.
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Contact list retrieved successfully
Indicates if the request was successful
Example: `true`
The contact list object
Unique contact list identifier
Example: `"60d5ec49f1b2c72d9c8b4567"`
Name of the contact list
Example: `"Newsletter Subscribers"`
Description of the contact list
Example: `"Users who signed up for the weekly newsletter"`
Type: `list` or `segment`
Example: `"list"`
Number of contacts in the list
Example: `340`
Filter criteria (for segments only)
Parent list ID (if sub-segment)
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
# Get Contacts in List
Source: https://docs.autosend.com/api-reference/contact-lists/get-contacts
POST /contact-lists/contacts/search
Search and retrieve contacts within a specific contact list using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contact-lists/contacts/search \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"page": 1,
"limit": 20,
"email": "jane"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists/contacts/search"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"page": 1,
"limit": 20,
"email": "jane"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists/contacts/search', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contactListId: '60d5ec49f1b2c72d9c8b4567',
page: 1,
limit: 20,
email: 'jane'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'60d5ec49f1b2c72d9c8b4567',
'page' => 1,
'limit' => 20,
'email' => 'jane'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists/contacts/search"
payload := map[string]interface{}{
"contactListId": "60d5ec49f1b2c72d9c8b4567",
"page": 1,
"limit": 20,
"email": "jane",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class GetContactsInList {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists/contacts/search");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"contactListId\": \"60d5ec49f1b2c72d9c8b4567\",\n" +
" \"page\": 1,\n" +
" \"limit\": 20,\n" +
" \"email\": \"jane\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists/contacts/search')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
contactListId: '60d5ec49f1b2c72d9c8b4567',
page: 1,
limit: 20,
email: 'jane'
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"contacts": [
{
"id": "60d5ec49f1b2c72d9c8b1111",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Smith",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"pages": 1
}
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Search parameters for retrieving contacts within a list
The ID of the contact list to search in.
Example: `"60d5ec49f1b2c72d9c8b4567"`
Page number for pagination (starts at 1).
Minimum: `1`
Default: `1`
Example: `1`
Number of results per page.
Range: `1` - `100`
Default: `20`
Example: `20`
Filter contacts by email address (partial match).
Example: `"jane"`
### Response
Contacts retrieved successfully
Indicates if the request was successful
Example: `true`
Array of contact objects
Unique contact identifier
Example: `"60d5ec49f1b2c72d9c8b1111"`
Contact email address
Example: `"jane@example.com"`
Contact first name
Example: `"Jane"`
Contact last name
Example: `"Smith"`
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
Pagination metadata
Current page number
Example: `1`
Results per page
Example: `20`
Total number of matching contacts
Example: `1`
Total number of pages
Example: `1`
# List Contact Lists
Source: https://docs.autosend.com/api-reference/contact-lists/list
GET /contact-lists
List all contact lists in your account with optional pagination using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/contact-lists?type=list' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists"
headers = {
"Authorization": "Bearer "
}
params = {
"type": "list"
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists?type=list', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists?type=list"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListContactLists {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists?type=list");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists?type=list')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"lists": [
{
"id": "GLOBAL_CONTACT_LIST",
"name": "All Contacts",
"description": "All contacts in the project",
"type": "list",
"contactCount": 1250
},
{
"id": "60d5ec49f1b2c72d9c8b4567",
"name": "Newsletter Subscribers",
"description": "Users who signed up for the weekly newsletter",
"type": "list",
"contactCount": 340,
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
},
{
"id": "60d5ec49f1b2c72d9c8b9876",
"name": "Active Users",
"description": "Segment of users who opened an email in the last 30 days",
"type": "segment",
"contactCount": 890,
"filterCriteria": {
"logicalOperator": "AND",
"groups": [
{
"field": "lastActivity",
"fieldType": "date",
"operator": "after",
"value": "2026-02-17T00:00:00.000Z"
}
]
},
"createdAt": "2026-02-01T08:15:00.000Z",
"updatedAt": "2026-03-01T11:20:00.000Z"
}
]
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Filter contact lists by type.
Allowed values: `list`, `segment`
Example: `"list"`
### Response
Contact lists retrieved successfully
Indicates if the request was successful
Example: `true`
Array of contact list objects. The virtual "All Contacts" list is always prepended as the first item with ID `GLOBAL_CONTACT_LIST`.
Unique contact list identifier. The global list uses the special ID `GLOBAL_CONTACT_LIST`.
Example: `"60d5ec49f1b2c72d9c8b4567"`
Name of the contact list
Example: `"Newsletter Subscribers"`
Description of the contact list
Example: `"Users who signed up for the weekly newsletter"`
Type: `list` or `segment`
Example: `"list"`
Number of contacts in the list
Example: `340`
Filter criteria (for segments only)
Parent list ID (if sub-segment)
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
# Remove Contacts from List
Source: https://docs.autosend.com/api-reference/contact-lists/remove-contacts
POST /contact-lists/{contactListId}/contacts/remove
Remove one or more contacts from a specific contact list using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"emails": [
"jane@example.com",
"john@example.com"
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"emails": [
"jane@example.com",
"john@example.com"
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emails: [
'jane@example.com',
'john@example.com'
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
[
'jane@example.com',
'john@example.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove"
payload := map[string]interface{}{
"emails": []string{
"jane@example.com",
"john@example.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class RemoveContacts {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"emails\": [\n" +
" \"jane@example.com\",\n" +
" \"john@example.com\"\n" +
" ]\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contact-lists/60d5ec49f1b2c72d9c8b4567/contacts/remove')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
emails: [
'jane@example.com',
'john@example.com'
]
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"removed": 2,
"notInList": 0,
"errors": []
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the contact list to remove contacts from.
You cannot remove contacts from the global `GLOBAL_CONTACT_LIST`. Use the Delete Contact API instead.
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Body
Remove contacts from a list by email addresses or contact IDs. Provide either `emails` or `contactIds`, not both.
Array of email addresses to remove from the list.
Example:
```json theme={null}
["jane@example.com", "john@example.com"]
```
Array of contact IDs to remove from the list.
Example:
```json theme={null}
["60d5ec49f1b2c72d9c8b1111", "60d5ec49f1b2c72d9c8b2222"]
```
### Response
Contacts removed from list
Indicates if the request was successful
Example: `true`
Number of contacts successfully removed from the list
Example: `2`
Number of contacts that were not in the list
Example: `0`
Array of errors for contacts that failed to be removed
# Create Contact Property
Source: https://docs.autosend.com/api-reference/contact-properties/create
POST /contact-properties
Create a new contact property to store additional contact attributes using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contact-properties \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "company",
"type": "string"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-properties"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"name": "company",
"type": "string"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/contact-properties", {
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "company",
type: "string",
}),
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/contact-properties",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"name" => "company",
"type" => "string",
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"io/ioutil"
)
func main() {
payload, _ := json.Marshal(map[string]string{
"name": "company",
"type": "string",
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/contact-properties", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"name": "company",
"type": "string"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/contact-properties"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI.parse("https://api.autosend.com/v1/contact-properties")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer "
request["Content-Type"] = "application/json"
request.body = JSON.dump({
name: "company",
type: "string"
})
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact property created successfully",
"data": {
"id": "6960953e729f65f154369408",
"name": "company",
"type": "string",
"fieldName": "company",
"fieldType": "string",
"createdAt": "2026-01-09T05:42:22.171Z",
"updatedAt": "2026-01-09T05:42:22.171Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body Parameters
The programmatic name for the contact property. Must be unique within the project. Cannot be one of the reserved names: `firstName`, `first_name`, `lastName`, `last_name`, `email`, `mobile`, `unsubscribed`, `unsubscribe`, `unsubscribe_groups`, `unsubscribe_preferences`, `userId`, `externalId`, `createdAt`, `updatedAt`, `contactLists`, `address`, `address_line_1`, `address_line_2`, `city`, `state`, `zip`, `country`.
The legacy `fieldName` parameter is still accepted as an alias for `name`.
The data type of the contact property. Must be one of: `string`, `number`, `boolean`, `date`.
The legacy `fieldType` parameter is still accepted as an alias for `type`.
A human-readable description of what the property represents.
A fallback value used when a contact does not have an explicit value for this property.
### Response
Contact property created successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Contact property created successfully"`
The created contact property.
Unique identifier of the contact property.
The programmatic name of the contact property.
The data type: `string`, `number`, `boolean`, or `date`.
Legacy alias of `name`, returned for backward compatibility.
Legacy alias of `type`, returned for backward compatibility.
ISO 8601 timestamp when the property was created.
ISO 8601 timestamp when the property was last updated.
# Delete Contact Property by Name
Source: https://docs.autosend.com/api-reference/contact-properties/delete
DELETE /contact-properties/name/{propertyName}
Delete a contact property by name to remove it from all contacts using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/contact-properties/name/planTier \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-properties/name/planTier"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/contact-properties/name/planTier",
{
method: "DELETE",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/contact-properties/name/planTier",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.autosend.com/v1/contact-properties/name/planTier", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/contact-properties/name/planTier"))
.header("Authorization", "Bearer ")
.DELETE()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/contact-properties/name/planTier")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact property deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The `name` of the contact property to delete.
### Response
Contact property deleted successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Contact property deleted successfully"`
#### Error Responses
Returned when no contact property with the given name exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Contact property not found",
"code": "CONTACT_PROPERTY_NOT_FOUND",
"status": 404
}
}
```
# Get Contact Property by Name
Source: https://docs.autosend.com/api-reference/contact-properties/get-by-name
GET /contact-properties/name/{propertyName}
Retrieve a specific contact property's details by its name using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/contact-properties/name/planTier \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-properties/name/planTier"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/contact-properties/name/planTier",
{
method: "GET",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/contact-properties/name/planTier",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/contact-properties/name/planTier", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/contact-properties/name/planTier"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/contact-properties/name/planTier")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact property retrieved successfully",
"data": {
"id": "69c258bae3e715c0521153fb",
"name": "isVerified",
"type": "boolean",
"fieldName": "isVerified",
"fieldType": "boolean",
"createdAt": "2026-03-24T09:26:18.826Z",
"updatedAt": "2026-03-24T09:26:18.826Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The `name` of the contact property to retrieve.
### Response
Contact property retrieved successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Contact property retrieved successfully"`
The contact property object.
Unique identifier of the contact property.
The programmatic name of the contact property.
The data type: `string`, `number`, `boolean`, or `date`.
Legacy alias of `name`, returned for backward compatibility.
Legacy alias of `type`, returned for backward compatibility.
ISO 8601 timestamp when the property was created.
ISO 8601 timestamp when the property was last updated.
# List Contact Properties
Source: https://docs.autosend.com/api-reference/contact-properties/list
GET /contact-properties
List all contact properties defined in your account using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/contact-properties \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contact-properties"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/contact-properties", {
method: "GET",
headers: {
Authorization: "Bearer ",
},
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/contact-properties",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/contact-properties", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/contact-properties"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/contact-properties")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact properties retrieved successfully",
"data": {
"contactProperties": [
{
"id": "69c258bae3e715c0521153fb",
"name": "isVerified",
"type": "boolean",
"fieldName": "isVerified",
"fieldType": "boolean",
"createdAt": "2026-03-24T09:26:18.826Z",
"updatedAt": "2026-03-24T09:26:18.826Z"
},
{
"id": "6960953e729f65f154369408",
"name": "company",
"type": "string",
"fieldName": "company",
"fieldType": "string",
"createdAt": "2026-01-09T05:42:22.171Z",
"updatedAt": "2026-01-09T05:42:22.171Z"
}
]
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Whether to include built-in reserved fields (email, firstName, lastName, createdAt, userId) in the response. Defaults to `true`.
### Response
Contact properties retrieved successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Contact properties retrieved successfully"`
Array of contact property objects.
Unique identifier of the contact property.
The programmatic name of the contact property.
The data type: `string`, `number`, `boolean`, or `date`.
Legacy alias of `name`, returned for backward compatibility.
Legacy alias of `type`, returned for backward compatibility.
ISO 8601 timestamp when the property was created.
ISO 8601 timestamp when the property was last updated.
# Bulk Update Contacts
Source: https://docs.autosend.com/api-reference/contacts/bulk-update-contacts
POST /contacts/bulk-update
Updates or creates multiple contacts in a single API request. Each contact is either created (if it doesn't exist) or updated (if it does) based on the email address. Maximum limit: 100 contacts per request.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contacts/bulk-update \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"contacts": [
{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}
],
"runWorkflow": false
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/bulk-update"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"contacts": [
{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}
],
"runWorkflow": False
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/bulk-update', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
contacts: [
{
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
}
],
runWorkflow: false
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
[
[
'email' => 'john.doe@example.com',
'firstName' => 'John',
'lastName' => 'Doe',
'userId' => 'user_12345',
'contactProperties' => [
'company' => 'Acme Corp',
'role' => 'Developer',
'plan' => 'premium'
]
]
],
'runWorkflow' => false
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/bulk-update"
payload := map[string]interface{}{
"contacts": []map[string]interface{}{
{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": map[string]string{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium",
},
},
},
"runWorkflow": false,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class BulkUpdateContacts {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/bulk-update");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"contacts\": [\n" +
" {\n" +
" \"email\": \"john.doe@example.com\",\n" +
" \"firstName\": \"John\",\n" +
" \"lastName\": \"Doe\",\n" +
" \"userId\": \"user_12345\",\n" +
" \"contactProperties\": {\n" +
" \"company\": \"Acme Corp\",\n" +
" \"role\": \"Developer\",\n" +
" \"plan\": \"premium\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"runWorkflow\": false\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/bulk-update')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
contacts: [
{
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
}
],
runWorkflow: false
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"successCount": 1,
"failedCount": 0,
"totalCount": 1
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Array of contacts to update or create
Array of contact objects (minimum 1, maximum 100)
Maximum 100 contacts per request.
Valid email address (automatically normalized to lowercase)
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
Your application's user identifier
Example: `"user_12345"`
Custom contact attributes
Example:
```jsx theme={null}
{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
```
Whether to trigger workflows for updated contacts
Example: `false`
### Response
Bulk update completed
Example: `true`
Number of successfully updated/created contacts
Example: `1`
Number of failed contacts
Example: `0`
Total number of contacts processed
Example: `1`
# Create Contact
Source: https://docs.autosend.com/api-reference/contacts/create-contacts
POST /contacts
Creates a new contact in your AutoSend project.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contacts \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'john.doe@example.com',
'firstName' => 'John',
'lastName' => 'Doe',
'userId' => 'user_12345',
'contactProperties' => [
'company' => 'Acme Corp',
'role' => 'Developer',
'plan' => 'premium'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts"
payload := map[string]interface{}{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": map[string]string{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateContact {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"email\": \"john.doe@example.com\",\n" +
" \"firstName\": \"John\",\n" +
" \"lastName\": \"Doe\",\n" +
" \"userId\": \"user_12345\",\n" +
" \"contactProperties\": {\n" +
" \"company\": \"Acme Corp\",\n" +
" \"role\": \"Developer\",\n" +
" \"plan\": \"premium\"\n" +
" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
},
"listIds": ["507f1f77bcf86cd799439011"],
"updatedAt": "2024-01-15T10:30:00.000Z",
"createdAt": "2024-01-15T10:30:00.000Z",
"projectId": "229f1f77bcf86cd9273048038"
}
}
```
Use this API when you know the contact is new. Use Upsert Contact when the contact may already exist. It will create or update without throwing an error.
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Contact information to create
Valid email address (automatically normalized to lowercase)
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
An optional reference field to store your application's user ID. Use this to map your internal users to AutoSend contacts.
Example: `"user_12345"`
IDs of the contact lists to add this contact to. You can find list IDs in your AutoSend Dashboard.
Do not pass segment IDs here. Segments are computed automatically based on contact field values. Adding a contact to a list will also trigger any live automations associated with that list.
Example: `["507f1f77bcf86cd799439011"]`
Key-value pairs for custom contact attributes. There are four supported value types: `string`, `number`, `boolean`, and `date`. Learn more about contact properties.
Example:
```jsx theme={null}
contactProperties: {
"company": "Acme Corp",
"isPremium": true,
"loginCount": 42,
"trialEndsAt": "2024-03-01"
}
```
### Response
Contact created successfully
Indicates if the request was successful
Example: `true`
Contact ID
Example: `"507f1f77bcf86cd799439011"`
Contact email address
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
Your application's user identifier
Example: `"user_12345"`
Custom contact attributes
Example:
```jsx theme={null}
{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
```
Contact creation timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Contact last update timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Project ID that the contact belongs to
Example: `"229f1f77bcf86cd9273048038"`
Contact List IDs
Example: `["507f1f77bcf86cd799439011"]`
# Delete Contact by ID
Source: https://docs.autosend.com/api-reference/contacts/delete-contact-by-ids
DELETE /contacts/{id}
Deletes a contact by its id.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/contacts/{id} \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/{id}"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/{id}', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/{id}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteContactById {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/{id}");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/{id}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri.path)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact deleted successfully"
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
Single contact id
Example: `"507f1f77bcf86cd799439011"`
### Response
Contact deleted successfully
Example: `true`
Confirmation message.
Example: `"Contact deleted successfully"`
#### Error Responses
Returned when a database error occurs during deletion.
```json theme={null}
{
"success": false,
"error": {
"message": "Contact deletion failed. Please try again.",
"code": "CONTACT_DELETION_FAILED",
"status": 500
}
}
```
# Delete Contact by User ID
Source: https://docs.autosend.com/api-reference/contacts/delete-contact-by-user-id
DELETE /contacts/email/userId/{userId}
Permanently deletes a contact identified by the userId field (your application's user identifier).
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/contacts/email/userId/{id} \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/email/userId/{id}"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/email/userId/{id}', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/email/userId/{id}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteContactByUserId {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/email/userId/{id}");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/email/userId/{id}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri.path)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Contact deleted successfully"
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
Your application's user identifier
Example: `"user_12345"`
### Response
Contact deleted successfully
Example: `true`
Confirmation message.
Example: `"Contact deleted successfully"`
#### Error Responses
Returned when no contact with the given user ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Contact not found",
"code": "CONTACT_NOT_FOUND",
"status": 404
}
}
```
Returned when a database error occurs during deletion.
```json theme={null}
{
"success": false,
"error": {
"message": "Contact deletion failed. Please try again.",
"code": "CONTACT_DELETION_FAILED",
"status": 500
}
}
```
# Get Contact by ID
Source: https://docs.autosend.com/api-reference/contacts/get-contact-by-id
GET /contacts/{id}
Retrieves a single contact by its unique identifier.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/contacts/{id} \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/{id}"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/{id}', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/{id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class GetContactById {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/{id}");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/{id}')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
},
"listIds":["692822107f092ea9019d3af8"],
"segmentIds":["6a0c0fb56ae88a19f5a010a9"],
"updatedAt": "2024-01-15T10:30:00.000Z",
"createdAt": "2024-01-15T10:30:00.000Z",
"projectId": "229f1f77bcf86cd9273048038"
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
Unique contact ID
Example: `"507f1f77bcf86cd799439011"`
### Response
Contact retrieved successfully
Indicates if the request was successful
Example: `true`
Contact ID
Example: `"507f1f77bcf86cd799439011"`
Contact email address
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
Your application's user identifier
Example: `"user_12345"`
Custom contact attributes
Example:
```jsx theme={null}
{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
```
Contact creation timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Contact last update timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Project ID that the contact belongs to
Example: `"229f1f77bcf86cd9273048038"`
Contact List IDs
Example: `["692822107f092ea9019d3af8"]`
Segment IDs the contact belongs to
Example: `["6a0c0fb56ae88a19f5a010a9"]`
# Get Unsubscribe Groups
Source: https://docs.autosend.com/api-reference/contacts/get-unsubscribe-groups
GET /contacts/{id}/unsubscribe-groups
Returns an array of unsubscribe group
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class GetUnsubscribeGroups {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/{id}/unsubscribe-groups')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.path)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"groups": [
{
"groupId": "20XWO",
"name": "Our blog"
}
]
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
Unique id of the contact
Example: `"507f1f77bcf86cd799439011"`
### Response
Unsubscribe groups retrieved successfully
Indicates if the request was successful
Example: `true`
Array of unsubscribe groups with groupId and name
Example: `"20XWO"`
Example: `"Our blog"`
# Remove Contacts
Source: https://docs.autosend.com/api-reference/contacts/remove
POST /contacts/remove
Removes one or more contacts by their email addresses. Returns the IDs of the contacts that were successfully removed.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contacts/remove \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"emails": [
"email1@gmail.com"
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/remove"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"emails": [
"email1@gmail.com"
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/remove', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emails: [
'email1@gmail.com'
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
[
'email1@gmail.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/remove"
payload := map[string]interface{}{
"emails": []string{
"email1@gmail.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class RemoveContacts {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/remove");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"emails\": [\n" +
" \"email1@gmail.com\"\n" +
" ]\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/remove')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
emails: [
'email1@gmail.com'
]
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Array of email addresses to remove
Array of email addresses to remove (minimum 1, automatically normalized)
Minimum length: `1`
Example: `["email1@gmail.com"]`
### Response
Contacts removed successfully
Example: `true`
# Search Contacts by Emails
Source: https://docs.autosend.com/api-reference/contacts/search-contacts-by-emails
POST /contacts/search/emails
Searches for multiple contacts by their email addresses in a single request. Returns all matching contacts found in your project.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contacts/search/emails \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"emails": [
"john.doe@example.com",
"jane.smith@example.com"
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/search/emails"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"emails": [
"john.doe@example.com",
"jane.smith@example.com"
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/search/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
emails: [
'john.doe@example.com',
'jane.smith@example.com'
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
[
'john.doe@example.com',
'jane.smith@example.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/search/emails"
payload := map[string]interface{}{
"emails": []string{
"john.doe@example.com",
"jane.smith@example.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SearchContactsByEmails {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/search/emails");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"emails\": [\n" +
" \"john.doe@example.com\",\n" +
" \"jane.smith@example.com\"\n" +
" ]\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/search/emails')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
emails: [
'john.doe@example.com',
'jane.smith@example.com'
]
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"contacts": [
{
"id": "507f1f77bcf86cd799439011",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
},
"updatedAt": "2024-01-15T10:30:00.000Z",
"createdAt": "2024-01-15T10:30:00.000Z",
"projectId": "229f1f77bcf86cd9273048038",
"listIds": ["69929ee99ba41f12c56b710e"],
"segmentIds": ["6a0c0fb56ae88a19f5a010a9"]
}
]
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Array of email addresses to search
Array of email addresses (minimum 1, automatically normalized)
Example: `["john.doe@example.com", "jane.smith@example.com"]`
### Response
Contacts retrieved successfully
Indicates if the request was successful
Example: `true`
Array of contact objects
Contact ID
Example: `"507f1f77bcf86cd799439011"`
Contact email address
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
Your application's user identifier
Example: `"user_12345"`
Custom contact attributes
Example:
```jsx theme={null}
{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
```
Contact creation timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Contact last update timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Project ID that the contact belongs to
Example: `"229f1f77bcf86cd9273048038"`
Contact List IDs
Example: `["69929ee99ba41f12c56b710e"]`
Segment IDs the contact belongs to
Example: `["6a0c0fb56ae88a19f5a010a9"]`
# Upsert Contact
Source: https://docs.autosend.com/api-reference/contacts/upsert-contact
POST /contacts/email
Creates a new contact or updates an existing contact if the email already exists.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/contacts/email \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"mobile": "+14155552671",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/contacts/email"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"mobile": "+14155552671",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/contacts/email', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
mobile: '+14155552671',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'john.doe@example.com',
'firstName' => 'John',
'lastName' => 'Doe',
'userId' => 'user_12345',
'mobile' => '+14155552671',
'contactProperties' => [
'company' => 'Acme Corp',
'role' => 'Developer',
'plan' => 'premium'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/contacts/email"
payload := map[string]interface{}{
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"mobile": "+14155552671",
"contactProperties": map[string]string{
"company": "Acme Corp",
"role": "Developer",
"plan": "premium",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpsertContact {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/contacts/email");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"email\": \"john.doe@example.com\",\n" +
" \"firstName\": \"John\",\n" +
" \"lastName\": \"Doe\",\n" +
" \"userId\": \"user_12345\",\n" +
" \"mobile\": \"+14155552671\",\n" +
" \"contactProperties\": {\n" +
" \"company\": \"Acme Corp\",\n" +
" \"role\": \"Developer\",\n" +
" \"plan\": \"premium\"\n" +
" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/contacts/email')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
userId: 'user_12345',
mobile: '+14155552671',
contactProperties: {
company: 'Acme Corp',
role: 'Developer',
plan: 'premium'
}
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"userId": "user_12345",
"mobile": "+14155552671",
"contactProperties": {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
},
"listIds": ["507f1f77bcf86cd799439011"],
"segmentIds": [],
"updatedAt": "2024-01-15T10:30:00.000Z",
"createdAt": "2024-01-15T10:30:00.000Z",
"projectId": "229f1f77bcf86cd9273048038"
}
}
```
This is the recommended endpoint for most contact synchronization scenarios.
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Contact information to create or update
For the optional fields `firstName`, `lastName`, `userId`, and `mobile`, sending an empty string (`""`) clears the stored value on an existing contact. Sending `null` or omitting the field leaves the current value unchanged.
Valid email address (automatically normalized to lowercase)
Example: `"john.doe@example.com"`
Contact's first name
Send an empty string (`""`) to clear this field on an existing contact. Sending `null` or omitting it leaves the current value unchanged.
Example: `"John"`
Contact's last name
Send an empty string (`""`) to clear this field on an existing contact. Sending `null` or omitting it leaves the current value unchanged.
Example: `"Doe"`
An optional reference field to store your application's user ID. Use this to map your internal users to AutoSend contacts.
Send an empty string (`""`) to clear this field on an existing contact. Sending `null` or omitting it leaves the current value unchanged.
Example: `"user_12345"`
Contact's mobile number in E.164 format.
Send an empty string (`""`) to clear this field on an existing contact. Sending `null` or omitting it leaves the current value unchanged.
Example: `"+14155552671"`
IDs of the contact lists to add this contact to. You can find list IDs in your AutoSend Dashboard.
Do not pass segment IDs here. Segments are computed automatically based on contact field values. Adding a contact to a list will also trigger any live automations associated with that list.
Example: `["507f1f77bcf86cd799439011"]`
Key-value pairs for custom contact attributes. There are four supported value types: `string`, `number`, `boolean`, and `date`. Learn more about contact properties.
Example:
```jsx theme={null}
contactProperties: {
"company": "Acme Corp",
"isPremium": true,
"loginCount": 42,
"trialEndsAt": "2024-03-01"
}
```
### Response
Contact created or updated successfully
Indicates if the request was successful
Example: `true`
Contact ID
Example: `"507f1f77bcf86cd799439011"`
Contact email address
Example: `"john.doe@example.com"`
Contact's first name
Example: `"John"`
Contact's last name
Example: `"Doe"`
Your application's user identifier
Example: `"user_12345"`
Contact's mobile number in E.164 format
Example: `"+14155552671"`
Custom contact attributes
Example:
```jsx theme={null}
contactProperties: {
"company": "Acme Corp",
"role": "Developer",
"plan": "premium"
}
```
Contact creation timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Contact last update timestamp
Example: `"2024-01-15T10:30:00.000Z"`
Project ID that the contact belongs to
Example: `"229f1f77bcf86cd9273048038"`
Contact List IDs
Example: `["507f1f77bcf86cd799439011"]`
Segment IDs the contact belongs to (computed automatically based on contact field values)
Example: `[]`
# Create Custom Field
Source: https://docs.autosend.com/api-reference/custom-fields/create
POST /custom-fields
Create a new custom field to store additional contact attributes using the AutoSend API.
**Deprecated.** Custom fields are now called contact properties. This `/custom-fields` endpoint still works as an alias, but new integrations should use Create Contact Property. The new endpoint accepts `name`/`type`; the legacy `fieldName`/`fieldType` are still accepted.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/custom-fields \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"fieldName": "company",
"fieldType": "string"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/custom-fields"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"fieldName": "company",
"fieldType": "string"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/custom-fields", {
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
fieldName: "company",
fieldType: "string",
}),
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/custom-fields",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"fieldName" => "company",
"fieldType" => "string",
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"io/ioutil"
)
func main() {
payload, _ := json.Marshal(map[string]string{
"fieldName": "company",
"fieldType": "string",
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/custom-fields", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"fieldName": "company",
"fieldType": "string"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/custom-fields"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI.parse("https://api.autosend.com/v1/custom-fields")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer "
request["Content-Type"] = "application/json"
request.body = JSON.dump({
fieldName: "company",
fieldType: "string"
})
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Custom field created successfully",
"data": {
"id": "6960953e729f65f154369408",
"fieldName": "company",
"fieldType": "string",
"projectId": "229f1f77bcf86cd9273048038",
"createdAt": "2026-01-09T05:42:22.171Z",
"updatedAt": "2026-01-09T05:42:22.171Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body Parameters
The programmatic name for the custom field. Must be unique within the project. Cannot be one of the reserved names: `firstName`, `first_name`, `lastName`, `last_name`, `email`, `mobile`, `unsubscribed`, `unsubscribe`, `unsubscribe_groups`, `unsubscribe_preferences`, `userId`, `externalId`, `createdAt`, `updatedAt`, `contactLists`, `address`, `address_line_1`, `address_line_2`, `city`, `state`, `zip`, `country`.
The data type of the custom field. Must be one of: `string`, `number`, `boolean`, `date`.
A human-readable description of what the field represents.
A fallback value used when a contact does not have an explicit value for this field.
### Response
Custom field created successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Custom field created successfully"`
The created custom field.
Unique identifier of the custom field.
The programmatic name of the custom field.
The data type: `string`, `number`, `boolean`, or `date`.
The project this custom field belongs to.
ISO 8601 timestamp when the field was created.
ISO 8601 timestamp when the field was last updated.
# Delete Custom Field by Name
Source: https://docs.autosend.com/api-reference/custom-fields/delete
DELETE /custom-fields/fieldName/{customFieldName}
Delete a custom field by name to remove it from all contacts using the AutoSend API.
**Deprecated.** Custom fields are now called contact properties. This `/custom-fields` endpoint still works as an alias, but new integrations should use Delete Contact Property by Name.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/custom-fields/fieldName/planTier \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/custom-fields/fieldName/planTier"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/custom-fields/fieldName/planTier",
{
method: "DELETE",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/custom-fields/fieldName/planTier",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.autosend.com/v1/custom-fields/fieldName/planTier", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/custom-fields/fieldName/planTier"))
.header("Authorization", "Bearer ")
.DELETE()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/custom-fields/fieldName/planTier")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Custom field deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The `fieldName` of the custom field to delete.
### Response
Custom field deleted successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Custom field deleted successfully"`
#### Error Responses
Returned when no custom field with the given name exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Custom field not found",
"code": "CUSTOM_FIELD_NOT_FOUND",
"status": 404
}
}
```
# Get Custom Field by Name
Source: https://docs.autosend.com/api-reference/custom-fields/get-by-name
GET /custom-fields/fieldName/{customFieldName}
Retrieve a specific custom field's details by its name using the AutoSend API.
**Deprecated.** Custom fields are now called contact properties. This `/custom-fields` endpoint still works as an alias, but new integrations should use Get Contact Property by Name.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/custom-fields/fieldName/planTier \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/custom-fields/fieldName/planTier"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/custom-fields/fieldName/planTier",
{
method: "GET",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/custom-fields/fieldName/planTier",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/custom-fields/fieldName/planTier", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/custom-fields/fieldName/planTier"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/custom-fields/fieldName/planTier")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Custom field retrieved successfully",
"data": {
"id": "69c258bae3e715c0521153fb",
"fieldName": "isVerified",
"fieldType": "boolean",
"createdAt": "2026-03-24T09:26:18.826Z",
"updatedAt": "2026-03-24T09:26:18.826Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The `fieldName` of the custom field to retrieve.
### Response
Custom field retrieved successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Custom field retrieved successfully"`
The custom field object.
Unique identifier of the custom field.
The programmatic name of the custom field.
The data type: `string`, `number`, `boolean`, or `date`.
ISO 8601 timestamp when the field was created.
ISO 8601 timestamp when the field was last updated.
# List Custom Fields
Source: https://docs.autosend.com/api-reference/custom-fields/list
GET /custom-fields
List all custom fields defined in your account using the AutoSend API.
**Deprecated.** Custom fields are now called contact properties. This `/custom-fields` endpoint still works as an alias, but new integrations should use List Contact Properties.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/custom-fields \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/custom-fields"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/custom-fields", {
method: "GET",
headers: {
Authorization: "Bearer ",
},
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/custom-fields",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/custom-fields", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/custom-fields"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/custom-fields")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Custom fields retrieved successfully",
"data": {
"customFields": [
{
"id": "69c258bae3e715c0521153fb",
"fieldName": "isVerified",
"fieldType": "boolean",
"createdAt": "2026-03-24T09:26:18.826Z",
"updatedAt": "2026-03-24T09:26:18.826Z"
},
{
"id": "6960953e729f65f154369408",
"fieldName": "company",
"fieldType": "string",
"createdAt": "2026-01-09T05:42:22.171Z",
"updatedAt": "2026-01-09T05:42:22.171Z"
},
{
"id": "69609535729f65f154369402",
"fieldName": "industry",
"fieldType": "string",
"createdAt": "2026-01-09T05:42:13.892Z",
"updatedAt": "2026-01-09T05:42:13.892Z"
}
]
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Whether to include built-in reserved fields (email, firstName, lastName, createdAt, userId) in the response. Defaults to `true`.
### Response
Custom fields retrieved successfully
Indicates whether the request was successful.
Confirmation message.
Example: `"Custom fields retrieved successfully"`
Array of custom field objects.
Unique identifier of the custom field.
The programmatic name of the custom field.
The data type: `string`, `number`, `boolean`, or `date`.
ISO 8601 timestamp when the field was created.
ISO 8601 timestamp when the field was last updated.
# Add Domain
Source: https://docs.autosend.com/api-reference/domains/add
POST /domains
Add a new sending domain to your account for email authentication using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/domains \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"domain": "example.com",
"regionKey": "us-east-1"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json",
}
payload = {
"domain": "example.com",
"regionKey": "us-east-1",
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/domains", {
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
domain: "example.com",
regionKey: "us-east-1",
}),
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
"domain" => "example.com",
"regionKey" => "us-east-1",
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]string{
"domain": "example.com",
"regionKey": "us-east-1",
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/domains", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String body = "{\"domain\": \"example.com\", \"regionKey\": \"us-east-1\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI.parse("https://api.autosend.com/v1/domains")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Authorization"] = "Bearer "
request["Content-Type"] = "application/json"
request.body = JSON.generate({ domain: "example.com", regionKey: "us-east-1" })
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b2222",
"domainName": "example.com",
"verificationStatus": "PENDING_CONFIGURATION",
"ownershipVerified": false,
"dkimEnabled": false,
"mailFromEnabled": false,
"dmarcEnabled": false,
"dnsRecords": {
"ownership": {
"name": "_autosend-verify.example.com",
"value": "autosend-verify-d3b3f3481f3541b1b1d96e401ad89c91",
"type": "TXT",
"purpose": "Domain ownership verification"
},
"dkim": {
"name": "autosend._domainkey.example.com",
"value": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"type": "TXT",
"purpose": "DKIM authentication"
},
"mailFrom": [
{
"name": "asend.example.com",
"value": "feedback-smtp.us-east-1.amazonses.com",
"type": "MX",
"purpose": "Mail-from domain",
"priority": 10
},
{
"name": "asend.example.com",
"value": "v=spf1 include:amazonses.com ~all",
"type": "TXT",
"purpose": "SPF record for mail-from domain"
}
],
"dmarc": {
"name": "_dmarc.example.com",
"value": "v=DMARC1; p=none;",
"type": "TXT",
"purpose": "DMARC policy"
}
},
"createdAt": "2026-03-19T08:00:00.000Z",
"lastCheckedAt": "2026-03-19T08:00:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false
}
}
```
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Body
A valid domain name to add (e.g. `example.com` or `mail.example.com`). Must not include a protocol (`http://`), a path, or a trailing slash. Maximum 253 characters.
The AutoSend region key where your email service will be configured for this domain (e.g. `us-east-1`, `ap-south-1`, `us-east-2`, `eu-central-1`). If omitted, the project's primary region is used.
### Response
Indicates whether the request was successful.
The newly created domain object. The initial `verificationStatus` is always `PENDING_CONFIGURATION`. Configure the required DNS records in your DNS provider and then call `POST /domains/{domainId}/verify` to start verification.
Unique identifier for the domain (id). Use this as `domainId` in subsequent requests.
The domain name that was added.
Always `PENDING_CONFIGURATION` on creation.
Always `false` on creation.
Always `false` on creation.
Always `false` on creation.
Always `false` on creation.
DNS records to configure in your DNS provider before calling verify.
TXT record for domain ownership verification. Contains `name`, `value`, `type`, and `purpose` fields.
TXT record for DKIM authentication. Contains `name`, `value`, `type`, and `purpose` fields.
Array of MX and TXT records for the MAIL FROM domain. Each record contains `name`, `value`, `type`, `purpose`, and optionally `priority` fields.
TXT record for DMARC policy. Contains `name`, `value`, `type`, and `purpose` fields.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
The region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress. Always `false` on creation.
#### Errors
Returned when the `domain` field is missing, empty, exceeds 253 characters, or is not a valid domain name (e.g. includes a protocol or path).
Returned when the domain has already been added to this project.
# Delete Domain
Source: https://docs.autosend.com/api-reference/domains/delete
DELETE /domains/{domainId}
Remove a sending domain from your account using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111"
headers = {"Authorization": "Bearer "}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111",
{
method: "DELETE",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111"))
.header("Authorization", "Bearer ")
.DELETE()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Domain deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Path Parameters
The id of the domain to remove.
### Response
Indicates whether the domain was removed successfully.
Confirmation message.
Example: `"Domain deleted successfully"`
#### Error Responses
Returned when no domain with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Domain not found",
"code": "DOMAIN_NOT_FOUND",
"status": 404
}
}
```
# Disable Inbound
Source: https://docs.autosend.com/api-reference/domains/disable-inbound
POST /domains/{domainId}/inbound/disable
Disable inbound email receiving on a custom domain using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable",
{
method: "POST",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/disable")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1111",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"ownershipVerified": true,
"dkimEnabled": true,
"mailFromEnabled": true,
"dmarcEnabled": true,
"dnsRecords": {},
"createdAt": "2026-01-05T10:00:00.000Z",
"lastCheckedAt": "2026-03-10T14:20:00.000Z",
"verifiedAt": "2026-01-05T12:00:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false,
"inboundEnabledAt": null,
"inboundDomainVerifiedAt": null
}
}
```
Disable inbound email receiving on a custom domain.
Calling this endpoint clears the domain's inbound state and removes the inbound MX
record from the returned DNS records. If the domain had been added to the SES receipt
rule, its recipient condition is removed so SES stops accepting its mail. You can safely
remove the inbound MX record from your DNS afterwards.
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Path Parameters
The id of the domain on which to disable inbound receiving.
### Response
Indicates whether the request was successful.
The updated domain object.
Unique identifier for the domain (id).
The domain name (e.g. `example.com`).
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether the TXT ownership record has been verified.
Whether DKIM records have been verified.
Whether the MAIL FROM MX/TXT records have been verified.
Whether a DMARC TXT record has been detected.
DNS records for the domain. The `inboundMx` record is removed after inbound is disabled.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
ISO 8601 timestamp of when the domain was verified. Only present for verified domains.
The AutoSend region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress.
ISO 8601 timestamp of when inbound receiving was enabled. `null` after inbound is disabled.
ISO 8601 timestamp of when inbound was verified and live. `null` after inbound is disabled.
#### Errors
Returned when no domain with the given `domainId` exists in the project.
Returned when inbound receiving could not be disabled on the domain.
# Enable Inbound
Source: https://docs.autosend.com/api-reference/domains/enable-inbound
POST /domains/{domainId}/inbound/enable
Enable inbound email receiving on a verified custom domain using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable",
{
method: "POST",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/inbound/enable")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1111",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"ownershipVerified": true,
"dkimEnabled": true,
"mailFromEnabled": true,
"dmarcEnabled": true,
"dnsRecords": {
"inboundMx": {
"name": "example.com",
"value": "inbound-smtp.us-east-1.amazonaws.com",
"type": "MX",
"purpose": "inbound",
"priority": 10
}
},
"createdAt": "2026-01-05T10:00:00.000Z",
"lastCheckedAt": "2026-03-10T14:20:00.000Z",
"verifiedAt": "2026-01-05T12:00:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false,
"inboundEnabledAt": "2026-03-10T14:25:00.000Z",
"inboundDomainVerifiedAt": null
}
}
```
Enable inbound email receiving on a custom domain that has already been verified.
Calling this endpoint stamps the domain as inbound-enabled and returns the inbound MX
record you must publish in your DNS. Once that MX record is detected in DNS during the
next verification check, AutoSend wires up the SES receipt rule and inbound becomes live
for the domain (at which point `inboundDomainVerifiedAt` is set).
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Path Parameters
The id of the domain on which to enable inbound receiving.
### Response
Indicates whether the request was successful.
The updated domain object.
Unique identifier for the domain (id).
The domain name (e.g. `example.com`).
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether the TXT ownership record has been verified.
Whether DKIM records have been verified.
Whether the MAIL FROM MX/TXT records have been verified.
Whether a DMARC TXT record has been detected.
DNS records for the domain. After enabling inbound, this includes the `inboundMx`
record you must publish.
The MX record to publish so AutoSend can receive mail for this domain. Contains
`name`, `value`, `type`, `purpose`, and `priority` fields.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
ISO 8601 timestamp of when the domain was verified. Only present for verified domains.
The AutoSend region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress.
ISO 8601 timestamp of when inbound receiving was enabled on the domain.
ISO 8601 timestamp of when the inbound MX record was detected and the domain was
added to the SES receipt rule — i.e. inbound is verified and live. `null` until the
MX record is detected in DNS.
#### Errors
Returned when no domain with the given `domainId` exists in the project.
Returned when inbound receiving could not be enabled on the domain.
# Get Domain
Source: https://docs.autosend.com/api-reference/domains/get
GET /domains/{domainId}
Retrieve the details and verification status of a specific domain using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111"
headers = {"Authorization": "Bearer "}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111",
{
method: "GET",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1111",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"ownershipVerified": true,
"dkimEnabled": true,
"mailFromEnabled": true,
"dmarcEnabled": true,
"dnsRecords": {
"ownership": {
"name": "_autosend-verify.example.com",
"value": "autosend-verify-cfdbf714dbec4584b82b925bfff91770",
"type": "TXT",
"purpose": "Domain ownership verification"
},
"dkim": {
"name": "autosend._domainkey.example.com",
"value": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"type": "TXT",
"purpose": "DKIM authentication"
},
"mailFrom": [
{
"name": "mail.example.com",
"value": "feedback-smtp.us-east-1.amazonses.com",
"type": "MX",
"purpose": "Mail-from domain",
"priority": 10
},
{
"name": "mail.example.com",
"value": "v=spf1 include:amazonses.com ~all",
"type": "TXT",
"purpose": "SPF record for mail-from domain"
}
],
"dmarc": {
"name": "_dmarc.example.com",
"value": "v=DMARC1; p=none;",
"type": "TXT",
"purpose": "DMARC policy"
}
},
"createdAt": "2026-01-05T10:00:00.000Z",
"lastCheckedAt": "2026-03-10T14:20:00.000Z",
"verifiedAt": "2026-01-05T12:00:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false
}
}
```
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Path Parameters
The id of the domain to retrieve.
### Response
Indicates whether the request was successful.
The requested domain object.
Unique identifier for the domain (id).
The domain name (e.g. `example.com`).
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether the TXT ownership record has been verified. Once `true`, never reverts to `false`.
Whether DKIM CNAME records have been verified. Once `true`, never reverts to `false`.
Whether the MAIL FROM MX/TXT records have been verified. Once `true`, never reverts to `false`.
Whether a DMARC TXT record has been detected. Once `true`, never reverts to `false`.
DNS records required for domain verification.
TXT record for domain ownership verification. Contains `name`, `value`, `type`, and `purpose` fields.
TXT record for DKIM authentication. Contains `name`, `value`, `type`, and `purpose` fields.
Array of MX and TXT records for the MAIL FROM domain. Each record contains `name`, `value`, `type`, `purpose`, and optionally `priority` fields.
TXT record for DMARC policy. Contains `name`, `value`, `type`, and `purpose` fields.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
ISO 8601 timestamp of when the domain was verified. Only present for verified domains.
The AutoSend region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress.
#### Errors
Returned when no domain with the given `domainId` exists in the project.
# List Domains
Source: https://docs.autosend.com/api-reference/domains/list
GET /domains
List all sending domains configured in your account using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/domains \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains"
headers = {"Authorization": "Bearer "}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch("https://api.autosend.com/v1/domains", {
method: "GET",
headers: {
Authorization: "Bearer ",
},
});
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/domains", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"domains": [
{
"id": "60d5ec49f1b2c72d9c8b1111",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"ownershipVerified": true,
"dkimEnabled": true,
"mailFromEnabled": true,
"dmarcEnabled": true,
"dnsRecords": {
"ownership": {
"name": "_autosend-verify.example.com",
"value": "autosend-verify-cfdbf714dbec4584b82b925bfff91770",
"type": "TXT",
"purpose": "Domain ownership verification"
},
"dkim": {
"name": "autosend._domainkey.example.com",
"value": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"type": "TXT",
"purpose": "DKIM authentication"
},
"mailFrom": [
{
"name": "mail.example.com",
"value": "feedback-smtp.us-east-1.amazonses.com",
"type": "MX",
"purpose": "Mail-from domain",
"priority": 10
},
{
"name": "mail.example.com",
"value": "v=spf1 include:amazonses.com ~all",
"type": "TXT",
"purpose": "SPF record for mail-from domain"
}
],
"dmarc": {
"name": "_dmarc.example.com",
"value": "v=DMARC1; p=none;",
"type": "TXT",
"purpose": "DMARC policy"
}
},
"createdAt": "2026-01-05T10:00:00.000Z",
"lastCheckedAt": "2026-03-10T14:20:00.000Z",
"verifiedAt": "2026-01-05T12:00:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false
},
{
"id": "60d5ec49f1b2c72d9c8b2222",
"domainName": "mail.example.com",
"verificationStatus": "PENDING_CONFIGURATION",
"ownershipVerified": false,
"dkimEnabled": false,
"mailFromEnabled": false,
"dmarcEnabled": false,
"dnsRecords": {
"ownership": {
"name": "_autosend-verify.mail.example.com",
"value": "autosend-verify-785f80bdecf44a94ad9ac28eac1a6c07",
"type": "TXT",
"purpose": "Domain ownership verification"
},
"dkim": {
"name": "autosend._domainkey.mail.example.com",
"value": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"type": "TXT",
"purpose": "DKIM authentication"
},
"mailFrom": [
{
"name": "mail.mail.example.com",
"value": "feedback-smtp.us-east-1.amazonses.com",
"type": "MX",
"purpose": "Mail-from domain",
"priority": 10
},
{
"name": "mail.mail.example.com",
"value": "v=spf1 include:amazonses.com ~all",
"type": "TXT",
"purpose": "SPF record for mail-from domain"
}
],
"dmarc": {
"name": "_dmarc.mail.example.com",
"value": "v=DMARC1; p=none;",
"type": "TXT",
"purpose": "DMARC policy"
}
},
"createdAt": "2026-03-01T09:00:00.000Z",
"lastCheckedAt": "2026-03-19T08:45:00.000Z",
"regionKey": "us-east-1",
"verificationInProgress": false
}
]
}
}
```
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
### Response
Indicates whether the request was successful.
Response data object.
Array of domain objects for the project. Returns an empty array if no domains have been added.
Unique identifier for the domain (id).
The domain name (e.g. `example.com`).
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether the TXT ownership record has been verified. Once `true`, never reverts to `false`.
Whether DKIM CNAME records have been verified. Once `true`, never reverts to `false`.
Whether the MAIL FROM MX/TXT records have been verified. Once `true`, never reverts to `false`.
Whether a DMARC TXT record has been detected. Once `true`, never reverts to `false`.
DNS records required for domain verification.
TXT record for domain ownership verification. Contains `name`, `value`, `type`, and `purpose` fields.
TXT record for DKIM authentication. Contains `name`, `value`, `type`, and `purpose` fields.
Array of MX and TXT records for the MAIL FROM domain. Each record contains `name`, `value`, `type`, `purpose`, and optionally `priority` fields.
TXT record for DMARC policy. Contains `name`, `value`, `type`, and `purpose` fields.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
ISO 8601 timestamp of when the domain was verified. Only present for verified domains.
The AutoSend region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress.
# Verify Domain
Source: https://docs.autosend.com/api-reference/domains/verify
POST /domains/{domainId}/verify
Trigger DNS verification for a sending domain to confirm ownership using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify"
headers = {"Authorization": "Bearer "}
response = requests.post(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify",
{
method: "POST",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => "",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer ",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify"))
.header("Authorization", "Bearer ")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI.parse("https://api.autosend.com/v1/domains/60d5ec49f1b2c72d9c8b1111/verify")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Authorization"] = "Bearer "
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"domain": {
"id": "60d5ec49f1b2c72d9c8b1111",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"ownershipVerified": true,
"dkimEnabled": true,
"mailFromEnabled": false,
"dmarcEnabled": false,
"dnsRecords": {
"ownership": {
"name": "_autosend-verify.example.com",
"value": "autosend-verify-cfdbf714dbec4584b82b925bfff91770",
"type": "TXT",
"purpose": "Domain ownership verification"
},
"dkim": {
"name": "autosend._domainkey.example.com",
"value": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"type": "TXT",
"purpose": "DKIM authentication"
},
"mailFrom": [
{
"name": "mail.example.com",
"value": "feedback-smtp.us-east-1.amazonses.com",
"type": "MX",
"purpose": "Mail-from domain",
"priority": 10
},
{
"name": "mail.example.com",
"value": "v=spf1 include:amazonses.com ~all",
"type": "TXT",
"purpose": "SPF record for mail-from domain"
}
],
"dmarc": {
"name": "_dmarc.example.com",
"value": "v=DMARC1; p=none;",
"type": "TXT",
"purpose": "DMARC policy"
}
},
"createdAt": "2026-01-05T10:00:00.000Z",
"lastCheckedAt": "2026-03-25T07:05:29.525Z",
"regionKey": "us-east-1",
"verificationInProgress": false
},
"verificationInProgress": true,
"ownershipVerified": true,
"verificationStatus": "VERIFIED",
"dkimEnabled": true,
"mailFromEnabled": false,
"dmarcEnabled": false
},
"message": "Domain verification status updated"
}
```
### Authorizations
Bearer authentication header of the form `Bearer `, where `` is your API key.
#### Path Parameters
The id of the domain to verify.
### Response
Indicates whether the request was accepted.
Always `"Domain verification status updated"` on success.
Contains the full domain object and top-level verification status summary fields.
The full domain object with updated verification state.
Unique identifier for the domain (id).
The domain name (e.g. `example.com`).
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether the TXT ownership record has been verified.
Whether DKIM CNAME records have been verified.
Whether the MAIL FROM MX/TXT records have been verified.
Whether a DMARC TXT record has been detected.
DNS records required for domain verification.
TXT record for domain ownership verification. Contains `name`, `value`, `type`, and `purpose` fields.
TXT record for DKIM authentication. Contains `name`, `value`, `type`, and `purpose` fields.
Array of MX and TXT records for the MAIL FROM domain. Each record contains `name`, `value`, `type`, `purpose`, and optionally `priority` fields.
TXT record for DMARC policy. Contains `name`, `value`, `type`, and `purpose` fields.
ISO 8601 timestamp of when the domain was added.
ISO 8601 timestamp of the last DNS verification check.
The AutoSend region key where the domain is configured (e.g. `us-east-1`).
Whether a DNS verification check is currently in progress for this domain.
Whether verification is currently in progress.
Whether the TXT ownership record has been verified.
Overall domain verification status. One of `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`.
Whether DKIM CNAME records have been verified.
Whether the MAIL FROM MX/TXT records have been verified.
Whether a DMARC TXT record has been detected.
#### Errors
Returned when no domain with the given `domainId` exists in the project.
Returned when the verify endpoint has been called too many times in a short period. Wait before retrying.
# API Errors
Source: https://docs.autosend.com/api-reference/errors
This guide provides a comprehensive breakdown of all possible API errors, HTTP status codes, and how to handle them effectively.
## HTTP Status Codes Overview
The AutoSend API uses standard HTTP status codes to indicate the success or failure of your requests:
| Code | Status | Description | When It Occurs |
| ----- | --------------------- | ---------------------------------- | ----------------------------------------- |
| `400` | Bad Request | Invalid request parameters | Validation errors, malformed requests |
| `401` | Unauthorized | Missing or invalid API key | Authentication failures |
| `402` | Payment Required | Payment required | Payment issues, subscription expired |
| `403` | Forbidden | Insufficient permissions or limits | Plan limits exceeded, insufficient access |
| `404` | Not Found | Resource doesn't exist | Requesting non-existent resources |
| `429` | Too Many Requests | Rate limit exceeded | Too many requests in a time period |
| `500` | Internal Server Error | Server error | Unexpected server-side errors |
## Client Errors (4xx)
### 400 Bad Request - Invalid Payload
The request was malformed or contains invalid data. This is the most common error code for validation issues.
**Common Causes:**
* Invalid data format
* Missing required fields
* Invalid data types
* Value constraints violated (e.g., max length exceeded)
* Malformed JSON
**Error Response Format:**
```json theme={null}
{
"success": false,
"error": {
"message": "Validation failed",
"code": "VALIDATION_FAILED",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
}
```
### 401 Unauthorized
The API key is missing, invalid, or expired.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "Unauthorized",
"code": "UNAUTHORIZED"
}
}
```
**Common Causes:**
* Missing `Authorization` header
* Invalid API key
* Expired API key
* Incorrect API key format
### 402 Payment Required
Payment is required to complete the request. This typically occurs when there are payment issues or the subscription has expired.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "Please upgrade your plan to access these resources.",
"code": "PLAN_UPGRADE_REQUIRED"
}
}
```
**Common Causes:**
* Subscription expired
* Payment method declined
* Account requires payment
* Billing issues
### 403 Forbidden
The API key is valid but doesn't have permission to perform the requested action.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "Insufficient permissions to access these resources.",
"code": "INSUFFICIENT_PERMISSIONS"
}
}
```
### 404 Not Found
The requested resource doesn't exist.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "The requested resource was not found.",
"code": "NOT_FOUND"
}
}
```
**Common Causes:**
* Invalid resource ID
* Resource was deleted
* Resource belongs to a different project
* Incorrect endpoint path
### 429 Too Many Requests
The rate limit has been exceeded. You've made too many requests in a given time period.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 10 // epoch timestamp
}
}
```
## Server Errors (5xx)
### 500 Internal Server Error
An unexpected error occurred on the server side.
**Error Response:**
```json theme={null}
{
"success": false,
"error": {
"message": "Something went wrong on our end",
"code": "INTERNAL_SERVER_ERROR"
}
}
```
## Best Practices for Error Handling
### 1. Always Check Response Status
```javascript theme={null}
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
// Handle error based on status code
}
```
### 2. Implement Comprehensive Error Handling
```javascript theme={null}
async function handleApiRequest(url, options) {
try {
const response = await fetch(url, options);
const data = await response.json();
if (!response.ok) {
switch (response.status) {
case 400:
handleValidationError(data);
break;
case 401:
handleAuthenticationError();
break;
case 402:
handlePaymentError(data);
break;
case 403:
handlePermissionError(data);
break;
case 404:
handleNotFoundError();
break;
case 409:
handleConflictError(data);
break;
case 429:
await handleRateLimitError(data);
break;
case 500:
await handleServerError(response.status);
break;
default:
handleUnknownError(response.status, data);
}
throw new Error(data.message || "Request failed");
}
return data;
} catch (error) {
if (error instanceof TypeError) {
// Network error
console.error("Network error:", error);
} else {
// API error
console.error("API error:", error);
}
throw error;
}
}
```
### 3. Implement Retry Logic
```javascript theme={null}
async function retryRequest(requestFn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Only retry on certain errors
const shouldRetry = error.status === 429 || error.status === 500;
if (shouldRetry) {
const delay = error.retryAfter
? error.retryAfter * 1000
: Math.pow(2, i) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}
```
### 4. Log Errors for Monitoring
```javascript theme={null}
function logError(error, context) {
// Log to your error tracking service
console.error("API Error:", {
status: error.status,
message: error.message,
errors: error.errors,
context: context,
timestamp: new Date().toISOString(),
});
}
```
### 5. Provide User-Friendly Error Messages
```javascript theme={null}
function getUserFriendlyError(error) {
switch (error.status) {
case 400:
return "Please check your input and try again.";
case 401:
return "Please check your API key and try again.";
case 402:
return "Payment required. Please update your payment method.";
case 403:
return "You have reached your plan limit. Please upgrade your plan.";
case 404:
return "The requested resource was not found.";
case 429:
return "Too many requests. Please wait a moment and try again.";
case 500:
return "Service temporarily unavailable. Please try again later.";
default:
return "An unexpected error occurred. Please try again.";
}
}
```
## Need Help?
If you're experiencing persistent errors or need assistance with error handling, please contact support at .
When reporting errors, please include:
* The endpoint you're calling
* The request payload
* The full error response
* Steps to reproduce the issue
# Create Event
Source: https://docs.autosend.com/api-reference/events/create-event
POST /events
Creates a new event definition under the project.
Defines a new event for the project. Event names and property names accept ASCII letters, digits, and underscores only, and must be 64 characters or fewer.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/events \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": [
{ "propertyName": "order_total", "type": "number", "description": "Total order value in USD" },
{ "propertyName": "currency", "type": "string", "suggestedValues": ["USD", "EUR", "GBP"] },
{ "propertyName": "is_first_purchase", "type": "boolean" }
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events"
headers = {
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json"
}
payload = {
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": [
{"propertyName": "order_total", "type": "number", "description": "Total order value in USD"},
{"propertyName": "currency", "type": "string", "suggestedValues": ["USD", "EUR", "GBP"]},
{"propertyName": "is_first_purchase", "type": "boolean"}
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
eventName: 'order_completed',
description: 'Fired when a customer completes a checkout',
properties: [
{ propertyName: 'order_total', type: 'number', description: 'Total order value in USD' },
{ propertyName: 'currency', type: 'string', suggestedValues: ['USD', 'EUR', 'GBP'] },
{ propertyName: 'is_first_purchase', type: 'boolean' }
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'order_completed',
'description' => 'Fired when a customer completes a checkout',
'properties' => [
['propertyName' => 'order_total', 'type' => 'number', 'description' => 'Total order value in USD'],
['propertyName' => 'currency', 'type' => 'string', 'suggestedValues' => ['USD', 'EUR', 'GBP']],
['propertyName' => 'is_first_purchase', 'type' => 'boolean']
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-project-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events"
payload := map[string]interface{}{
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": []map[string]interface{}{
{"propertyName": "order_total", "type": "number", "description": "Total order value in USD"},
{"propertyName": "currency", "type": "string", "suggestedValues": []string{"USD", "EUR", "GBP"}},
{"propertyName": "is_first_purchase", "type": "boolean"},
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateEvent {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"eventName\": \"order_completed\",\n" +
" \"description\": \"Fired when a customer completes a checkout\",\n" +
" \"properties\": [\n" +
" { \"propertyName\": \"order_total\", \"type\": \"number\" },\n" +
" { \"propertyName\": \"currency\", \"type\": \"string\" }\n" +
" ]\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/events')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
request['Content-Type'] = 'application/json'
request.body = {
eventName: 'order_completed',
description: 'Fired when a customer completes a checkout',
properties: [
{ propertyName: 'order_total', type: 'number' },
{ propertyName: 'currency', type: 'string', suggestedValues: ['USD', 'EUR', 'GBP'] },
{ propertyName: 'is_first_purchase', type: 'boolean' }
]
}.to_json
response = http.request(request)
puts response.body
```
```json 201 Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": [
{
"propertyName": "order_total",
"type": "number",
"description": "Total order value in USD",
"suggestedValues": []
},
{
"propertyName": "currency",
"type": "string",
"suggestedValues": ["USD", "EUR", "GBP"]
},
{
"propertyName": "is_first_purchase",
"type": "boolean",
"suggestedValues": []
}
],
"projectId": "229f1f77bcf86cd9273048038",
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Body
Unique name of the event within the project. ASCII letters, digits, and underscores only.
Maximum length: `64`
Example: `"order_completed"`
Optional human-readable description.
Example: `"Fired when a customer completes a checkout"`
Schema for the event's custom properties. Up to 50 properties per event.
Property identifier. ASCII letters, digits, and underscores only. Must be unique within the event.
Maximum length: `64`
Example: `"order_total"`
Declared type for the property value. One of `string`, `number`, `date`, or `boolean`.
Example: `"number"`
Optional human-readable description of the property.
Optional list of suggested values shown in the UI. Accepts an array, or a comma-separated string. Each value is coerced to the declared `type`.
Example: `["USD", "EUR", "GBP"]`
#### Response
Event created successfully (201)
Indicates if the request was successful
Example: `true`
The created event definition
Unique event definition identifier
Event name as supplied at creation
Human-readable description, or `null`
Declared property schema for the event.
Property identifier.
Declared type: `string`, `number`, `date`, or `boolean`.
Optional human-readable description.
Suggested values for the property (may be empty).
The project this event definition belongs to.
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
#### Error Responses
```json theme={null}
{
"success": false,
"error": {
"message": "Event name can only contain ASCII letters (a-z, A-Z), numbers (0-9), and underscores (_).",
"code": "INVALID_EVENT_NAME_CHARACTERS"
}
}
```
Returned when an event with the same `eventName` already exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Event with this name already exists in the project",
"code": "EVENT_ALREADY_EXISTS"
}
}
```
Returned when the project has 100 active event definitions.
```json theme={null}
{
"success": false,
"error": {
"message": "Maximum number of events (100) reached for this project.",
"code": "MAX_EVENTS_REACHED"
}
}
```
# Delete Event
Source: https://docs.autosend.com/api-reference/events/delete-event
DELETE /events/eventName/{eventName}
Soft-deletes an event definition. Existing event logs for this name are retained but no new logs can be recorded against it.
Soft-deletes an event definition. Existing event logs recorded for this name are retained for analytics, but no new events can be sent against it.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/events/eventName/order_completed \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events/eventName/order_completed"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events/eventName/order_completed', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events/eventName/order_completed"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteEvent {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events/eventName/order_completed");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/events/eventName/order_completed')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Event deleted successfully"
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Name of the event to delete.
Example: `"order_completed"`
#### Response
Event deleted successfully
Example: `true`
Confirmation message.
Example: `"Event deleted successfully"`
#### Error Responses
```json theme={null}
{
"success": false,
"error": {
"message": "Event not found",
"code": "EVENT_NOT_FOUND"
}
}
```
# Get Event
Source: https://docs.autosend.com/api-reference/events/get-event
GET /events/eventName/{eventName}
Retrieves a single event definition by its name.
Looks up a single event definition by its `eventName`. Soft-deleted events are not returned.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/events/eventName/order_completed \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events/eventName/order_completed"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events/eventName/order_completed', {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events/eventName/order_completed"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetEvent {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events/eventName/order_completed");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/events/eventName/order_completed')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": [
{
"propertyName": "order_total",
"type": "number",
"description": "Total order value in USD",
"suggestedValues": []
},
{
"propertyName": "currency",
"type": "string",
"suggestedValues": ["USD", "EUR", "GBP"]
}
],
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Name of the event to fetch. ASCII letters, digits, and underscores only.
Maximum length: `64`
Example: `"order_completed"`
#### Response
Event retrieved successfully
Indicates if the request was successful
Example: `true`
The event definition
Unique event definition identifier
Event name
Human-readable description
Declared property schema.
Property identifier.
Declared type: `string`, `number`, `date`, or `boolean`.
Optional human-readable description.
Suggested values for the property (may be empty).
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
#### Error Responses
```json theme={null}
{
"success": false,
"error": {
"message": "Event not found",
"code": "EVENT_NOT_FOUND"
}
}
```
# List Events
Source: https://docs.autosend.com/api-reference/events/list-events
GET /events
Retrieves all event definitions for the authenticated project.
Returns every active event definition for the authenticated project, sorted newest-first.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/events \
--header 'Authorization: Bearer AS_your-project-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events"
headers = {
"Authorization": "Bearer AS_your-project-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events', {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-project-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListEvents {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/events')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer AS_your-project-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"events": [
{
"id": "60d5ec49f1b2c72d9c8b1234",
"eventName": "order_completed",
"description": "Fired when a customer completes a checkout",
"properties": [
{
"propertyName": "order_total",
"type": "number",
"description": "Total order value in USD",
"suggestedValues": []
},
{
"propertyName": "currency",
"type": "string",
"suggestedValues": ["USD", "EUR", "GBP"]
}
],
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T10:00:00.000Z"
},
{
"id": "60d5ec49f1b2c72d9c8b5678",
"eventName": "signup_completed",
"description": null,
"properties": [],
"createdAt": "2026-05-01T08:30:00.000Z",
"updatedAt": "2026-05-01T08:30:00.000Z"
}
]
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
#### Response
Events retrieved successfully
Indicates if the request was successful
Example: `true`
Array of event definitions belonging to the project
Unique event definition identifier
Event name
Example: `"order_completed"`
Human-readable description
Declared property schema for the event
Property identifier
Declared type. One of `string`, `number`, `date`, or `boolean`
Optional human-readable description
Suggested values for the property (may be empty)
ISO 8601 timestamp of creation
ISO 8601 timestamp of last update
# Send Event
Source: https://docs.autosend.com/api-reference/events/send-event
POST /events/send
Records an event log for a contact. The eventName must match an existing event definition; properties are validated and coerced against the declared schema.
Records an event log for a contact. The `eventName` must match an existing event definition; supplied `eventProperties` are validated and coerced against the declared property schema. Either `email` or `contactId` is required to identify the contact.
Triggering an event also evaluates any active workflow automations whose entry criteria match this event name. The workflow evaluation is fire-and-forget — it never blocks the API response.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/events/send \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"eventName": "order_completed",
"email": "jane@example.com",
"eventProperties": {
"order_total": 129.99,
"currency": "USD",
"is_first_purchase": true
}
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events/send"
headers = {
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json"
}
payload = {
"eventName": "order_completed",
"email": "jane@example.com",
"eventProperties": {
"order_total": 129.99,
"currency": "USD",
"is_first_purchase": True
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
eventName: 'order_completed',
email: 'jane@example.com',
eventProperties: {
order_total: 129.99,
currency: 'USD',
is_first_purchase: true
}
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'order_completed',
'email' => 'jane@example.com',
'eventProperties' => [
'order_total' => 129.99,
'currency' => 'USD',
'is_first_purchase' => true
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-project-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events/send"
payload := map[string]interface{}{
"eventName": "order_completed",
"email": "jane@example.com",
"eventProperties": map[string]interface{}{
"order_total": 129.99,
"currency": "USD",
"is_first_purchase": true,
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SendEvent {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events/send");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"eventName\": \"order_completed\",\n" +
" \"email\": \"jane@example.com\",\n" +
" \"eventProperties\": {\n" +
" \"order_total\": 129.99,\n" +
" \"currency\": \"USD\",\n" +
" \"is_first_purchase\": true\n" +
" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/events/send')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
request['Content-Type'] = 'application/json'
request.body = {
eventName: 'order_completed',
email: 'jane@example.com',
eventProperties: {
order_total: 129.99,
currency: 'USD',
is_first_purchase: true
}
}.to_json
response = http.request(request)
puts response.body
```
```json 201 Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b9999",
"eventName": "order_completed",
"contactId": "60d5ec49f1b2c72d9c8b8888",
"properties": {
"order_total": 129.99,
"currency": "USD",
"is_first_purchase": true
},
"createdAt": "2026-05-08T13:45:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Body
Name of an existing event definition for this project.
Example: `"order_completed"`
You must provide either `email` or `contactId` to identify the contact. Providing both is allowed - `contactId` takes precedence.
Email address of the contact this event belongs to.
Example: `"jane@example.com"`
ID of the contact this event belongs to.
Example: `"60d5ec49f1b2c72d9c8b8888"`
Key/value map of property values. Each key must match a `propertyName` declared on the event definition; values are coerced to the declared `type` (string, number, date, or boolean). Unknown properties are rejected.
Example: `{ "order_total": 129.99, "currency": "USD" }`
#### Response
Event recorded (201)
Example: `true`
The recorded event log
Unique event log identifier
Event name
Contact this event was recorded against
Coerced event property values
ISO 8601 timestamp the event was recorded at
#### Error Responses
Returned when neither `email` nor `contactId` is provided.
```json theme={null}
{
"success": false,
"error": {
"message": "Either email or contactId is required",
"code": "EMAIL_OR_CONTACT_ID_REQUIRED"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Contact not found for the provided email or contactId",
"code": "CONTACT_NOT_FOUND_FOR_EVENT"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Event not found",
"code": "EVENT_NOT_FOUND"
}
}
```
Returned when `eventProperties` contains a key not declared on the event definition.
```json theme={null}
{
"success": false,
"error": {
"message": "Property \"foo\" is not declared on event \"order_completed\"",
"code": "UNKNOWN_PROPERTY"
}
}
```
Returned when a property value cannot be coerced to its declared type.
```json theme={null}
{
"success": false,
"error": {
"message": "Property \"order_total\" must be a number",
"code": "INVALID_PROPERTY_VALUE"
}
}
```
# Update Event
Source: https://docs.autosend.com/api-reference/events/update-event
PATCH /events/eventName/{eventName}
Updates the description or property schema of an existing event definition. The eventName itself cannot be changed.
Updates the `description` and/or `properties` schema of an existing event definition. The `eventName` itself cannot be changed — to rename, create a new event and delete the old one.
When `properties` is provided, the supplied array fully replaces the existing property schema. Properties not included in the request will be removed.
```bash cURL theme={null}
curl --request PATCH \
--url https://api.autosend.com/v1/events/eventName/order_completed \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"description": "Updated: fired on successful checkout",
"properties": [
{ "propertyName": "order_total", "type": "number" },
{ "propertyName": "currency", "type": "string", "suggestedValues": ["USD", "EUR"] },
{ "propertyName": "coupon_code", "type": "string" }
]
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/events/eventName/order_completed"
headers = {
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json"
}
payload = {
"description": "Updated: fired on successful checkout",
"properties": [
{"propertyName": "order_total", "type": "number"},
{"propertyName": "currency", "type": "string", "suggestedValues": ["USD", "EUR"]},
{"propertyName": "coupon_code", "type": "string"}
]
}
response = requests.patch(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/events/eventName/order_completed', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
description: 'Updated: fired on successful checkout',
properties: [
{ propertyName: 'order_total', type: 'number' },
{ propertyName: 'currency', type: 'string', suggestedValues: ['USD', 'EUR'] },
{ propertyName: 'coupon_code', type: 'string' }
]
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'Updated: fired on successful checkout',
'properties' => [
['propertyName' => 'order_total', 'type' => 'number'],
['propertyName' => 'currency', 'type' => 'string', 'suggestedValues' => ['USD', 'EUR']],
['propertyName' => 'coupon_code', 'type' => 'string']
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-project-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/events/eventName/order_completed"
payload := map[string]interface{}{
"description": "Updated: fired on successful checkout",
"properties": []map[string]interface{}{
{"propertyName": "order_total", "type": "number"},
{"propertyName": "currency", "type": "string", "suggestedValues": []string{"USD", "EUR"}},
{"propertyName": "coupon_code", "type": "string"},
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-project-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpdateEvent {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/events/eventName/order_completed");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PATCH");
con.setRequestProperty("Authorization", "Bearer AS_your-project-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"description\": \"Updated: fired on successful checkout\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/events/eventName/order_completed')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(uri.path)
request['Authorization'] = 'Bearer AS_your-project-api-key'
request['Content-Type'] = 'application/json'
request.body = {
description: 'Updated: fired on successful checkout',
properties: [
{ propertyName: 'order_total', type: 'number' },
{ propertyName: 'currency', type: 'string', suggestedValues: ['USD', 'EUR'] }
]
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"eventName": "order_completed",
"description": "Updated: fired on successful checkout",
"properties": [
{ "propertyName": "order_total", "type": "number", "suggestedValues": [] },
{ "propertyName": "currency", "type": "string", "suggestedValues": ["USD", "EUR"] },
{ "propertyName": "coupon_code", "type": "string", "suggestedValues": [] }
],
"createdAt": "2026-05-08T10:00:00.000Z",
"updatedAt": "2026-05-08T11:30:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
Name of the event to update.
Example: `"order_completed"`
### Body
At least one of `description` or `properties` must be provided.
New human-readable description for the event.
Replacement property schema. Up to 50 properties per event. See [Create Event](./create-event) for the property object shape.
#### Response
Event updated successfully
Example: `true`
The updated event definition.
Unique event definition identifier.
Event name.
Human-readable description, or `null`.
Replacement property schema after the update.
Property identifier.
Declared type: `string`, `number`, `date`, or `boolean`.
Optional human-readable description.
Suggested values for the property (may be empty).
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
#### Error Responses
Returned when neither `description` nor `properties` is provided.
```json theme={null}
{
"success": false,
"error": {
"message": "No data to update",
"code": "NO_DATA_TO_UPDATE"
}
}
```
```json theme={null}
{
"success": false,
"error": {
"message": "Event not found",
"code": "EVENT_NOT_FOUND"
}
}
```
# Download Attachment
Source: https://docs.autosend.com/api-reference/inbound-emails/download-attachment
GET /inbound/messages/{id}/attachments/{idx}
Retrieves a single attachment from an inbound message, referenced by its attachmentId or zero-based index. By default returns JSON metadata with a short-lived pre-signed download URL; pass download=true to stream the raw attachment bytes with the appropriate Content-Type and Content-Disposition headers.
This endpoint uses a standard **project API key** (`AS_` prefix). It returns a single attachment from an inbound message, identified either by its `attachmentId` (recommended, stable) or by its zero-based `index` from the message's `attachments` array.
By default it returns **JSON metadata** with a short-lived, pre-signed download URL. Pass `?download=true` to stream the **raw bytes** instead, with `Content-Type` and `Content-Disposition: attachment; filename="…"` headers.
```bash cURL (JSON) theme={null}
curl --request GET \
--url https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/att_60d5ec49f1b2c72d9c8b9999 \
--header 'Authorization: Bearer as_your-api-key'
```
```python Python (JSON) theme={null}
import requests
url = "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/att_60d5ec49f1b2c72d9c8b9999"
headers = {
"Authorization": "Bearer as_your-api-key"
}
response = requests.get(url, headers=headers)
data = response.json()
# Follow the pre-signed URL to download the file (no auth header needed).
file_url = data["data"]["downloadUrl"]
file = requests.get(file_url)
with open(data["data"]["filename"], "wb") as f:
f.write(file.content)
```
```javascript JavaScript (JSON) theme={null}
const response = await fetch(
'https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/att_60d5ec49f1b2c72d9c8b9999',
{
method: 'GET',
headers: {
'Authorization': 'Bearer as_your-api-key'
}
}
);
const { data } = await response.json();
// Follow the pre-signed URL to download the file (no auth header needed).
const file = await fetch(data.downloadUrl);
const blob = await file.blob();
// Save or process the blob as needed
```
```bash cURL (stream) theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0?download=true' \
--header 'Authorization: Bearer as_your-api-key' \
--output receipt.pdf
```
```python Python (stream) theme={null}
import requests
url = "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0"
headers = {
"Authorization": "Bearer as_your-api-key"
}
response = requests.get(url, headers=headers, params={"download": "true"})
with open("receipt.pdf", "wb") as f:
f.write(response.content)
```
```javascript JavaScript (stream) theme={null}
const response = await fetch(
'https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0?download=true',
{
method: 'GET',
headers: {
'Authorization': 'Bearer as_your-api-key'
}
}
);
const blob = await response.blob();
// Save or process the blob as needed
```
```php PHP (stream) theme={null}
```
```go Go (stream) theme={null}
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
url := "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0?download=true"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer as_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
out, _ := os.Create("receipt.pdf")
defer out.Close()
io.Copy(out, resp.Body)
}
```
```java Java (stream) theme={null}
import java.io.InputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class DownloadAttachment {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0?download=true");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer as_your-api-key");
try (InputStream in = con.getInputStream();
FileOutputStream out = new FileOutputStream("receipt.pdf")) {
byte[] buffer = new byte[8192];
int n;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
}
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby (stream) theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/attachments/0?download=true')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer as_your-api-key'
response = http.request(request)
File.open('receipt.pdf', 'wb') { |f| f.write(response.body) }
```
```json 200 Response (JSON) theme={null}
{
"success": true,
"data": {
"attachmentId": "att_60d5ec49f1b2c72d9c8b9999",
"filename": "receipt.pdf",
"contentType": "application/pdf",
"size": 20480,
"downloadUrl": "https://s3.amazonaws.com/autosend-inbound/attachments/60d5ec49f1b2c72d9c8b1234/att_60d5ec49f1b2c72d9c8b9999.pdf?X-Amz-Signature=...",
"expiresIn": 900
}
}
```
```text 200 Response (?download=true) theme={null}
Content-Type: application/pdf
Content-Disposition: attachment; filename="receipt.pdf"
```
***
#### Authorizations
Project API key header of the form Bearer `as_`. You can also pass the key via the `x-api-key` header.
### Path Parameters
The unique identifier of the inbound message. The message must belong to the authenticated project.
Example: `"60d5ec49f1b2c72d9c8b1234"`
Identifies the attachment. Accepts either the attachment's `attachmentId` (recommended, stable across requests, e.g. `att_60d5ec49f1b2c72d9c8b9999`) or a zero-based index into the message's `attachments` array (back-compat, e.g. `0`).
Example: `att_60d5ec49f1b2c72d9c8b9999`
### Query Parameters
Controls the response format.
* `false` (default): returns JSON metadata with a short-lived, pre-signed `downloadUrl`.
* `true`: streams the raw attachment bytes with `Content-Type` and `Content-Disposition` headers.
#### Response
Attachment metadata retrieved successfully. With `?download=true`, the body is instead the raw binary content and the `Content-Type` and `Content-Disposition` response headers describe the file.
Indicates if the request was successful
Example: `true`
Attachment metadata and download URL
Stable identifier for this attachment. Use it as the `{idx}` path parameter for repeatable references.
Original file name of the attachment.
MIME type of the attachment (e.g. `application/pdf`).
Size of the attachment in bytes.
Pre-signed URL to download the file. It requires no authentication header and downloads with the correct filename. Treat it as short-lived and single-use; request a fresh one once it expires.
Number of seconds the `downloadUrl` stays valid. Currently `900` (15 minutes).
#### Error Responses
Returned when `id` is not valid or `idx` is neither a non-negative integer nor a valid `attachmentId`.
```json theme={null}
{
"success": false,
"error": {
"message": "Invalid value",
}
}
```
Returned when the message does not exist or does not belong to the authenticated project.
```json theme={null}
{
"success": false,
"error": {
"message": "Inbound email not found"
}
}
```
Returned when no attachment matches the given `attachmentId` or index.
```json theme={null}
{
"success": false,
"error": {
"message": "Attachment not found"
}
}
```
Returned when the message's raw MIME source is not yet stored (the message may still be processing), so the attachment cannot be produced.
```json theme={null}
{
"success": false,
"error": {
"message": "Raw MIME is not yet available for this message"
}
}
```
Returned when an unexpected error occurs while producing the download URL or streaming the attachment.
```json theme={null}
{
"success": false,
"error": {
"message": "Failed to download attachment. Please try again."
}
}
```
# Get Message
Source: https://docs.autosend.com/api-reference/inbound-emails/get-message
GET /inbound/messages/{id}
Retrieves a single inbound email message by its ID, including full text/HTML bodies, headers, attachments metadata, threading information, and verdicts.
This endpoint uses a standard **project API key** (`AS_` prefix). The message must belong to the project the key is scoped to.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer as_your-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234"
headers = {
"Authorization": "Bearer as_your-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234', {
method: 'GET',
headers: {
'Authorization': 'Bearer as_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer as_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetMessage {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer as_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer as_your-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"messageId": "",
"domainName": "support.example.com",
"from": {
"email": "customer@gmail.com",
"name": "Jane Customer"
},
"to": [
{
"email": "help@support.example.com",
"name": null
}
],
"cc": [],
"bcc": [],
"replyTo": [],
"subject": "Question about my order",
"text": "Hi, I wanted to check on the status of order #4821.",
"html": "Hi, I wanted to check on the status of order #4821.
",
"attachments": [
{
"attachmentId": "att_60d5ec49f1b2c72d9c8b9999",
"filename": "receipt.pdf",
"contentType": "application/pdf",
"size": 20480,
"index": 0
}
],
"headers": {
"from": "Jane Customer ",
"to": "help@support.example.com",
"subject": "Question about my order"
},
"spamVerdict": "PASS",
"virusVerdict": "PASS",
"spfVerdict": "PASS",
"dkimVerdict": "PASS",
"dmarcVerdict": "PASS",
"status": "PROCESSED",
"threadId": "60d5ec49f1b2c72d9c8b1234",
"inReplyTo": null,
"inReplyToEmailActivityId": null,
"inReplyToInboundEmailId": null,
"receivedAt": "2026-06-20T10:15:30.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `as_`. You can also pass the key via the `x-api-key` header.
### Path Parameters
The unique identifier of the inbound message. The message must belong to the authenticated project.
Example: `"60d5ec49f1b2c72d9c8b1234"`
#### Response
Message retrieved successfully
Indicates if the request was successful
Example: `true`
The full inbound message object
Unique inbound message identifier (MongoDB ID). This is the same value sent as `inboundEmailId` in the `email.received` webhook event - use it as the `{id}` path parameter to fetch this message.
RFC 5322 `Message-ID` header from the original email
Domain the message was received on
Sender address (`email`, `name`)
Recipient addresses (`email`, `name`)
CC addresses (`email`, `name`)
BCC addresses (`email`, `name`)
Reply-To addresses (`email`, `name`)
Message subject
Plain-text body (truncated; raw MIME in storage is the source of truth)
HTML body (truncated; raw MIME in storage is the source of truth)
Attachment metadata. Each entry includes `attachmentId`, `filename`, `contentType`, `size`, and a zero-based `index` used to download the attachment.
Map of raw email headers (header name → string value)
Spam verdict (e.g. `PASS`, `FAIL`)
Virus verdict
SPF verdict
DKIM verdict
DMARC verdict
Message status: `PROCESSED`, `PROCESSING`, `FAILED`, `BLOCKED_UNVERIFIED`, or `BLOCKED_UNROUTED`
Identifier of the conversation thread this message belongs to
Raw `In-Reply-To` header value from the original email
ID of the outbound email this message is a reply to, if matched
ID of a previously received inbound message this message is a reply to, if matched
Timestamp the message was received (ISO 8601)
#### Error Responses
Returned when the `id` parameter is not valid.
```json theme={null}
{
"success": false,
"error": {
"message": "Invalid value",
}
}
```
Returned when the message does not exist or does not belong to the authenticated project.
```json theme={null}
{
"success": false,
"error": {
"message": "Inbound email not found"
}
}
```
# List Messages
Source: https://docs.autosend.com/api-reference/inbound-emails/list-messages
GET /inbound/messages
Retrieves inbound email messages received on the authenticated project's inbound-enabled domains. Supports filtering by domain, sender, recipient, thread, subject search, and date range, with pagination. Blocked/unrouted messages are excluded unless includeBlocked is set.
This endpoint uses a standard **project API key** (`AS_` prefix). Messages are scoped to the project the key belongs to. Blocked and unrouted messages are excluded unless `includeBlocked=true` is set.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/inbound/messages?page=1&limit=50' \
--header 'Authorization: Bearer as_your-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/inbound/messages"
headers = {
"Authorization": "Bearer as_your-api-key"
}
params = {
"page": 1,
"limit": 50
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({ page: '1', limit: '50' });
fetch(`https://api.autosend.com/v1/inbound/messages?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer as_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/inbound/messages?page=1&limit=50"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer as_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListMessages {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/inbound/messages?page=1&limit=50");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer as_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/inbound/messages?page=1&limit=50')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer as_your-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"items": [
{
"id": "60d5ec49f1b2c72d9c8b1234",
"messageId": "",
"domainName": "support.example.com",
"from": {
"email": "customer@gmail.com",
"name": "Jane Customer"
},
"to": [
{
"email": "help@support.example.com",
"name": null
}
],
"cc": [],
"subject": "Question about my order",
"status": "PROCESSED",
"attachmentCount": 0,
"receivedAt": "2026-06-20T10:15:30.000Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 1,
"pages": 1
}
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `as_`. You can also pass the key via the `x-api-key` header.
### Query Parameters
Filter by sender email address (exact match, case-insensitive).
Example: `"customer@gmail.com"`
Filter by recipient email address (exact match, case-insensitive).
Example: `"help@support.example.com"`
Filter to messages belonging to a specific conversation thread .
Example: `"60d5ec49f1b2c72d9c8b1234"`
Case-insensitive substring search against the message subject.
Maximum length: `200`
Return only messages created on or after this timestamp (ISO 8601).
Example: `"2026-06-01T00:00:00.000Z"`
Return only messages created on or before this timestamp (ISO 8601).
Example: `"2026-06-30T23:59:59.000Z"`
Page number (1-based). Defaults to `1`.
Minimum: `1`
Number of messages per page. Defaults to `50`.
Minimum: `1`
Maximum: `200`
#### Response
Messages retrieved successfully
Indicates if the request was successful
Example: `true`
Array of inbound message summaries, newest first
Unique inbound message identifier
RFC 5322 `Message-ID` header from the original email
Domain the message was received on
Sender address (`email`, `name`)
Recipient addresses (`email`, `name`)
CC addresses (`email`, `name`)
Message subject
Message status: `PROCESSED`, `PROCESSING`, `FAILED`, `BLOCKED_UNVERIFIED`, or `BLOCKED_UNROUTED`
Number of attachments on the message
Timestamp the message was received (ISO 8601)
Pagination metadata
Current page number
Page size used for this response
Total number of matching messages
Total number of pages
#### Error Responses
Returned when a query parameter fails validation (e.g. a malformed `domainId`, invalid email, or out-of-range `limit`).
```json theme={null}
{
"success": false,
"error": {
"message": "Invalid value",
"path": "limit"
}
}
```
Returned when the API key is missing or invalid.
```json theme={null}
{
"success": false,
"error": {
"message": "Invalid or missing API key"
}
}
```
# Reply to Message
Source: https://docs.autosend.com/api-reference/inbound-emails/reply-to-message
POST /inbound/messages/{id}/reply
Sends a reply to an inbound email message. The reply is threaded into the original conversation via In-Reply-To and References headers, and queued through the standard sending pipeline. The from domain must be a verified sending domain on the project.
This endpoint uses a standard **project API key** (`AS_` prefix). The reply is threaded into the original conversation (via `In-Reply-To` and `References`) and queued through the standard sending pipeline. The response returns `202 Accepted`.
The `from.email` domain must be a **verified sending domain** on the project.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply \
--header 'Authorization: Bearer as_your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"from": { "email": "help@support.example.com", "name": "Support Team" },
"subject": "Re: Question about my order",
"html": "Hi Jane, your order #4821 shipped today.
",
"text": "Hi Jane, your order #4821 shipped today."
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply"
headers = {
"Authorization": "Bearer as_your-api-key",
"Content-Type": "application/json"
}
payload = {
"from": {"email": "help@support.example.com", "name": "Support Team"},
"subject": "Re: Question about my order",
"html": "Hi Jane, your order #4821 shipped today.
",
"text": "Hi Jane, your order #4821 shipped today."
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply', {
method: 'POST',
headers: {
'Authorization': 'Bearer as_your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: { email: 'help@support.example.com', name: 'Support Team' },
subject: 'Re: Question about my order',
html: 'Hi Jane, your order #4821 shipped today.
',
text: 'Hi Jane, your order #4821 shipped today.'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
['email' => 'help@support.example.com', 'name' => 'Support Team'],
'subject' => 'Re: Question about my order',
'html' => 'Hi Jane, your order #4821 shipped today.
',
'text' => 'Hi Jane, your order #4821 shipped today.'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer as_your-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply"
payload := map[string]interface{}{
"from": map[string]string{"email": "help@support.example.com", "name": "Support Team"},
"subject": "Re: Question about my order",
"html": "Hi Jane, your order #4821 shipped today.
",
"text": "Hi Jane, your order #4821 shipped today.",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer as_your-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class ReplyToMessage {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer as_your-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"from\": { \"email\": \"help@support.example.com\", \"name\": \"Support Team\" },\n" +
" \"subject\": \"Re: Question about my order\",\n" +
" \"html\": \"Hi Jane, your order #4821 shipped today.
\",\n" +
" \"text\": \"Hi Jane, your order #4821 shipped today.\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/inbound/messages/60d5ec49f1b2c72d9c8b1234/reply')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer as_your-api-key'
request['Content-Type'] = 'application/json'
request.body = {
from: { email: 'help@support.example.com', name: 'Support Team' },
subject: 'Re: Question about my order',
html: 'Hi Jane, your order #4821 shipped today.
',
text: 'Hi Jane, your order #4821 shipped today.'
}.to_json
response = http.request(request)
puts response.body
```
```json 202 Response theme={null}
{
"success": true,
"message": "Reply queued",
"data": {
"emailId": "0102018f-0000-0000-0000-000000000000",
"message": "Email queued successfully.",
"status": "QUEUED",
"totalRecipients": 1
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `as_`. You can also pass the key via the `x-api-key` header.
### Path Parameters
The unique identifier of the inbound message to reply to. The message must belong to the authenticated project.
Example: `"60d5ec49f1b2c72d9c8b1234"`
### Body
The sender address for the reply. The email's domain must be a verified sending domain on the project.
Sender email address.
Example: `"help@support.example.com"`
Sender display name. Falls back to the project's sender name when omitted.
Maximum length: `256`
Example: `"Support Team"`
Subject line for the reply. When omitted, defaults to the original subject prefixed with `Re:` (no double prefixing).
Minimum length: `1`
Maximum length: `998`
Example: `"Re: Question about my order"`
HTML body of the reply.
Plain-text body of the reply.
CC recipients. Each entry has a required `email` and optional `name`.
Maximum items: `50`
BCC recipients. Each entry has a required `email` and optional `name`.
Maximum items: `50`
Attachments to include on the reply. Provide each attachment either inline (base64 `content`) or by `fileUrl`.
Maximum items: `20`
Attachment file name.
Minimum length: `1`
Maximum length: `256`
Public URL to fetch the attachment content from (alternative to `content`).
Base64-encoded attachment content (alternative to `fileUrl`).
MIME type of the attachment (e.g. `application/pdf`).
Size of the attachment in bytes.
Minimum: `1`
Content-ID for inline attachments referenced from the HTML body.
Maximum length: `128`
Optional description of the attachment.
Maximum length: `256`
#### Response
Reply queued successfully (202)
Indicates if the request was successful
Example: `true`
Confirmation message
Example: `"Reply queued"`
Result of queuing the reply through the sending pipeline.
Unique identifier of the queued outbound email (email activity ID).
Queue confirmation message.
Example: `"Email queued successfully."`
Initial status of the queued email. Always `QUEUED` on success.
Total number of recipients the reply was queued for (TO + CC + BCC).
#### Error Responses
Returned when `from.email` is missing.
```json theme={null}
{
"success": false,
"error": {
"message": "A `from` address is required to reply"
}
}
```
Returned when the `from` domain is not a verified sending domain on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Reply `from` address must use the same domain that received the message"
}
}
```
Returned when the inbound message does not exist or does not belong to the authenticated project, or when it has no usable reply target address.
```json theme={null}
{
"success": false,
"error": {
"message": "Inbound email not found"
}
}
```
# API Reference
Source: https://docs.autosend.com/api-reference/introduction
Send transactional emails programmatically using the AutoSend REST API. This guide covers details of all APIs, request/response formats, and best practices.
## Base URL
All API requests should be made to:
```
https://api.autosend.com
```
## Authentication
All API requests require authentication using an API key. Include your API Key in the `Authorization` header:
```bash theme={null}
Authorization: Bearer AS_xxxxxx
```
## HTTP Status Codes
| Code | Meaning | Description |
| ----- | --------------------- | ------------------------------------ |
| `200` | OK | Request succeeded |
| `400` | Bad Request | Invalid request parameters |
| `401` | Unauthorized | Missing or invalid API key |
| `402` | Payment Required | Payment required |
| `403` | Forbidden | API key doesn't have required access |
| `404` | Not Found | Resource doesn't exist |
| `429` | Too Many Requests | Rate limit exceeded |
| `500` | Internal Server Error | Something went wrong on our end |
## Need Help?
If you're experiencing issues with the API, please contact support .
# Send Bulk Email
Source: https://docs.autosend.com/api-reference/mails/bulk
POST /mails/bulk
Sends the same email to multiple recipients in a single API request. This endpoint is identical to the send email endpoint, with the only difference being that `recipients` is an array of recipients instead of a single recipient. Maximum limit: 100 recipients per request.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/mails/bulk \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"recipients": [
{
"email": "customer1@example.com",
"name": "Jane Smith",
"dynamicData": {
"firstName": "Jane",
"orderNumber": "ORD-123",
"orderTotal": "$19.99"
}
},
{
"email": "customer2@example.com",
"name": "John Doe",
"dynamicData": {
"firstName": "John",
"orderNumber": "ORD-124",
"orderTotal": "$29.99"
}
}
],
"from": {
"email": "hello@mail.yourdomain.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"replyTo": {
"email": "support@yourdomain.com"
}
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/mails/bulk"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"recipients": [
{
"email": "customer1@example.com",
"name": "Jane Smith",
"dynamicData": {
"firstName": "Jane",
"orderNumber": "ORD-123",
"orderTotal": "$19.99"
}
},
{
"email": "customer2@example.com",
"name": "John Doe",
"dynamicData": {
"firstName": "John",
"orderNumber": "ORD-124",
"orderTotal": "$29.99"
}
}
],
"from": {
"email": "hello@mail.yourdomain.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"replyTo": {
"email": "support@yourdomain.com"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/mails/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipients: [
{
email: 'customer1@example.com',
name: 'Jane Smith',
dynamicData: {
firstName: 'Jane',
orderNumber: 'ORD-123',
orderTotal: '$19.99'
}
},
{
email: 'customer2@example.com',
name: 'John Doe',
dynamicData: {
firstName: 'John',
orderNumber: 'ORD-124',
orderTotal: '$29.99'
}
}
],
from: {
email: 'hello@mail.yourdomain.com',
name: 'Your Company'
},
subject: 'Welcome to Our Platform!',
html: 'Welcome, {{name}}!
Thanks for signing up.
',
replyTo: {
email: 'support@yourdomain.com'
}
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
[
[
'email' => 'customer1@example.com',
'name' => 'Jane Smith',
'dynamicData' => [
'firstName' => 'Jane',
'orderNumber' => 'ORD-123',
'orderTotal' => '$19.99'
]
],
[
'email' => 'customer2@example.com',
'name' => 'John Doe',
'dynamicData' => [
'firstName' => 'John',
'orderNumber' => 'ORD-124',
'orderTotal' => '$29.99'
]
]
],
'from' => [
'email' => 'hello@mail.yourdomain.com',
'name' => 'Your Company'
],
'subject' => 'Welcome to Our Platform!',
'html' => 'Welcome, {{name}}!
Thanks for signing up.
',
'replyTo' => [
'email' => 'support@yourdomain.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/mails/bulk"
payload := map[string]interface{}{
"recipients": []map[string]interface{}{
{
"email": "customer1@example.com",
"name": "Jane Smith",
"dynamicData": map[string]interface{}{
"firstName": "Jane",
"orderNumber": "ORD-123",
"orderTotal": "$19.99",
},
},
{
"email": "customer2@example.com",
"name": "John Doe",
"dynamicData": map[string]interface{}{
"firstName": "John",
"orderNumber": "ORD-124",
"orderTotal": "$29.99",
},
},
},
"from": map[string]string{
"email": "hello@mail.yourdomain.com",
"name": "Your Company",
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"replyTo": map[string]string{
"email": "support@yourdomain.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SendBulkEmail {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/mails/bulk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"recipients\": [\n" +
" {\n" +
" \"email\": \"customer1@example.com\",\n" +
" \"name\": \"Jane Smith\",\n" +
" \"dynamicData\": {\n" +
" \"firstName\": \"Jane\",\n" +
" \"orderNumber\": \"ORD-123\",\n" +
" \"orderTotal\": \"$19.99\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"email\": \"customer2@example.com\",\n" +
" \"name\": \"John Doe\",\n" +
" \"dynamicData\": {\n" +
" \"firstName\": \"John\",\n" +
" \"orderNumber\": \"ORD-124\",\n" +
" \"orderTotal\": \"$29.99\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"from\": {\n" +
" \"email\": \"hello@mail.yourdomain.com\",\n" +
" \"name\": \"Your Company\"\n" +
" },\n" +
" \"subject\": \"Welcome to Our Platform!\",\n" +
" \"html\": \"Welcome, {{name}}!
Thanks for signing up.
\",\n" +
" \"replyTo\": {\n" +
" \"email\": \"support@yourdomain.com\"\n" +
" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/mails/bulk')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
recipients: [
{
email: 'customer1@example.com',
name: 'Jane Smith',
dynamicData: {
firstName: 'Jane',
orderNumber: 'ORD-123',
orderTotal: '$19.99'
}
},
{
email: 'customer2@example.com',
name: 'John Doe',
dynamicData: {
firstName: 'John',
orderNumber: 'ORD-124',
orderTotal: '$29.99'
}
}
],
from: {
email: 'hello@mail.yourdomain.com',
name: 'Your Company'
},
subject: 'Welcome to Our Platform!',
html: 'Welcome, {{name}}!
Thanks for signing up.
',
replyTo: {
email: 'support@yourdomain.com'
}
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"batchId": "ae22c1e0-2022-4f6b-bdca-ce94901fbc6e",
"totalRecipients": 2,
"successCount": 2,
"failedCount": 0
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Email data to send to multiple recipients
Array of recipient email addresses and names (maximum 100 recipients)
Maximum 100 recipients per request.
Email address
Example: `"customer1@example.com"`
Display name
Example: `"Jane Smith"`
Key-value pairs for template variable substitution (Handlebars syntax)
Example:
```jsx theme={null}
{
"firstName": "Jane",
"orderNumber": "ORD-123",
"orderTotal": "$19.99"
}
```
Cc Recipients array of email address and name object
Email address
Example: `"cc@example.com"`
Cc User name
Example: `"Cc User Name"`
Bcc Recipients array of email address and name object
Email address
Example: `"bcc@example.com"`
Bcc User name
Example: `"Bcc User Name"`
Sender email address (must be from a verified domain) and name
Email address
Example: `"hello@mail.yourdomain.com"`
Display name
Example: `"Your Company"`
Key-value pairs for template variable substitution (Handlebars syntax)
Example:
```jsx theme={null}
{
"firstName": "Jane",
"orderNumber": "ORD-123",
"orderTotal": "$19.99"
}
```
Email subject line (max 998 characters). Required if not using templateId.
Maximum length: `998`
Example: `"Welcome to Our Platform!"`
HTML content of the email. Required if not using templateId.
**Handlebars Template Variables:**
Use Handlebars syntax for template variables in your HTML. Variables are wrapped in double curly braces: `{{variableName}}`.
Example:
```html theme={null}
Hello {{firstName}}!
Your order #{{orderNumber}} has been shipped.
Total: {{orderTotal}}
```
Provide the values for these variables in the `dynamicData` field (either at the root level for all recipients, or per recipient).
Example:
```jsx theme={null}
html: "Hello {{firstName}}! Sending this email via AutoSend.
"
```
Key-value pairs for template variable substitution (Handlebars syntax). Same data will be used for all recipients.
Example:
```jsx theme={null}
dynamicData: {
"name": "Valued Customer",
"firstName": "Valued",
"orderNumber": "ORD-12345",
"orderTotal": "$99.99"
}
```
Plain text version of the email
Example: `"Welcome! Thanks for signing up."`
ID of the email template to use. Required if not providing html/text.
Example: `"A-abc123"`
Reply-to email address and name
Email address
Example: `"support@yourdomain.com"`
Display name
Example: `"John Doe"`
Key-value pairs for template variable substitution (Handlebars syntax)
Example:
```jsx theme={null}
{
"name": "Jane",
"firstName": "Jane",
"orderNumber": "ORD-12345",
"orderTotal": "$99.99"
}
```
ID of the unsubscribe group
Example: `"unsub_group_123"`
Enable or disable click tracking for links in the email. When enabled, links are rewritten so clicks can be tracked. If omitted, the project-level setting configured in your AutoSend dashboard is used.
Example: `false`
Enable or disable open tracking for the email. When enabled, a tracking pixel is added to record opens. If omitted, the project-level setting configured in your AutoSend dashboard is used.
Example: `false`
Custom email headers to include with the message as key-value pairs. Applied to every recipient in the batch.
* Maximum 20 custom headers per email.
* Header names must match `^[A-Za-z0-9-]{1,76}$` (ASCII letters, digits, and hyphens, up to 76 characters).
* Header values can be up to 1000 characters.
* Reserved headers managed by AutoSend or the underlying mail transport cannot be overridden, including: `From`, `To`, `Cc`, `Bcc`, `Subject`, `Date`, `Message-ID`, `Return-Path`, `Sender`, `Reply-To`, `Received`, `DKIM-Signature`, `MIME-Version`, `Content-Type`, `Content-Transfer-Encoding`, `List-Unsubscribe`, `List-Unsubscribe-Post`, `X-SES-Configuration-Set`, and `X-SES-Message-Tags`.
Example:
```jsx theme={null}
headers: {
"X-Entity-Ref-ID": "order-12345",
"X-Campaign-ID": "welcome-series"
}
```
### Response
Bulk send completed
Indicates if the request was successful
Example: `true`
Unique identifier for the batch
Example: `"ae22c1e0-2022-4f6b-bdca-ce94901fbc6e"`
Total number of recipients
Example: `2`
Number of successfully queued emails
Example: `2`
Number of failed emails
Example: `0`
# Send Email
Source: https://docs.autosend.com/api-reference/mails/send
POST /mails/send
Sends a transactional or marketing email. Either templateId OR html/text content must be provided. If using a template, subject is optional.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/mails/send \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"to": {
"email": "to@example.com",
"name": "Jane Smith"
},
"cc": [
{"email": "cc1@example.com", "name": "CC User 1"},
{"email": "cc2@example.com", "name": "CC User 2"}
],
"bcc": [{"email": "bcc@example.com"}],
"from": {
"email": "from@yourdomain.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"dynamicData": {
"name": "Jane"
},
"replyTo": {
"email": "to@example.com"
}
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/mails/send"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"to": {
"email": "to@example.com",
"name": "Jane Smith"
},
"cc": [
{"email": "cc1@example.com", "name": "CC User 1"},
{"email": "cc2@example.com", "name": "CC User 2"}
],
"bcc": [{"email": "bcc@example.com"}],
"from": {
"email": "from@example.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"dynamicData": {
"name": "Jane"
},
"replyTo": {
"email": "to@example.com"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
Authorization: 'Bearer ',
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: {
email: 'to@example.com',
name: 'Jane Smith',
},
from: {
email: 'from@example.com',
name: 'Your Company',
},
subject: 'Welcome to Our Platform!',
html: 'Welcome, {{name}}!
Thanks for signing up.
',
dynamicData: {
name: 'Jane',
},
replyTo: {
email: 'to@example.com',
},
}),
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error('Error:', error));
```
```php PHP theme={null}
[
'email' => 'to@example.com',
'name' => 'Jane Smith'
],
'from' => [
'email' => 'from@example.com',
'name' => 'Your Company'
],
'subject' => 'Welcome to Our Platform!',
'html' => 'Welcome, {{name}}!
Thanks for signing up.
',
'dynamicData' => [
'name' => 'Jane'
],
'replyTo' => [
'email' => 'to@example.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/mails/send"
payload := map[string]interface{}{
"to": map[string]string{
"email": "to@example.com",
"name": "Jane Smith",
},
"from": map[string]string{
"email": "from@example.com",
"name": "Your Company",
},
"subject": "Welcome to Our Platform!",
"html": "Welcome, {{name}}!
Thanks for signing up.
",
"dynamicData": map[string]string{
"name": "Jane",
},
"replyTo": map[string]string{
"email": "to@example.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SendEmail {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/mails/send");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"to\": {\n" +
" \"email\": \"to@example.com\",\n" +
" \"name\": \"Jane Smith\"\n" +
" },\n" +
" \"from\": {\n" +
" \"email\": \"from@example.com\",\n" +
" \"name\": \"Your Company\"\n" +
" },\n" +
" \"subject\": \"Welcome to Our Platform!\",\n" +
" \"html\": \"Welcome, {{name}}!
Thanks for signing up.
\",\n" +
" \"dynamicData\": {\n" +
" \"name\": \"Jane\"\n" +
" },\n" +
" \"replyTo\": {\n" +
" \"email\": \"to@example.com\"\n" +
" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/mails/send')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
to: {
email: 'to@example.com',
name: 'Jane Smith'
},
from: {
email: 'from@example.com',
name: 'Your Company'
},
subject: 'Welcome to Our Platform!',
html: 'Welcome, {{name}}!
Thanks for signing up.
',
dynamicData: {
name: 'Jane'
},
replyTo: {
email: 'to@example.com'
}
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": "Email queued successfully.",
"totalRecipients": 1
}
}
```
***
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Email data to send
Recipient email address and name
Email address
Example: `"to@example.com"`
Display name
Example: `"Jane Smith"`
To send to multiple recipients, use the send bulk email
endpoint.
Cc Recipients array of email address and name object
Email address
Example: `"cc@example.com"`
Cc User name
Example: `"Cc User Name"`
Bcc Recipients array of email address and name object
Email address
Example: `"bcc@example.com"`
Bcc User name
Example: `"Bcc User Name"`
The total combined recipients across `to`, `cc`, and `bcc` cannot exceed 50 per email.
Sender email address (must be from a verified domain) and name
Email address
Example: `"from@example.com"`
Display name
Example: `"Your Company"`
Email subject line (max 998 characters). Required if not using templateId. Maximum length: `998`
Example: `"Welcome to Our Platform!"`
HTML content of the email. Required if not using templateId. **Handlebars Template Variables:**
Use Handlebars syntax for template variables in your HTML. Variables are wrapped in double curly
braces: `{{ variableName }}`. Example: `html Hello {{ firstName }}!
Your order #{{ orderNumber }} has been shipped.
Total: {{ orderTotal }}
` Provide the values for these variables in the `dynamicData` field. Example: `jsx html: " Hello {{ firstName }}! Sending this email via AutoSend.
" `
Key-value pairs for template variable substitution (Handlebars syntax)
Example:
```jsx theme={null}
dynamicData: {
"name": "Jane",
"firstName": "Jane",
"orderNumber": "ORD-12345",
"orderTotal": "$99.99"
}
```
Plain text version of the email Example: `"Welcome! Thanks for signing up."`
ID of the email template to use. Required if not providing html/text. Example: `"A-abc123"`
Reply-to email address and name
Email address
Example: `"customer@example.com"`
Display name
Example: `"John Doe"`
Key-value pairs for template variable substitution (Handlebars syntax)
Example:
```jsx theme={null}
{
"name": "Jane",
"firstName": "Jane",
"orderNumber": "ORD-12345",
"orderTotal": "$99.99"
}
```
ID of the unsubscribe group Example: `"unsub_group_123"`
Enable or disable click tracking for links in the email. When enabled, links are rewritten so clicks can be tracked. If omitted, the project-level setting configured in your AutoSend dashboard is used.
Example: `false`
Enable or disable open tracking for the email. When enabled, a tracking pixel is added to record opens. If omitted, the project-level setting configured in your AutoSend dashboard is used.
Example: `false`
Custom email headers to include with the message as key-value pairs.
* Maximum 20 custom headers per email.
* Header names must match `^[A-Za-z0-9-]{1,76}$` (ASCII letters, digits, and hyphens, up to 76 characters).
* Header values can be up to 1000 characters.
* Reserved headers managed by AutoSend or the underlying mail transport cannot be overridden, including: `From`, `To`, `Cc`, `Bcc`, `Subject`, `Date`, `Message-ID`, `Return-Path`, `Sender`, `Reply-To`, `Received`, `DKIM-Signature`, `MIME-Version`, `Content-Type`, `Content-Transfer-Encoding`, `List-Unsubscribe`, `List-Unsubscribe-Post`, `X-SES-Configuration-Set`, and `X-SES-Message-Tags`.
Example:
```jsx theme={null}
headers: {
"X-Entity-Ref-ID": "order-12345",
"X-Campaign-ID": "welcome-series"
}
```
Filename and content of attachments.
Maximum 20 files can be attached to an email. The total size of the email should be max 40MB after Base64 encoding of the attachments.
Filename of the attachment
Example: `"attachment.pdf"`
Base64-encoded content of the attachment
Content type of the attachment (e.g. application/pdf, image/png, image/jpeg)
File URL where the attachment is hosted (required if content is not provided)
Description of the attachment (optional)
`.adp` `.app` `.asp` `.bas` `.bat`
`.cer` `.chm` `.cmd` `.com` `.cpl`
`.crt` `.csh` `.der` `.exe` `.fxp`
`.gadget` `.hlp` `.hta` `.inf` `.ins`
`.isp` `.its` `.js` `.jse` `.ksh`
`.lib` `.lnk` `.mad` `.maf` `.mag`
`.mam` `.maq` `.mar` `.mas` `.mat`
`.mau` `.mav` `.maw` `.mda` `.mdb`
`.mde` `.mdt` `.mdw` `.mdz` `.msc`
`.msh` `.msh1` `.msh2` `.mshxml` `.msh1xml`
`.msh2xml` `.msi` `.msp` `.mst` `.ops`
`.pcd` `.pif` `.plg` `.prf` `.prg`
`.reg` `.scf` `.scr` `.sct` `.shb`
`.shs` `.sys` `.ps1` `.ps1xml` `.ps2`
`.ps2xml` `.psc1` `.psc2` `.tmp` `.url`
`.vb` `.vbe` `.vbs` `.vps` `.vsmacros`
`.vss` `.vst` `.vsw` `.vxd` `.ws`
`.wsc` `.wsf` `.wsh` `.xnk`
Example:
```jsx theme={null}
attachments: [
{
"fileName": "attachment.pdf",
"content": "base64-encoded-content",
"contentType": "application/pdf"
}
]
```
When set to `true`, the email is delivered even if the recipient has unsubscribed from all groups or is on your suppression list. Reserve this for critical transactional emails that a recipient must receive regardless of their marketing preferences, such as one-time passwords, security alerts, and account verifications.
Use this flag sparingly and only for genuinely essential messages. Sending to suppressed or bounced addresses drives up your bounce rate, and a high bounce rate can place your account under review or affect your sending reputation.
### Response
Email queued successfully
Indicates if the request was successful Example: `true`
The ID of the queued email Example: `"698afb75ff4bc5466e3a797a"`
Success message indicating the email was queued Example: `"Email queued successfully."`
Total number of recipients for the email Example: `1`
# Create Project
Source: https://docs.autosend.com/api-reference/projects/create-project
POST /account/projects
Creates a new project under the organization. The number of projects is limited by the organization's plan. Requires an organization admin API key (ASA_ prefix).
This endpoint requires an **organization admin API key** (`ASA_` prefix). Standard project API keys cannot access this endpoint.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/account/projects \
--header 'Authorization: Bearer ASA_your-admin-api-key' \
--header 'Content-Type: application/json' \
--data '{
"name": "My New Project",
"domain": "example.com",
"regionKey": "us-east-1"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/account/projects"
headers = {
"Authorization": "Bearer ASA_your-admin-api-key",
"Content-Type": "application/json"
}
payload = {
"name": "My New Project",
"domain": "example.com",
"regionKey": "us-east-1"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/account/projects', {
method: 'POST',
headers: {
'Authorization': 'Bearer ASA_your-admin-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'My New Project',
domain: 'example.com',
regionKey: 'us-east-1'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'My New Project',
'domain' => 'example.com',
'regionKey' => 'us-east-1'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ASA_your-admin-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/account/projects"
payload := map[string]interface{}{
"name": "My New Project",
"domain": "example.com",
"regionKey": "us-east-1",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ASA_your-admin-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateProject {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/account/projects");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ASA_your-admin-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"name\": \"My New Project\",\n" +
" \"domain\": \"example.com\",\n" +
" \"regionKey\": \"us-east-1\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/account/projects')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer ASA_your-admin-api-key'
request['Content-Type'] = 'application/json'
request.body = {
name: 'My New Project',
domain: 'example.com',
regionKey: 'us-east-1'
}.to_json
response = http.request(request)
puts response.body
```
```json 201 Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"name": "My New Project",
"domain": "example.com",
"domains": [],
"regionKey": "us-east-1",
"trackingOpen": false,
"trackingClick": false
}
}
```
***
#### Authorizations
Organization admin API key header of the form Bearer `ASA_`. Standard project API keys (`AS_` prefix) will receive a `403` error.
### Body
Name of the project (max 100 characters).
Maximum length: `100`
Example: `"My New Project"`
Domain name through which you plan to send emails, without the `https://` prefix. Accepts a root domain (e.g., `example.com`) or a subdomain (e.g., `mail.example.com`).
Example: `"example.com"`
Account region where the project data will be stored.
Allowed values: `us-east-1`, `us-east-2`, `ap-south-1`, `eu-central-1`
Example: `"us-east-1"`
#### Response
Project created successfully (201)
Indicates if the request was successful
Example: `true`
The created project object
Unique project identifier
Example: `"60d5ec49f1b2c72d9c8b1234"`
Project name
Example: `"My New Project"`
Primary domain set for the project, or `null` if none was provided
List of verified email domains (empty for new projects)
Account region for the project. One of `us-east-1`, `us-east-2`, `ap-south-1`, or `eu-central-1`
Whether open tracking is enabled
Example: `false`
Whether click tracking is enabled
Example: `false`
#### Error Responses
Returned when using a standard project API key instead of an organization admin API key.
```json theme={null}
{
"success": false,
"error": {
"message": "This endpoint requires an organization admin API key (ASA_ prefix)",
}
}
```
Returned when the organization has reached its plan's project limit.
```json theme={null}
{
"success": false,
"error": {
"message": "Plan upgrade required"
}
}
```
# Delete Project
Source: https://docs.autosend.com/api-reference/projects/delete-project
DELETE /account/projects/{projectId}
Deletes a project by its ID. The project must belong to the organization. Requires an organization admin API key (ASA_ prefix). No OTP verification is required for admin API key callers.
This endpoint requires an **organization admin API key** (`ASA_` prefix). Standard project API keys cannot access this endpoint.
This action deletes the project and all associated resources (domains, senders, templates, campaigns, contacts). This cannot be undone.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer ASA_your-admin-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234"
headers = {
"Authorization": "Bearer ASA_your-admin-api-key"
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer ASA_your-admin-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ASA_your-admin-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteProject {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ASA_your-admin-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer ASA_your-admin-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Project deleted successfully"
}
```
***
#### Authorizations
Organization admin API key header of the form Bearer `ASA_`. Standard project API keys (`AS_` prefix) will receive a `403` error.
### Path Parameters
The unique identifier of the project to delete. The project must belong to the authenticated organization.
Example: `"60d5ec49f1b2c72d9c8b1234"`
#### Response
Project deleted successfully
Indicates if the request was successful
Example: `true`
Confirmation message
Example: `"Project deleted successfully"`
#### Error Responses
Returned when using a standard project API key instead of an organization admin API key.
```json theme={null}
{
"success": false,
"error":{
"message":"This endpoint requires an organization admin API key (ASA_ prefix)"
}
}
```
Returned when the `projectId` parameter is not valid.
```json theme={null}
{
"success": false,
"error": [
{
"message": "Invalid project ID"
}
]
}
```
Returned when the project does not exist or does not belong to the organization.
```json theme={null}
{
"success": false,
"error": {
"message": "Project not found"
}
}
```
Returned when attempting to delete the only remaining project in the organization.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot delete the last project in the organization. At least one project must exist.",
"code": "LAST_PROJECT_CANNOT_BE_DELETED",
"status": 400
}
}
```
# Get Project
Source: https://docs.autosend.com/api-reference/projects/get-project
GET /account/projects/{projectId}
Retrieves a single project by its ID. The project must belong to the organization. Requires an organization admin API key (ASA_ prefix).
This endpoint requires an **organization admin API key** (`ASA_` prefix). Standard project API keys cannot access this endpoint.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer ASA_your-admin-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234"
headers = {
"Authorization": "Bearer ASA_your-admin-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234', {
method: 'GET',
headers: {
'Authorization': 'Bearer ASA_your-admin-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ASA_your-admin-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetProject {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ASA_your-admin-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/account/projects/60d5ec49f1b2c72d9c8b1234')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer ASA_your-admin-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"name": "Production App",
"domain": "example.com",
"domains": [
{
"id": "60d5ec49f1b2c72d9c8b5678",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"regionKey": "us-east-1"
}
],
"createdAt": "2024-01-15T10:30:00.000Z",
"trackingOpen": true,
"trackingClick": true,
"inboundEmailDomain": "token.autosend.email"
}
}
```
***
#### Authorizations
Organization admin API key header of the form Bearer `ASA_`. Standard project API keys (`AS_` prefix) will receive a `403` error.
#### Path Parameters
The project's unique ID. Must be a valid Mongo ObjectId.
#### Response
Project retrieved successfully
Indicates if the request was successful
Example: `true`
The project object
Unique project identifier
Example: `"60d5ec49f1b2c72d9c8b1234"`
Project name
Example: `"Production App"`
Primary domain associated with the project
Example: `"example.com"`
List of email domains configured for the project
Domain identifier
Domain name
Example: `"example.com"`
Domain verification status: `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`
Account region for the project. One of `us-east-1`, `us-east-2`, `ap-south-1`, or `eu-central-1`
ISO 8601 timestamp of when the project was created
Example: `"2024-01-15T10:30:00.000Z"`
Whether open tracking is enabled
Example: `true`
Whether click tracking is enabled
Example: `true`
The project's inbound receiving domain. Present only when inbound is configured.
Example: `"token.autosend.email"`
The response intentionally contains no secrets. It never includes API keys or credentials, SES tokens, or DKIM keys.
#### Error Responses
Returned when the supplied `projectId` is not a valid Mongo ObjectId.
```json theme={null}
{
"success": false,
"error":{
"code":"VALIDATION_ERROR",
"message":"Invalid project ID"
}
}
```
Returned when using a standard project API key instead of an organization admin API key.
```json theme={null}
{
"success": false,
"error":{
"message":"This endpoint requires an organization admin API key (ASA_ prefix)"
}
}
```
Returned when no project with that ID exists in the organization. This is also returned when the project belongs to a different organization.
```json theme={null}
{
"success": false,
"error":{
"code":"PROJECT_NOT_FOUND",
"message":"Project not found"
}
}
```
# List Projects
Source: https://docs.autosend.com/api-reference/projects/list-projects
GET /account/projects
Retrieves all projects for the authenticated organization. Requires an organization admin API key (ASA_ prefix).
This endpoint requires an **organization admin API key** (`ASA_` prefix). Standard project API keys cannot access this endpoint.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/account/projects \
--header 'Authorization: Bearer ASA_your-admin-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/account/projects"
headers = {
"Authorization": "Bearer ASA_your-admin-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/account/projects', {
method: 'GET',
headers: {
'Authorization': 'Bearer ASA_your-admin-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/account/projects"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ASA_your-admin-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListProjects {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/account/projects");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ASA_your-admin-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/account/projects')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer ASA_your-admin-api-key'
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"projects": [
{
"id": "60d5ec49f1b2c72d9c8b1234",
"name": "Production App",
"domain": "example.com",
"domains": [
{
"id": "60d5ec49f1b2c72d9c8b5678",
"domainName": "example.com",
"verificationStatus": "VERIFIED",
"regionKey": "us-east-1",
}
],
"trackingOpen": true,
"trackingClick": true
},
{
"id": "60d5ec49f1b2c72d9c8b7890",
"name": "Staging App",
"domain": null,
"domains": [],
"trackingOpen": false,
"trackingClick": false
}
]
}
}
```
***
#### Authorizations
Organization admin API key header of the form Bearer `ASA_`. Standard project API keys (`AS_` prefix) will receive a `403` error.
#### Response
Projects retrieved successfully
Indicates if the request was successful
Example: `true`
Array of project objects belonging to the organization
Unique project identifier
Example: `"60d5ec49f1b2c72d9c8b1234"`
Project name
Example: `"Production App"`
Primary domain associated with the project
Example: `"example.com"`
List of email domains configured for the project
Domain identifier
Domain name
Example: `"example.com"`
Domain verification status: `PENDING_CONFIGURATION`, `PENDING`, or `VERIFIED`
Account region for the project. One of `us-east-1`, `us-east-2`, `ap-south-1`, or `eu-central-1`
Whether open tracking is enabled
Example: `true`
Whether click tracking is enabled
Example: `true`
#### Error Responses
Returned when using a standard project API key instead of an organization admin API key.
```json theme={null}
{
"success": false,
"error":{
"message":"This endpoint requires an organization admin API key (ASA_ prefix)"
}
}
```
# Rate Limit
Source: https://docs.autosend.com/api-reference/rate-limit
Learn how AutoSend rate limits work, including sending thresholds and API limits
API keys are subject to the following rate limits:
**2 requests per second** per API key. This is a default limit and can be increased upon request.
When you exceed the rate limit, you'll receive a `429 Too Many Requests` response with a `retryAfter` field indicating how many seconds to wait.
### Rate Limit Headers
Each API response includes rate limit information:
```
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 115
X-RateLimit-Reset: 1696075200
```
### Handling Rate Limits
To prevent overwhelming the API, you should implement exponential backoff when receiving `429` errors:
```javascript theme={null}
async function sendEmailWithRetry(data, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await sendEmail(data);
return response;
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const waitTime = Math.pow(2, i) * 1000; // Exponential backoff
await new Promise((resolve) => setTimeout(resolve, waitTime));
} else {
throw error;
}
}
}
}
```
# Create Sender
Source: https://docs.autosend.com/api-reference/senders/create
POST /senders
Create a new sender identity with email address and display name using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/senders \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/senders"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/senders', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'hello@example.com',
name: 'Example Team',
replyTo: 'support@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'hello@example.com',
'name' => 'Example Team',
'replyTo' => 'support@example.com'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/senders"
payload := map[string]interface{}{
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateSender {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/senders");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"email\": \"hello@example.com\",\n" +
" \"name\": \"Example Team\",\n" +
" \"replyTo\": \"support@example.com\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/senders')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
email: 'hello@example.com',
name: 'Example Team',
replyTo: 'support@example.com'
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"sender": {
"id": "60d5ec49f1b2c72d9c8b4567",
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com"
},
"projectId": "60d5ec49f1b2c72d9c8b1234"
},
"message": "Authenticated sender added successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Sender data for creating a new authenticated sender. The email domain must match a verified domain on the project.
Email address for the sender. The domain portion must match a verified domain on the project.
Must be a valid email address.
Example: `"hello@example.com"`
Display name for the sender (max 200 characters). Shown as the "from" name in recipients' email clients.
Maximum length: `200`
Example: `"Example Team"`
Reply-to email address. When recipients reply to emails from this sender, replies go to this address.
Must be a valid email address.
Example: `"support@example.com"`
### Response
Sender created successfully
Indicates if the request was successful
Example: `true`
The created sender object
Unique sender identifier
Example: `"60d5ec49f1b2c72d9c8b4567"`
Sender email address
Example: `"hello@example.com"`
Display name for the sender
Example: `"Example Team"`
Reply-to email address
Example: `"support@example.com"`
ID of the project the sender belongs to
Example: `"60d5ec49f1b2c72d9c8b1234"`
Confirmation message
Example: `"Authenticated sender added successfully"`
#### Error Responses
Returned when the sender's email domain does not match any verified domain on the project.
```json theme={null}
{
"success": false,
"error": "A verified domain is required before adding an authenticated sender"
}
```
Returned when a sender with the same email already exists on the project.
```json theme={null}
{
"success": false,
"error": "An authenticated sender with this email already exists"
}
```
# Delete Sender
Source: https://docs.autosend.com/api-reference/senders/delete
DELETE /senders/{senderId}
Delete a sender identity by ID using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteSender {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Authenticated sender deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the sender to delete (id).
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Sender deleted successfully
Indicates if the request was successful
Example: `true`
Confirmation message
Example: `"Authenticated sender deleted successfully"`
#### Error Responses
Returned when the sender is used in templates and cannot be deleted.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot delete authenticated sender as it is used in templates",
"code": "AUTHENTICATED_SENDER_IN_USE",
"status": 400
}
}
```
Returned when no sender with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Authenticated sender not found",
"code": "AUTHENTICATED_SENDER_NOT_FOUND",
"status": 404
}
}
```
# Get Sender
Source: https://docs.autosend.com/api-reference/senders/get
GET /senders/{senderId}
Retrieve the details of a specific sender identity by ID using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567 \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetSender {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/senders/60d5ec49f1b2c72d9c8b4567')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the sender (id).
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Sender retrieved successfully
Indicates if the request was successful
Example: `true`
The sender object
Unique sender identifier
Example: `"60d5ec49f1b2c72d9c8b4567"`
Sender email address
Example: `"hello@example.com"`
Display name for the sender
Example: `"Example Team"`
Reply-to email address
Example: `"support@example.com"`
# List Senders
Source: https://docs.autosend.com/api-reference/senders/list
GET /senders
List all sender identities configured in your account using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/senders \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/senders"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/senders', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/senders"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListSenders {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/senders");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/senders')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"senders": [
{
"id": "60d5ec49f1b2c72d9c8b4567",
"email": "hello@example.com",
"name": "Example Team",
"replyTo": "support@example.com"
},
{
"id": "60d5ec49f1b2c72d9c8b4568",
"email": "noreply@example.com",
"name": "No Reply",
"replyTo": ""
}
]
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Response
Senders retrieved successfully
Indicates if the request was successful
Example: `true`
Response data object
Array of sender objects
Unique sender identifier (id)
Example: `"60d5ec49f1b2c72d9c8b4567"`
Sender email address. The domain must be verified on the project.
Example: `"hello@example.com"`
Display name for the sender
Example: `"Example Team"`
Reply-to email address. Empty string if not set.
Example: `"support@example.com"`
# Bulk Suppress Emails
Source: https://docs.autosend.com/api-reference/suppression-groups/bulk-suppress
POST /suppression-groups/emails/suppress
Add multiple email addresses to a suppression group in bulk using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/suppression-groups/emails/suppress' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"emails": ["user@example.com", "another@example.com"],
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"isGlobal": false
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.autosend.com/v1/suppression-groups/emails/suppress",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"emails": ["user@example.com", "another@example.com"],
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"isGlobal": False,
},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/emails/suppress",
{
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: ["user@example.com", "another@example.com"],
groupId: "AB12C3",
reason: "UNSUBSCRIBE",
isGlobal: false,
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
["user@example.com", "another@example.com"],
"groupId" => "AB12C3",
"reason" => "UNSUBSCRIBE",
"isGlobal" => false,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer ",
"Content-Type: application/json",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]interface{}{
"emails": []string{"user@example.com", "another@example.com"},
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"isGlobal": false,
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/suppression-groups/emails/suppress", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"emails": ["user@example.com", "another@example.com"],
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"isGlobal": false
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/emails/suppress"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI("https://api.autosend.com/v1/suppression-groups/emails/suppress")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer "
req["Content-Type"] = "application/json"
req.body = {
emails: ["user@example.com", "another@example.com"],
groupId: "AB12C3",
reason: "UNSUBSCRIBE",
isGlobal: false,
}.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"suppressed": 2
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body
An array of valid email addresses to suppress. Must contain at least one address.
The ID of the suppression group to add the addresses to. Omit to add to the default group.
The reason for suppression. Stored on each entry for reporting. One of `UNSUBSCRIBE`, `BOUNCE`, `COMPLAINT`, or `MANUAL`.
When `true`, adds the entries to the global suppression list rather than a project-specific group.
#### Response
Emails suppressed successfully
Example: `true`
Number of email addresses successfully suppressed.
Example: `2`
# Bulk Unsuppress Emails
Source: https://docs.autosend.com/api-reference/suppression-groups/bulk-unsuppress
POST /suppression-groups/emails/unsuppress
Remove multiple email addresses from a suppression group in bulk using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/suppression-groups/emails/unsuppress' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"emails": ["user@example.com"],
"groupId": "AB12C3"
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.autosend.com/v1/suppression-groups/emails/unsuppress",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"emails": ["user@example.com"],
"groupId": "AB12C3",
},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/emails/unsuppress",
{
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: ["user@example.com"],
groupId: "AB12C3",
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
["user@example.com"],
"groupId" => "AB12C3",
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer ",
"Content-Type: application/json",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]interface{}{
"emails": []string{"user@example.com"},
"groupId": "AB12C3",
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/suppression-groups/emails/unsuppress", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"emails": ["user@example.com"],
"groupId": "AB12C3"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/emails/unsuppress"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI("https://api.autosend.com/v1/suppression-groups/emails/unsuppress")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer "
req["Content-Type"] = "application/json"
req.body = {
emails: ["user@example.com"],
groupId: "AB12C3",
}.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"unsuppressed": 1
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body
An array of email addresses to remove from suppression.
Scopes the removal to a specific suppression group. When omitted, matching entries are removed from all groups.
When `true`, removes entries from the global suppression list rather than a project-specific group.
#### Response
Emails removed from suppression successfully
Example: `true`
Number of email addresses successfully removed from suppression.
Example: `1`
# Create Suppression Group
Source: https://docs.autosend.com/api-reference/suppression-groups/create
POST /suppression-groups
Create a new suppression group for managing email opt-outs by category using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/suppression-groups' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isActive": true
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.autosend.com/v1/suppression-groups",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isActive": true,
},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups",
{
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Marketing Unsubscribes",
description: "Users who opted out of marketing",
isActive: true,
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"Marketing Unsubscribes",
"description" => "Users who opted out of marketing",
"isActive" => true,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer ",
"Content-Type: application/json",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]interface{}{
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isActive": true,
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/suppression-groups", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isActive": true
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI("https://api.autosend.com/v1/suppression-groups")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer "
req["Content-Type"] = "application/json"
req.body = {
name: "Marketing Unsubscribes",
description: "Users who opted out of marketing",
isActive: true,
}.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "6a0c49ef6ae88a19f5a01cb1",
"groupId": "GEHYB",
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isGlobal": false,
"isActive": true,
"createdAt": "2026-05-19T11:30:55.935Z",
"updatedAt": "2026-05-19T11:30:55.935Z",
"suppressionCount": 0
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body
The display name of the suppression group. Must be between 1 and 200 characters.
An optional human-readable description of the suppression group.
Whether the suppression group is active. Defaults to `true` if not specified.
#### Response
Suppression group created successfully
Example: `true`
The created suppression group.
Unique identifier for the suppression group.
Short code identifier used when referencing the group in campaigns and templates.
Example: `"GEHYB"`
Display name of the suppression group.
Optional description of the suppression group.
Whether this is a global suppression group.
Whether the suppression group is active.
Number of email addresses currently suppressed in this group.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
# Delete Suppression Group
Source: https://docs.autosend.com/api-reference/suppression-groups/delete
DELETE /suppression-groups/{groupId}
Delete a suppression group by ID using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url 'https://api.autosend.com/v1/suppression-groups/AB12C3' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
response = requests.delete(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
headers={"Authorization": "Bearer "},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
{
method: "DELETE",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.autosend.com/v1/suppression-groups/AB12C3", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/AB12C3"))
.header("Authorization", "Bearer ")
.DELETE()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI("https://api.autosend.com/v1/suppression-groups/AB12C3")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer "
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Suppression group deleted successfully"
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The unique identifier of the suppression group to delete.
#### Response
Suppression group deleted successfully
Example: `true`
Confirmation message.
Example: `"Suppression group deleted successfully"`
#### Error Responses
Returned when attempting to delete the global suppression group.
```json theme={null}
{
"success": false,
"error": {
"message": "Cannot delete global suppression group",
"code": "CANNOT_DELETE_GLOBAL_GROUP",
"status": 400
}
}
```
Returned when no suppression group with the given ID exists on the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Suppression group not found",
"code": "GROUP_NOT_FOUND",
"status": 404
}
}
```
# Get Suppression Group
Source: https://docs.autosend.com/api-reference/suppression-groups/get
GET /suppression-groups/{groupId}
Retrieve the details of a specific suppression group by ID using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/suppression-groups/AB12C3' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
headers={"Authorization": "Bearer "},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
{
method: "GET",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/suppression-groups/AB12C3", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/AB12C3"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI("https://api.autosend.com/v1/suppression-groups/AB12C3")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer "
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b4567",
"groupId": "AB12C3",
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isActive": true,
"isGlobal": false,
"suppressionCount": 42,
"createdAt": "2026-01-10T08:00:00.000Z",
"updatedAt": "2026-04-15T12:30:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The unique identifier of the suppression group to retrieve.
#### Response
Suppression group retrieved successfully
Example: `true`
The suppression group.
Unique identifier for the suppression group.
Short identifier used in path parameters (e.g., `AB12C3`).
Display name of the suppression group.
Optional description of the suppression group.
Whether the suppression group is active.
Whether this is a global suppression group.
Number of email addresses suppressed in this group.
ISO 8601 timestamp when the group was created.
ISO 8601 timestamp when the group was last updated.
# List Suppression Groups
Source: https://docs.autosend.com/api-reference/suppression-groups/list
GET /suppression-groups
List all suppression groups in your account using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/suppression-groups?includeGlobal=true' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.autosend.com/v1/suppression-groups",
params={"includeGlobal": True},
headers={"Authorization": "Bearer "},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups?includeGlobal=true",
{
method: "GET",
headers: {
Authorization: "Bearer ",
},
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/suppression-groups?includeGlobal=true", nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups?includeGlobal=true"))
.header("Authorization", "Bearer ")
.GET()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
uri = URI("https://api.autosend.com/v1/suppression-groups?includeGlobal=true")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer "
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"groups": [
{
"id": "69044947cc370f8a63845236",
"groupId": "20XWO",
"name": "Marketing Unsubscribes",
"description": "Users who opted out of marketing",
"isGlobal": false,
"isActive": true,
"createdAt": "2025-10-31T05:29:43.906Z",
"updatedAt": "2026-02-27T10:16:26.095Z",
"suppressionCount": 0
},
{
"id": "69c3a0c19278fca074bebb74",
"groupId": "B43YU",
"name": "Bounced Addresses",
"description": "Hard bounces from transactional sends",
"isGlobal": true,
"isActive": true,
"createdAt": "2026-03-25T08:45:53.085Z",
"updatedAt": "2026-03-25T08:45:53.085Z",
"suppressionCount": 196
}
]
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
When `true`, returns only global suppression groups, excluding any project-specific groups.
When `true`, includes global suppression groups in the results alongside project-specific groups.
### Response
Suppression groups retrieved successfully
Example: `true`
Array of suppression groups.
Unique identifier for the suppression group.
Short code identifier used when referencing the group in campaigns and templates.
Example: `"20XWO"`
Display name of the suppression group.
Optional description of the suppression group.
Whether this is a global suppression group shared across all projects.
Whether the suppression group is active.
Number of email addresses currently suppressed in this group.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
# Search Suppression Entries
Source: https://docs.autosend.com/api-reference/suppression-groups/search-entries
POST /suppression-groups/entries
Search for specific email addresses within a suppression group using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url 'https://api.autosend.com/v1/suppression-groups/entries' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"page": 1,
"limit": 20,
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-03-01T00:00:00.000Z"
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.autosend.com/v1/suppression-groups/entries",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"page": 1,
"limit": 20,
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-03-01T00:00:00.000Z",
},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/entries",
{
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
groupId: "AB12C3",
reason: "UNSUBSCRIBE",
page: 1,
limit: 20,
startDate: "2026-01-01T00:00:00.000Z",
endDate: "2026-03-01T00:00:00.000Z",
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"AB12C3",
"reason" => "UNSUBSCRIBE",
"page" => 1,
"limit" => 20,
"startDate" => "2026-01-01T00:00:00.000Z",
"endDate" => "2026-03-01T00:00:00.000Z",
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer ",
"Content-Type: application/json",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]interface{}{
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"page": 1,
"limit": 20,
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-03-01T00:00:00.000Z",
})
req, _ := http.NewRequest("POST", "https://api.autosend.com/v1/suppression-groups/entries", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"groupId": "AB12C3",
"reason": "UNSUBSCRIBE",
"page": 1,
"limit": 20,
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-03-01T00:00:00.000Z"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/entries"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI("https://api.autosend.com/v1/suppression-groups/entries")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer "
req["Content-Type"] = "application/json"
req.body = {
groupId: "AB12C3",
reason: "UNSUBSCRIBE",
page: 1,
limit: 20,
startDate: "2026-01-01T00:00:00.000Z",
endDate: "2026-03-01T00:00:00.000Z",
}.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"entries": [
{
"id": "69b28d3f806d99a2a35e4f1d",
"email": "user@example.com",
"groupId": "AB12C3",
"reason": "BOUNCE",
"createdAt": "2026-03-12T09:54:07.697Z",
"updatedAt": "2026-03-12T09:54:07.697Z"
},
{
"id": "69aff842806d99a2a3527a5e",
"email": "another@example.com",
"groupId": "AB12C3",
"reason": "BOUNCE",
"createdAt": "2026-03-10T10:53:54.327Z",
"updatedAt": "2026-03-10T10:53:54.327Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 142,
"pages": 8
}
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Body
Filter entries to a specific suppression group ID.
Filter entries by suppression reason. One of `UNSUBSCRIBE`, `BOUNCE`, `COMPLAINT`, or `MANUAL`.
Page number for pagination. Must be `1` or greater. Defaults to `1`.
Number of results per page. Must be between `1` and `100`. Defaults to `20`.
Free-text search string matched against the email address or other entry fields.
ISO 8601 timestamp. Returns only entries created on or after this date.
ISO 8601 timestamp. Returns only entries created on or before this date.
Filter entries to a specific email address.
When `true`, returns only entries belonging to global suppression groups.
#### Response
Suppression entries retrieved successfully
Example: `true`
Array of suppression entries.
Unique identifier for the suppression entry.
The suppressed email address.
ID of the suppression group this entry belongs to.
Reason for suppression. One of `UNSUBSCRIBE`, `BOUNCE`, `COMPLAINT`, or `MANUAL`.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
Current page number.
Number of results per page.
Total number of entries matching the filter.
Total number of pages.
# Update Suppression Group
Source: https://docs.autosend.com/api-reference/suppression-groups/update
PUT /suppression-groups/{groupId}
Update a suppression group's name or description using the AutoSend API.
```bash cURL theme={null}
curl --request PUT \
--url 'https://api.autosend.com/v1/suppression-groups/AB12C3' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"name": "Marketing Unsubscribes — Updated",
"description": "Users who opted out of all marketing emails",
"isActive": true
}'
```
```python Python theme={null}
import requests
response = requests.put(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"name": "Marketing Unsubscribes — Updated",
"description": "Users who opted out of all marketing emails",
"isActive": true,
},
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
"https://api.autosend.com/v1/suppression-groups/AB12C3",
{
method: "PUT",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Marketing Unsubscribes — Updated",
description: "Users who opted out of all marketing emails",
isActive: true,
}),
}
);
const data = await response.json();
console.log(data);
```
```php PHP theme={null}
"Marketing Unsubscribes — Updated",
"description" => "Users who opted out of all marketing emails",
"isActive" => true,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer ",
"Content-Type: application/json",
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
payload, _ := json.Marshal(map[string]interface{}{
"name": "Marketing Unsubscribes — Updated",
"description": "Users who opted out of all marketing emails",
"isActive": true,
})
req, _ := http.NewRequest("PUT", "https://api.autosend.com/v1/suppression-groups/AB12C3", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.URI;
import java.net.http.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"name": "Marketing Unsubscribes — Updated",
"description": "Users who opted out of all marketing emails",
"isActive": true
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.autosend.com/v1/suppression-groups/AB12C3"))
.header("Authorization", "Bearer ")
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={null}
require "net/http"
require "uri"
require "json"
uri = URI("https://api.autosend.com/v1/suppression-groups/AB12C3")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer "
req["Content-Type"] = "application/json"
req.body = {
name: "Marketing Unsubscribes — Updated",
description: "Users who opted out of all marketing emails",
isActive: true,
}.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"id": "6a0c49ef6ae88a19f5a01cb1",
"groupId": "GEHYB",
"name": "Marketing Unsubscribes - Updated",
"description": "Users who opted out of all marketing emails",
"isGlobal": false,
"isActive": true,
"suppressionCount": 0,
"createdAt": "2026-05-19T11:30:55.935Z",
"updatedAt": "2026-05-19T11:30:55.935Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
#### Path Parameters
The unique identifier of the suppression group to update.
#### Body
Updated display name for the suppression group. Must be between 1 and 200 characters.
Updated description for the suppression group.
Whether the suppression group is active. Defaults to `true` if not specified.
#### Response
Suppression group updated successfully
Example: `true`
The updated suppression group.
Unique identifier for the suppression group.
Short code identifier used when referencing the group in campaigns and templates.
Display name of the suppression group.
Optional description of the suppression group.
Whether this is a global suppression group.
Whether the suppression group is active.
Number of email addresses currently suppressed in this group.
ISO 8601 creation timestamp.
ISO 8601 last-updated timestamp.
# Create Template
Source: https://docs.autosend.com/api-reference/templates/create
POST /templates
Create a new email template with HTML content and design settings using the AutoSend API.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/templates \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to {{companyName}}, {{firstName}}!",
"previewText": "We are glad to have you on board",
"emailTemplate": "Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
",
"templateType": "transactional",
"builderType": "code"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to {{companyName}}, {{firstName}}!",
"previewText": "We are glad to have you on board",
"emailTemplate": "Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
",
"templateType": "transactional",
"builderType": "code"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates', {
method: 'POST',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
templateName: 'Welcome Email',
description: 'Welcome email for new users',
subject: 'Welcome to {{companyName}}, {{firstName}}!',
previewText: 'We are glad to have you on board',
emailTemplate: 'Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
',
templateType: 'transactional',
builderType: 'code'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'Welcome Email',
'description' => 'Welcome email for new users',
'subject' => 'Welcome to {{companyName}}, {{firstName}}!',
'previewText' => 'We are glad to have you on board',
'emailTemplate' => 'Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
',
'templateType' => 'transactional',
'builderType' => 'code'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates"
payload := map[string]interface{}{
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to {{companyName}}, {{firstName}}!",
"previewText": "We are glad to have you on board",
"emailTemplate": "Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
",
"templateType": "transactional",
"builderType": "code",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateTemplate {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"templateName\": \"Welcome Email\",\n" +
" \"description\": \"Welcome email for new users\",\n" +
" \"subject\": \"Welcome to {{companyName}}, {{firstName}}!\",\n" +
" \"previewText\": \"We are glad to have you on board\",\n" +
" \"emailTemplate\": \"Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
\",\n" +
" \"templateType\": \"transactional\",\n" +
" \"builderType\": \"code\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
templateName: 'Welcome Email',
description: 'Welcome email for new users',
subject: 'Welcome to {{companyName}}, {{firstName}}!',
previewText: 'We are glad to have you on board',
emailTemplate: 'Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
',
templateType: 'transactional',
builderType: 'code'
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templateId": "A-abc123def456ghi789jk",
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to {{companyName}}, {{firstName}}!",
"previewText": "We are glad to have you on board",
"emailTemplate": "Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-03-17T10:30:00.000Z",
"updatedAt": "2026-03-17T10:30:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Body
Template data for creating a new email template
Name of the template (max 90 characters).
Maximum length: `90`
Example: `"Welcome Email"`
Email subject line (max 988 characters). Supports Handlebars template variables.
Maximum length: `988`
Example: `"Welcome to {{companyName}}, {{firstName}}!"`
Short description of the template (max 140 characters).
Maximum length: `140`
Example: `"Welcome email for new users"`
Email preview text / preheader (max 140 characters). This text is shown in email clients before the email is opened.
Maximum length: `140`
Example: `"We are glad to have you on board"`
HTML content of the email template. Supports Handlebars syntax for dynamic variables.
**Handlebars Template Variables:**
Use Handlebars syntax for template variables in your HTML. Variables are wrapped in double curly braces: `{{variableName}}`.
Example:
```html theme={null}
Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
```
Provide the values for these variables in the `dynamicData` field when sending emails using this template.
Type of the template.
Allowed values: `transactional`, `marketing`
Default: `"transactional"`
Example: `"transactional"`
Builder type used to create the template.
Allowed values: `code`, `visual`
Default: `"code"`
Example: `"code"`
Dynamic variables configuration for the template.
Example:
```json theme={null}
{
"firstName": "string",
"companyName": "string"
}
```
ID of the sender to associate with this template. The sender must belong to the same project.
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Template created successfully
Indicates if the request was successful
Example: `true`
The created template object
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Welcome Email"`
Template description
Example: `"Welcome email for new users"`
Email subject line
Example: `"Welcome to {{companyName}}, {{firstName}}!"`
Email preview text
Example: `"We are glad to have you on board"`
HTML content of the template
Type of template
Example: `"transactional"`
Builder type used
Example: `"code"`
ISO 8601 timestamp of creation
Example: `"2026-03-17T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-03-17T10:30:00.000Z"`
# Delete Template
Source: https://docs.autosend.com/api-reference/templates/delete
DELETE /templates/{templateId}
Delete an email template by ID using the AutoSend API.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/templates/A-abc123def456ghi789jk \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
headers = {
"Authorization": "Bearer "
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteTemplate {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates/A-abc123def456ghi789jk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer ");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
con.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"message": "Template successfully deleted."
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the template to delete.
Example: `"A-abc123def456ghi789jk"`
### Response
Template deleted successfully
Indicates if the request was successful
Example: `true`
Confirmation message
Example: `"Template successfully deleted."`
#### Error Responses
Returned when the template could not be found or the deletion failed.
```json theme={null}
{
"success": false,
"error": {
"message": "Template delete failed",
"code": "DELETE_TEMPLATE_FAILED",
"status": 400
}
}
```
# Get Template
Source: https://docs.autosend.com/api-reference/templates/get
GET /templates/{templateId}
Retrieve the details and content of a specific email template by ID using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/templates/A-abc123def456ghi789jk \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
headers = {
"Authorization": "Bearer "
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetTemplate {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates/A-abc123def456ghi789jk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templateId": "A-abc123def456ghi789jk",
"projectId": "691adaeef6892e15944d4d96",
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to {{companyName}}, {{firstName}}!",
"previewText": "We are glad to have you on board",
"emailTemplate": "Welcome, {{firstName}}!
Thanks for joining {{companyName}}.
",
"plainTextTemplate": "Welcome, {{firstName}}! Thanks for joining {{companyName}}.",
"templateType": "transactional",
"builderType": "code",
"dynamicVariables": {
"firstName": "string",
"companyName": "string"
},
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the template.
Example: `"A-abc123def456ghi789jk"`
### Response
Template retrieved successfully
Indicates if the request was successful
Example: `true`
The template object
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Welcome Email"`
Template description
Example: `"Welcome email for new users"`
Email subject line
Example: `"Welcome to {{companyName}}, {{firstName}}!"`
Email preview text
Example: `"We are glad to have you on board"`
HTML content of the template
Plain text version of the template
Type of template: `transactional` or `marketing`
Example: `"transactional"`
Builder type used: `code` or `visual`
Example: `"code"`
Dynamic variables used in the template
Sender information (if a sender is associated)
Sender ID
Sender name
Sender email address
Sender reply-to address
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
The project this template belongs to.
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
# List Templates
Source: https://docs.autosend.com/api-reference/templates/list
GET /templates
List all email templates in your account with optional pagination using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/templates?templateType=transactional&limit=10' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates"
headers = {
"Authorization": "Bearer "
}
params = {
"templateType": "transactional",
"limit": 10
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates?templateType=transactional&limit=10', {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates?templateType=transactional&limit=10"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListTemplates {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates?templateType=transactional&limit=10");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates?templateType=transactional&limit=10')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templates": [
{
"templateId": "A-abc123def456ghi789jk",
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to Our Platform!",
"previewText": "We're glad to have you on board",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
},
{
"templateId": "A-lmn012opq345rst678uv",
"templateName": "Password Reset",
"description": "Password reset notification",
"subject": "Reset Your Password",
"previewText": "Click the link to reset your password",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-01-20T08:15:00.000Z",
"updatedAt": "2026-03-01T11:20:00.000Z"
}
]
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Filter templates by type.
Allowed values: `transactional`, `marketing`
Example: `"transactional"`
Maximum number of templates to return.
Range: `1` - `100`
Example: `10`
### Response
Templates retrieved successfully
Indicates if the request was successful
Example: `true`
Array of template objects
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Welcome Email"`
Template description
Example: `"Welcome email for new users"`
Email subject line
Example: `"Welcome to Our Platform!"`
Email preview text (preheader)
Example: `"We're glad to have you on board"`
HTML content of the template
Plain text version of the template
Type of template: `transactional` or `marketing`
Example: `"transactional"`
Builder used to create the template: `code` or `visual`
Example: `"code"`
Dynamic variables used in the template
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
# Search Templates
Source: https://docs.autosend.com/api-reference/templates/search
GET /templates/search
Search for email templates by name or other criteria using the AutoSend API.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/templates/search?query=welcome&templateType=transactional&page=1&limit=10' \
--header 'Authorization: Bearer '
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates/search"
headers = {
"Authorization": "Bearer "
}
params = {
"query": "welcome",
"templateType": "transactional",
"page": 1,
"limit": 10
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
const params = new URLSearchParams({
query: 'welcome',
templateType: 'transactional',
page: '1',
limit: '10'
});
fetch(`https://api.autosend.com/v1/templates/search?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer '
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'welcome',
'templateType' => 'transactional',
'page' => 1,
'limit' => 10
]);
$url = "https://api.autosend.com/v1/templates/search?{$params}";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer '
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates/search?query=welcome&templateType=transactional&page=1&limit=10"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer ")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SearchTemplates {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates/search?query=welcome&templateType=transactional&page=1&limit=10");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer ");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates/search?query=welcome&templateType=transactional&page=1&limit=10')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer '
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templates": [
{
"templateId": "A-abc123def456ghi789jk",
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to Our Platform!",
"previewText": "We're glad to have you on board",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-20T14:45:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 1,
"pages": 1
}
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Query Parameters
Search query string. Searches across template name, subject, and template ID.
Example: `"welcome"`
Filter templates by type.
Allowed values: `transactional`, `marketing`
Example: `"transactional"`
Filter by exact template name.
Example: `"Welcome Email"`
Filter by subject line.
Example: `"Welcome"`
Filter by template ID.
Example: `"A-abc123def456ghi789jk"`
Page number for pagination (starts at 1).
Minimum: `1`
Example: `1`
Number of results per page.
Range: `1` - `100`
Example: `10`
Field to sort results by.
Default: `"createdAt"`
Example: `"createdAt"`
### Response
Templates search results
Indicates if the request was successful
Example: `true`
Array of matching template objects
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Welcome Email"`
Template description
Example: `"Welcome email for new users"`
Email subject line
Example: `"Welcome to Our Platform!"`
Email preview text
Example: `"We're glad to have you on board"`
Type of template
Example: `"transactional"`
Builder type used
Example: `"code"`
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-02-20T14:45:00.000Z"`
Pagination metadata
Current page number
Example: `1`
Results per page
Example: `10`
Total number of matching templates
Example: `1`
Total number of pages
Example: `1`
# Update Template
Source: https://docs.autosend.com/api-reference/templates/update
PUT /templates/{templateId}
Update an existing email template's content, subject, or settings using the AutoSend API.
```bash cURL theme={null}
curl --request PUT \
--url https://api.autosend.com/v1/templates/A-abc123def456ghi789jk \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"templateName": "Updated Welcome Email",
"subject": "Welcome aboard, {{firstName}}!",
"emailTemplate": "Hey {{firstName}}!
We are excited to have you at {{companyName}}.
",
"previewText": "Your journey starts here"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"templateName": "Updated Welcome Email",
"subject": "Welcome aboard, {{firstName}}!",
"emailTemplate": "Hey {{firstName}}!
We are excited to have you at {{companyName}}.
",
"previewText": "Your journey starts here"
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
templateName: 'Updated Welcome Email',
subject: 'Welcome aboard, {{firstName}}!',
emailTemplate: 'Hey {{firstName}}!
We are excited to have you at {{companyName}}.
',
previewText: 'Your journey starts here'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'Updated Welcome Email',
'subject' => 'Welcome aboard, {{firstName}}!',
'emailTemplate' => 'Hey {{firstName}}!
We are excited to have you at {{companyName}}.
',
'previewText' => 'Your journey starts here'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk"
payload := map[string]interface{}{
"templateName": "Updated Welcome Email",
"subject": "Welcome aboard, {{firstName}}!",
"emailTemplate": "Hey {{firstName}}!
We are excited to have you at {{companyName}}.
",
"previewText": "Your journey starts here",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpdateTemplate {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates/A-abc123def456ghi789jk");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PUT");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"templateName\": \"Updated Welcome Email\",\n" +
" \"subject\": \"Welcome aboard, {{firstName}}!\",\n" +
" \"emailTemplate\": \"Hey {{firstName}}!
We are excited to have you at {{companyName}}.
\",\n" +
" \"previewText\": \"Your journey starts here\"\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Put.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
templateName: 'Updated Welcome Email',
subject: 'Welcome aboard, {{firstName}}!',
emailTemplate: 'Hey {{firstName}}!
We are excited to have you at {{companyName}}.
',
previewText: 'Your journey starts here'
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templateId": "A-abc123def456ghi789jk",
"templateName": "Updated Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome aboard, {{firstName}}!",
"previewText": "Your journey starts here",
"emailTemplate": "Hey {{firstName}}!
We are excited to have you at {{companyName}}.
",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-03-17T14:45:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the template to update.
Example: `"A-abc123def456ghi789jk"`
### Body
Template fields to update. All fields are optional — only provided fields will be updated.
Name of the template (max 90 characters).
Maximum length: `90`
Example: `"Updated Welcome Email"`
Email subject line (max 988 characters). Supports Handlebars template variables.
Maximum length: `988`
Example: `"Welcome aboard, {{firstName}}!"`
Short description of the template (max 140 characters).
Maximum length: `140`
Example: `"Updated welcome email for new users"`
Email preview text / preheader (max 140 characters).
Maximum length: `140`
Example: `"Your journey starts here"`
HTML content of the email template. Supports Handlebars syntax for dynamic variables.
Example:
```html theme={null}
Hey {{firstName}}!
We are excited to have you at {{companyName}}.
```
Type of the template.
Allowed values: `transactional`, `marketing`
Example: `"transactional"`
Dynamic variables configuration for the template.
Example:
```json theme={null}
{
"firstName": "string",
"companyName": "string"
}
```
ID of the sender to associate with this template. The sender must belong to the same project.
Example: `"60d5ec49f1b2c72d9c8b4567"`
### Response
Template updated successfully
Indicates if the request was successful
Example: `true`
The updated template object
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Updated Welcome Email"`
Template description
Example: `"Welcome email for new users"`
Email subject line
Example: `"Welcome aboard, {{firstName}}!"`
Email preview text
Example: `"Your journey starts here"`
HTML content of the template
Type of template
Example: `"transactional"`
Builder type used
Example: `"code"`
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-03-17T14:45:00.000Z"`
# Update Preview Text
Source: https://docs.autosend.com/api-reference/templates/update-preview-text
PATCH /templates/{templateId}/preview-text
Update the preview text of an email template using the AutoSend API.
```bash cURL theme={null}
curl --request PATCH \
--url https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"previewText": "Don'\''t miss out on our latest updates"
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text"
headers = {
"Authorization": "Bearer ",
"Content-Type": "application/json"
}
payload = {
"previewText": "Don't miss out on our latest updates"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text', {
method: 'PATCH',
headers: {
'Authorization': 'Bearer ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
previewText: "Don't miss out on our latest updates"
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
"Don't miss out on our latest updates"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text"
payload := map[string]string{
"previewText": "Don't miss out on our latest updates",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PATCH", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpdatePreviewText {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PATCH");
con.setRequestProperty("Authorization", "Bearer ");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\"previewText\": \"Don't miss out on our latest updates\"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/templates/A-abc123def456ghi789jk/preview-text')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(uri.path)
request['Authorization'] = 'Bearer '
request['Content-Type'] = 'application/json'
request.body = {
previewText: "Don't miss out on our latest updates"
}.to_json
response = http.request(request)
puts response.body
```
```json Response theme={null}
{
"success": true,
"data": {
"templateId": "A-abc123def456ghi789jk",
"templateName": "Welcome Email",
"description": "Welcome email for new users",
"subject": "Welcome to Our Platform!",
"previewText": "Don't miss out on our latest updates",
"templateType": "transactional",
"builderType": "code",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-03-17T14:45:00.000Z"
}
}
```
### Authorizations
Bearer authentication header of the form Bearer ``, where `` is your auth token.
### Path Parameters
The unique identifier of the template.
Example: `"A-abc123def456ghi789jk"`
### Body
Email preview text / preheader (max 140 characters). This text is automatically injected into the template HTML and shown in email clients before the email is opened.
Maximum length: `140`
Example: `"Don't miss out on our latest updates"`
### Response
Preview text updated successfully
Indicates if the request was successful
Example: `true`
The updated template object
Unique template identifier
Example: `"A-abc123def456ghi789jk"`
Name of the template
Example: `"Welcome Email"`
Template description.
Email subject line.
Updated preview text
Example: `"Don't miss out on our latest updates"`
Template type: `transactional` , `marketing` or `automations`.
How the template was built: `code` or `visual`.
ISO 8601 timestamp of creation
Example: `"2026-01-15T10:30:00.000Z"`
ISO 8601 timestamp of last update
Example: `"2026-03-17T14:45:00.000Z"`
# Create Webhook
Source: https://docs.autosend.com/api-reference/webhooks/create-webhook
POST /webhooks
Creates a new webhook subscribed to one or more events. The signing secret is returned only on creation — store it securely, as it cannot be retrieved again except via the reveal endpoint.
This endpoint accepts a **project API key** (`AS_` prefix). The signing `secret` is returned **only** on creation — store it securely. You can later retrieve it via the [Reveal Webhook Secret](/webhooks/reveal-webhook-secret) endpoint.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/webhooks \
--header 'Authorization: Bearer AS_your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://example.com/webhooks/autosend",
"events": ["email.delivered", "email.bounced"],
"metadata": { "team": "growth" }
}'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/webhooks"
headers = {
"Authorization": "Bearer AS_your-api-key",
"Content-Type": "application/json"
}
payload = {
"url": "https://example.com/webhooks/autosend",
"events": ["email.delivered", "email.bounced"],
"metadata": {"team": "growth"}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://example.com/webhooks/autosend',
events: ['email.delivered', 'email.bounced']
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'https://example.com/webhooks/autosend',
'events' => ['email.delivered', 'email.bounced'],
'metadata' => ['team' => 'growth']
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/webhooks"
payload := map[string]interface{}{
"url": "https://example.com/webhooks/autosend",
"events": []string{"email.delivered", "email.bounced"},
"metadata": map[string]string{"team": "growth"},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class CreateWebhook {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/webhooks");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"url\": \"https://example.com/webhooks/autosend\",\n" +
" \"events\": [\"email.delivered\", \"email.bounced\"],\n" +
" \"metadata\": { \"team\": \"growth\" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
uri = URI('https://api.autosend.com/v1/webhooks')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-api-key'
request['Content-Type'] = 'application/json'
request.body = {
url: 'https://example.com/webhooks/autosend',
events: ['email.delivered', 'email.bounced']
}.to_json
response = http.request(request)
puts response.body
```
```json 201 Response theme={null}
{
"success": true,
"message": "Webhook created successfully",
"data": {
"webhook": {
"id": "60d5ec49f1b2c72d9c8b1234",
"organizationId": "60d5ec49f1b2c72d9c8b0000",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"url": "https://example.com/webhooks/autosend",
"secret": "whsec_8f3a1c2d4e5b6a7c8d9e0f1a2b3c4d5e",
"events": ["email.delivered", "email.bounced"],
"isActive": true,
"status": "active",
"failureCount": 0,
"lastFailedAt": null,
"lastSuccessAt": null,
"lastDeliveredAt": null,
"metadata": { "team": "growth" },
"createdAt": "2026-06-12T10:15:00.000Z",
"updatedAt": "2026-06-12T10:15:00.000Z"
}
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Body
The HTTPS (or HTTP) endpoint AutoSend will POST events to. Must include the protocol.
Example: `"https://example.com/webhooks/autosend"`
One or more event types to subscribe to. Must contain at least one valid event. See [List Available Events](/webhooks/list-available-events) for the full set.
Example: `["email.delivered", "email.bounced"]`
#### Response
Webhook created (201)
Indicates if the request was successful
Example: `true`
Wrapper containing the created webhook
Unique webhook identifier
The destination URL events are delivered to
HMAC signing secret used to verify payload signatures. **Returned only on creation.**
The subscribed event types
Whether the webhook is currently active
Delivery status. One of `active`, `inactive`, or `disabled`
Number of consecutive delivery failures. Starts at `0` on creation.
Timestamp of the most recent failed delivery (ISO 8601), or `null`
Timestamp of the most recent successful delivery (ISO 8601), or `null`
Timestamp of the most recent delivery attempt (ISO 8601), or `null`
Arbitrary key-value metadata attached to the webhook
ISO 8601 creation timestamp
ISO 8601 last-updated timestamp
#### Error Responses
Returned when the URL is invalid or no valid events are supplied.
```json theme={null}
{
"success": false,
"error": {
"message": "A valid webhook URL is required",
"code": "VALIDATION_ERROR",
}
}
```
# Delete Webhook
Source: https://docs.autosend.com/api-reference/webhooks/delete-webhook
DELETE /webhooks/{id}
Deletes a webhook by its ID. The webhook must belong to the authenticated project.
This endpoint accepts a **project API key** (`AS_` prefix). The webhook must belong to the authenticated project.
```bash cURL theme={null}
curl --request DELETE \
--url https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer AS_your-api-key'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}"
headers = {
"Authorization": "Bearer AS_your-api-key"
}
response = requests.delete(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
'Authorization': 'Bearer AS_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.net.HttpURLConnection;
import java.net.URL;
public class DeleteWebhook {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("DELETE");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"message": "Webhook deleted successfully",
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook to delete.
Example: `"60d5ec49f1b2c72d9c8b1234"`
#### Response
Webhook deleted successfully (200)
Indicates if the request was successful
Confirmation message
Example: `"Webhook deleted successfully"`
#### Error Responses
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# Get Webhook
Source: https://docs.autosend.com/api-reference/webhooks/get-webhook
GET /webhooks/{id}
Retrieves a single webhook by its ID. The webhook must belong to the authenticated project. The signing secret is not included.
This endpoint accepts a **project API key** (`AS_` prefix). The signing secret is masked — use [Reveal Webhook Secret](/webhooks/reveal-webhook-secret) to retrieve it.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer AS_your-api-key'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}"
headers = {
"Authorization": "Bearer AS_your-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetWebhook {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"organizationId": "60d5ec49f1b2c72d9c8b0000",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"url": "https://example.com/webhooks/autosend",
"secret": "***hidden***",
"events": ["email.delivered", "email.bounced"],
"isActive": true,
"status": "active",
"failureCount": 0,
"lastFailedAt": null,
"lastSuccessAt": "2026-06-12T10:00:00.000Z",
"lastDeliveredAt": "2026-06-12T10:00:00.000Z",
"metadata": { "team": "growth" },
"createdAt": "2026-06-01T09:00:00.000Z",
"updatedAt": "2026-06-12T10:15:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook.
Example: `"60d5ec49f1b2c72d9c8b1234"`
#### Response
Webhook retrieved successfully (200)
Indicates if the request was successful
The webhook object. The signing secret is masked as `***hidden***`.
Unique webhook identifier
Organization the webhook belongs to
Project the webhook is scoped to
The destination URL events are delivered to
HMAC signing secret, always masked as `***hidden***` in this endpoint. Use [Reveal Webhook Secret](/webhooks/reveal-webhook-secret) to retrieve the actual value.
The subscribed event types
Whether the webhook is currently active
Delivery status. One of `active`, `inactive`, or `disabled` (disabled due to too many failures)
Number of consecutive delivery failures
Timestamp of the most recent failed delivery (ISO 8601), or `null`
Timestamp of the most recent successful delivery (ISO 8601), or `null`
Timestamp of the most recent delivery attempt (ISO 8601), or `null`
Arbitrary key-value metadata attached to the webhook
ISO 8601 creation timestamp
ISO 8601 last-updated timestamp
#### Error Responses
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# List Delivery Logs
Source: https://docs.autosend.com/api-reference/webhooks/list-delivery-logs
GET /webhooks/{id}/logs
Retrieves the delivery attempt history for a webhook, including response status and timestamps. Supports pagination.
This endpoint accepts a **project API key** (`AS_` prefix). Returns the delivery attempt history for a webhook, most recent first.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234/logs?page=1&limit=50' \
--header 'Authorization: Bearer AS_your-api-key'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}/logs"
headers = {
"Authorization": "Bearer AS_your-api-key"
}
params = {"page": 1, "limit": 50}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}/logs?page=1&limit=50`, {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID + "/logs?page=1&limit=50"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListDeliveryLogs {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId + "/logs?page=1&limit=50");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}/logs?page=1&limit=50")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"data": {
"logs": [
{
"_id": "60d5ec49f1b2c72d9c8b9999",
"webhookId": "60d5ec49f1b2c72d9c8b1234",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"event": "email.delivered",
"url": "https://example.com/webhooks/autosend",
"statusCode": 200,
"success": true,
"attempts": 1,
"duration": 142,
"deliveredAt": "2026-06-12T10:15:00.000Z",
"createdAt": "2026-06-12T10:15:00.000Z",
"updatedAt": "2026-06-12T10:15:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 50,
"totalPages": 1
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook.
Example: `"60d5ec49f1b2c72d9c8b1234"`
### Query Parameters
Page number for pagination.
Default: `1`
Number of logs to return per page.
Default: `50`
Range: `1`–`100`
#### Response
Delivery logs retrieved successfully (200)
Indicates if the request was successful
Paginated delivery logs
Array of delivery attempt records
The event type that was delivered
The destination URL the payload was POSTed to
HTTP status code returned by the destination
Whether the delivery was considered successful
Number of delivery attempts made
Time taken to deliver, in milliseconds
Error detail if the delivery failed
ISO 8601 timestamp of successful delivery
Total number of log entries
Total number of pages
#### Error Responses
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# List Webhooks
Source: https://docs.autosend.com/api-reference/webhooks/list-webhooks
GET /webhooks
Retrieves all webhooks for the authenticated project. Supports filtering by active state and pagination. Secrets are never included in list responses.
This endpoint accepts a **project API key** (`AS_` prefix). Secrets are never included in list responses.
```bash cURL theme={null}
curl --request GET \
--url 'https://api.autosend.com/v1/webhooks?isActive=true&page=1&limit=50' \
--header 'Authorization: Bearer AS_your-api-key'
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/webhooks"
headers = {
"Authorization": "Bearer AS_your-api-key"
}
params = {
"isActive": "true",
"page": 1,
"limit": 50
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
```
```javascript JavaScript theme={null}
fetch('https://api.autosend.com/v1/webhooks?isActive=true&page=1&limit=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/webhooks?isActive=true&page=1&limit=50"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class ListWebhooks {
public static void main(String[] args) {
try {
URL url = new URL("https://api.autosend.com/v1/webhooks?isActive=true&page=1&limit=50");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
uri = URI('https://api.autosend.com/v1/webhooks?isActive=true&page=1&limit=50')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"data": {
"webhooks": [
{
"id": "60d5ec49f1b2c72d9c8b1234",
"organizationId": "60d5ec49f1b2c72d9c8b0000",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"url": "https://example.com/webhooks/autosend",
"secret": "***hidden***",
"events": ["email.delivered", "email.bounced"],
"isActive": true,
"status": "active",
"failureCount": 0,
"lastFailedAt": null,
"lastSuccessAt": "2026-06-12T10:00:00.000Z",
"lastDeliveredAt": "2026-06-12T10:00:00.000Z",
"metadata": { "team": "growth" },
"createdAt": "2026-06-01T09:00:00.000Z",
"updatedAt": "2026-06-12T10:15:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 50,
"totalPages": 1
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Query Parameters
Filter webhooks by active state. Omit to return both active and inactive webhooks.
Allowed values: `true`, `false`
Page number for pagination.
Default: `1`
Example: `1`
Number of webhooks to return per page.
Default: `50`
Range: `1`–`100`
#### Response
Webhooks retrieved successfully (200)
Indicates if the request was successful
Example: `true`
The paginated list of webhooks
Array of webhook objects. Secrets are masked as `***hidden***` in list responses.
Total number of webhooks matching the query
Current page number
Page size
Total number of pages
# Resend Webhook
Source: https://docs.autosend.com/api-reference/webhooks/resend-webhook
POST /webhooks/{id}/resend
Queues a test delivery of a given event to the webhook's URL. The webhook must be subscribed to the supplied event.
This endpoint accepts a **project API key** (`AS_` prefix). It queues a test delivery of the supplied event to the webhook's URL. The webhook must already be subscribed to that event.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234/resend \
--header 'Authorization: Bearer AS_your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"event": "email.delivered",
"data": {
"messageId": "msg_abc123",
"to": "recipient@example.com"
}
}'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}/resend"
headers = {
"Authorization": "Bearer AS_your-api-key",
"Content-Type": "application/json"
}
payload = {
"event": "email.delivered",
"data": {
"messageId": "msg_abc123",
"to": "recipient@example.com"
}
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}/resend`, {
method: 'POST',
headers: {
'Authorization': 'Bearer AS_your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
event: 'email.delivered',
data: {
messageId: 'msg_abc123',
to: 'recipient@example.com'
}
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
'email.delivered',
'data' => [
'messageId' => 'msg_abc123',
'to' => 'recipient@example.com'
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID + "/resend"
payload := map[string]interface{}{
"event": "email.delivered",
"data": map[string]string{
"messageId": "msg_abc123",
"to": "recipient@example.com",
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class ResendWebhook {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId + "/resend");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"event\": \"email.delivered\",\n" +
" \"data\": { \"messageId\": \"msg_abc123\", \"to\": \"recipient@example.com\" }\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}/resend")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['Authorization'] = 'Bearer AS_your-api-key'
request['Content-Type'] = 'application/json'
request.body = {
event: 'email.delivered',
data: { messageId: 'msg_abc123', to: 'recipient@example.com' }
}.to_json
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"message": "Webhook queued for delivery",
"data": {
"jobId": "12345"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook.
Example: `"60d5ec49f1b2c72d9c8b1234"`
### Body
The event type to deliver. Must be one the webhook is subscribed to.
Example: `"email.delivered"`
The event payload that will be wrapped and POSTed to the webhook URL.
Example: `{ "messageId": "msg_abc123", "to": "recipient@example.com" }`
#### Response
Webhook queued for delivery (200)
Indicates if the request was successful
Wrapper containing the queued job reference
The queue job ID for the delivery attempt
#### Error Responses
Returned when the webhook is not subscribed to the supplied event.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook is not subscribed to this event: email.delivered",
"code": "WEBHOOK_NOT_SUBSCRIBED",
}
}
```
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# Reveal Webhook Secret
Source: https://docs.autosend.com/api-reference/webhooks/reveal-webhook-secret
GET /webhooks/{id}/reveal
Returns the raw signing secret for a webhook so you can verify payload signatures. The webhook must belong to the authenticated organization.
This endpoint accepts a **project API key** (`AS_` prefix). It returns the raw HMAC signing secret used to verify payload signatures. The webhook must belong to the authenticated organization.
```bash cURL theme={null}
curl --request GET \
--url https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234/reveal \
--header 'Authorization: Bearer AS_your-api-key'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}/reveal"
headers = {
"Authorization": "Bearer AS_your-api-key"
}
response = requests.get(url, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}/reveal`, {
method: 'GET',
headers: {
'Authorization': 'Bearer AS_your-api-key'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID + "/reveal"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer AS_your-api-key")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class RevealWebhookSecret {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId + "/reveal");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println(content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}/reveal")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"data": {
"secret": "whsec_8f3a1c2d4e5b6a7c8d9e0f1a2b3c4d5e"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook.
Example: `"60d5ec49f1b2c72d9c8b1234"`
#### Response
Secret revealed successfully (200)
Indicates if the request was successful
Wrapper containing the secret
The raw HMAC signing secret. Use it to verify the `X-Webhook-Signature` header on incoming deliveries.
#### Error Responses
Returned when the webhook does not belong to the authenticated organization.
```json theme={null}
{
"success": false,
"error": {
"message": "Unauthorized access to webhook",
"code": "UNAUTHORIZED_WEBHOOK_ACCESS",
"status": 403
}
}
```
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# Update Webhook
Source: https://docs.autosend.com/api-reference/webhooks/update-webhook
PUT /webhooks/{id}
Updates a webhook's URL, subscribed events, active state, or metadata. The signing secret is immutable — create a new webhook to rotate it.
This endpoint accepts a **project API key** (`AS_` prefix). The signing secret is immutable — create a new webhook to rotate it. Supplying a `secret` field returns a `400` error.
```bash cURL theme={null}
curl --request PUT \
--url https://api.autosend.com/v1/webhooks/60d5ec49f1b2c72d9c8b1234 \
--header 'Authorization: Bearer AS_your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"events": ["email.delivered", "email.opened", "email.clicked"],
"isActive": false
}'
```
```python Python theme={null}
import requests
webhook_id = "60d5ec49f1b2c72d9c8b1234"
url = f"https://api.autosend.com/v1/webhooks/{webhook_id}"
headers = {
"Authorization": "Bearer AS_your-api-key",
"Content-Type": "application/json"
}
payload = {
"events": ["email.delivered", "email.opened", "email.clicked"],
"isActive": False
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
```javascript JavaScript theme={null}
const webhookId = '60d5ec49f1b2c72d9c8b1234';
fetch(`https://api.autosend.com/v1/webhooks/${webhookId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer AS_your-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: ['email.delivered', 'email.opened', 'email.clicked'],
isActive: false
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```php PHP theme={null}
['email.delivered', 'email.opened', 'email.clicked'],
'isActive' => false
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer AS_your-api-key',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
webhookID := "60d5ec49f1b2c72d9c8b1234"
url := "https://api.autosend.com/v1/webhooks/" + webhookID
payload := map[string]interface{}{
"events": []string{"email.delivered", "email.opened", "email.clicked"},
"isActive": false,
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer AS_your-api-key")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class UpdateWebhook {
public static void main(String[] args) {
try {
String webhookId = "60d5ec49f1b2c72d9c8b1234";
URL url = new URL("https://api.autosend.com/v1/webhooks/" + webhookId);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("PUT");
con.setRequestProperty("Authorization", "Bearer AS_your-api-key");
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
String jsonInputString = "{\n" +
" \"events\": [\"email.delivered\", \"email.opened\", \"email.clicked\"],\n" +
" \"isActive\": false\n" +
"}";
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int status = con.getResponseCode();
System.out.println("Response Status: " + status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={null}
require 'net/http'
require 'json'
require 'uri'
webhook_id = '60d5ec49f1b2c72d9c8b1234'
uri = URI("https://api.autosend.com/v1/webhooks/#{webhook_id}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Put.new(uri.request_uri)
request['Authorization'] = 'Bearer AS_your-api-key'
request['Content-Type'] = 'application/json'
request.body = {
events: ['email.delivered', 'email.opened', 'email.clicked'],
isActive: false
}.to_json
response = http.request(request)
puts response.body
```
```json 200 Response theme={null}
{
"success": true,
"message": "Webhook updated successfully",
"data": {
"id": "60d5ec49f1b2c72d9c8b1234",
"organizationId": "60d5ec49f1b2c72d9c8b0000",
"projectId": "60d5ec49f1b2c72d9c8b1111",
"url": "https://example.com/webhooks/autosend",
"secret": "***hidden***",
"events": ["email.delivered", "email.opened", "email.clicked"],
"isActive": false,
"status": "inactive",
"failureCount": 0,
"lastFailedAt": null,
"lastSuccessAt": "2026-06-12T10:00:00.000Z",
"lastDeliveredAt": "2026-06-12T10:00:00.000Z",
"metadata": { "team": "growth" },
"createdAt": "2026-06-01T09:00:00.000Z",
"updatedAt": "2026-06-12T11:00:00.000Z"
}
}
```
***
#### Authorizations
Project API key header of the form Bearer `AS_`.
### Path Parameters
The unique identifier of the webhook.
Example: `"60d5ec49f1b2c72d9c8b1234"`
### Body
All fields are optional — only the fields you supply are updated.
A new destination URL (must include the `http`/`https` protocol).
Example: `"https://example.com/webhooks/autosend"`
Replaces the subscribed event list. Must contain at least one valid event.
Example: `["email.delivered", "email.opened"]`
Enable or disable delivery without deleting the webhook.
Example: `false`
#### Response
Webhook updated successfully (200)
Indicates if the request was successful
The updated webhook object. The signing secret is always masked as `***hidden***`.
Unique webhook identifier
The destination URL events are delivered to
The updated subscribed event types
Whether the webhook is currently active
Delivery status. One of `active`, `inactive`, or `disabled`
Number of consecutive delivery failures (reset to `0` when re-activating)
Timestamp of the most recent failed delivery (ISO 8601), or `null`
Timestamp of the most recent successful delivery (ISO 8601), or `null`
Timestamp of the most recent delivery attempt (ISO 8601), or `null`
Arbitrary key-value metadata attached to the webhook
ISO 8601 creation timestamp
ISO 8601 last-updated timestamp
#### Error Responses
Returned when a `secret` field is included in the request body.
```json theme={null}
{
"success": false,
"error": {
"message": "Secret cannot be updated. Create a new webhook instead.",
"code": "VALIDATION_ERROR",
}
}
```
Returned when no webhook with the given ID exists in the project.
```json theme={null}
{
"success": false,
"error": {
"message": "Webhook not found",
"code": "WEBHOOK_NOT_FOUND",
}
}
```
# Email Automations
Source: https://docs.autosend.com/automations
Create contact-triggered email automations with four trigger types: added to list or segment, contact property matching, contact property changes, and event received.
Email automation lets you send sequences of emails triggered by contact actions without manual intervention. You define the trigger, timing, content, and exit conditions once, then the system handles the rest.
AutoSend supports four trigger types:
* **New contact is added to** a list or segment
* **Contact property matches with** a specific condition
* **Contact property changes from → to** a new value
* **Event Received** when a custom event you've defined fires for a contact
## Common Use Cases
* **Onboarding sequences** for new users or customers on different plans
* **Lead nurturing campaigns** for prospects at different stages
* **Re-engagement campaigns** for inactive contacts
* **Educational drip campaigns** that deliver value over time
* **Event-based sequences** triggered by signup, download, or purchase using [custom events](/automations/events)
* **Product adoption sequences** guiding users through features
* **Renewal or upgrade campaigns** for subscription-based products
## Best Practices
**Start with clear goals.** Define what you want each automation to achieve, whether it's onboarding, nurturing, conversion, or retention.
**Map the contact journey.** Plan the logical flow of emails from the contact's perspective. What information do they need first? What actions do you want them to take?
**Choose the right trigger type.** Use list/segment triggers for broad audience entry, property matching for condition-based targeting, and property changes for reacting to specific transitions.
**Time your emails strategically.** Balance staying top-of-mind with avoiding inbox fatigue. Consider your audience's behavior patterns and industry norms.
**Test before activating.** Send test emails to yourself, verify all links work, and ensure personalization tokens display correctly.
**Understand exit conditions.** Any contact that no longer meets the trigger criteria will be automatically removed from the automation. Contacts who unsubscribe will also be removed and will not receive further emails.
**Avoid automation overlap.** Be mindful of contacts who might qualify for multiple automations. Structure your triggers to prevent overwhelming contacts with too many simultaneous emails.
# Branching
Source: https://docs.autosend.com/automations/branching
Branching lets you send different emails to different contacts within the same automation, based on contact properties or event properties.
## What is branching in AutoSend?
By default, every contact in an automation receives the same emails in the same order. Branching lets you change that. You define separate paths inside a single automation, and each path has its own conditions, emails, and timing. When a contact reaches a branch, AutoSend checks their properties and sends them down every path they match.
This means you can handle multiple cases without building separate automations for each one. A new user on a free plan and a new user on a paid plan can both enter the same automation, but from the branch onwards, each receives emails written specifically for them.
## How to branch your automations
Click the **+** button below any email in your automation. This gives you two options: **Add Email** or **Create Branch**. Select **Create Branch**.
Click on a **Branch Filter** to open the condition panel. Each lane supports two filter modes:
* **Contact property** - filter on a property stored on the contact record (default).
* **Event property** - filter on a property from the event that started the automation. Only available when the automation's entry trigger is **Event Received**.
### Contact property
Select **Contact property**, then set the field, condition, and value.
For example, to route contacts by industry:
* Branch Filter 1: `industry` contains `Healthcare`
* Branch Filter 2: `industry` contains `Finance`
Click **+ Add Filter** to add more conditions to the same branch. Use **AND** / **OR** to combine them:
* **AND** - contact must match all conditions
* **OR** - contact must match at least one condition
Property values are case-sensitive. `Healthcare` and `healthcare` are treated as different values.
### Event property
When your automation's trigger is **Event Received**, you can branch on the data carried in the event itself instead of the contact record. Select **Event property** in the filter mode.
The **Property** dropdown lists the properties declared on the entry event (e.g. `country`, `plan`, `amount`) rather than the contact's properties. Operators and value inputs work the same as for contact properties, and are matched per property type (string, number, date, or boolean).
For example, on an automation triggered by `order_completed`:
* Branch Filter 1: `order_total` greater than `100`
* Branch Filter 2: `order_total` less than or equal to `100`
The lane header copy changes to reflect the new mode: *"Only events matching these conditions will route into this branch."*
**Event property** is only enabled when the automation's entry trigger is **Event Received**. On other trigger types it appears disabled with a tooltip explaining the requirement.
Switching a lane between **Contact property** and **Event property** clears that lane's existing filter, because the field universes don't overlap. Set the mode first, then build the filter.
#### Suggested values for string properties
If you defined **suggested values** for a string property on the event (see Events), the value input becomes a combobox: pick an existing suggestion from the dropdown, or type a brand-new value and select **Use '…'** to apply it.
New values typed through **Use '…'** are saved back to the event definition automatically, so the next person editing a filter on that property sees them as existing options without having to edit the event.
Once your filters are set, click **Add Email** inside each branch to build out that path's sequence. Each branch has its own **Wait For** timing, so you can space emails out differently per audience.
### **Adding more branches**
Click **+ Branch** below the Branch node to add more paths. You can create upto 10 branches in an automation.
Branches are prioritized from left to right. A contact enters the first branch they match and won’t be checked against any remaining branches after that. If you have multiple branches, make sure to order them with the important conditions first.
## Use cases
* **Free vs. paid users** - Show different features or CTAs based on a contact's plan
* **Role-based content** - Send engineers to API guides and marketers to campaign tips based on their `role`
* **Industry-specific emails** - Share relevant case studies based on a contact's `industry`
* **Regional content** - Deliver localized messaging based on `region`
* **Onboarding by product** - Guide contacts through different setup flows depending on which product they signed up for
* **Trial expiry** - Send a different automation to contacts whose trial is expiring soon vs. those who still have time left
* **Company size** - Tailor emails for individual users vs. teams vs. enterprise accounts based on `company_size`
* **Signup source** - Follow up differently depending on whether a contact came from a demo, a referral, or organic signup
* **Order value (event property)** - On an automation triggered by `order_completed`, route high-value orders to a VIP thank-you and lower-value orders to a standard receipt sequence
* **Plan picked at signup (event property)** - On a `signup_completed` event carrying a `plan` property, send each plan into its own onboarding
# How to Create an Email Automation
Source: https://docs.autosend.com/automations/create
Learn how to create a contact-triggered email automation in AutoSend, including setting up triggers, adding emails, configuring settings, and activating your automation.
## Prerequisites
Before creating an automation, make sure you have the following ready based on your trigger type:
* **For list/segment triggers:** Create a contact list or segment that represents your trigger condition.
* **For property-based triggers:** Ensure your contacts have the relevant contact properties set up. The automation will evaluate these properties to determine when to trigger.
## How to Create an Email Automation
Under **Marketing Emails** in the sidebar, select **Automations** and click **New Automation**.
An untitled automation will be created. Give it a descriptive name that clearly identifies its purpose, such as "Pro Plan Onboarding" or "Welcome Series."
Click on the **Trigger** card to configure what starts your automation. AutoSend offers four trigger types:
### New contact is added to
Select a contact list or segment. When a contact joins this list or segment, they automatically enter the automation.
**Example:** Create a segment called "Pro Plan Members" with the contact property `plan: pro`. When a user purchases a Pro plan and enters this segment, the automation triggers.
Only contacts added via API or manually after activation will enter this automation. Existing contacts and CSV imports are excluded.
### Contact property matches with
Select a list to scope the contacts, then define a property condition. The automation triggers when a contact in that list matches your condition(s).
1. Choose a **contact property** from the dropdown (e.g., `verified`)
2. Select an **operator** (e.g., `equals`)
3. Enter the **value** to match (e.g., `true`)
4. Click **Add** to include additional conditions if needed
**Example:** Set `verified` equals `true` on the "All Contacts" list. Any contact whose `verified` property becomes `true` will enter the automation.
Only contacts added via API or manually after activation and matching the condition(s) will enter this automation. Existing contacts and CSV imports are excluded.
### Contact property changes from → to
Select a list to scope the contacts, then define one or more property transitions. The automation triggers when a contact's property changes from one specific value to another.
1. Choose a **contact property** from the dropdown (e.g., `published`)
2. Set the **from value** (e.g., `unpublished`) and **to value** (e.g., `published`)
3. Click **Add** to include additional property change conditions
4. Use **AND** / **OR** logic to combine multiple conditions
**Example:** Trigger when `published` changes from `unpublished` to `published` in the "All Contacts" list. Perfect for sending a congratulatory email when a user publishes their first project.
### Event Received
Pick a custom event from the **Event** dropdown. The automation triggers whenever that event is recorded for a contact, using the data carried in the event payload.
Unlike the other trigger types, **Event Received** is not scoped to a list or segment. Any contact for whom the event fires enters the automation.
1. Select **Event Received** as the trigger type
2. Choose an event from the **Event** dropdown (lists every event defined in this project)
3. Save the trigger
**Example:** Trigger when `order_completed` is received. Then use a [branch](/automations/branching) on the event's `order_total` property to send different post-purchase sequences for small vs. large orders.
Only events received after the automation is activated will trigger it. Historical events are not replayed. Don't have an event yet? See Events to define one.
1. Click **Add Email** to create your first email in the sequence
2. Set the **Wait For** timing to determine when this email should be sent:
* By default, the timing is set to 0 seconds (immediately)
* Adjust the timing based on your email strategy (e.g., 30 mins, 6 hours, 3 days)
3. Design your email content, subject line, and preview text and select the sender
4. Repeat to add more emails to your sequence
You can add up to 10 emails in a single automation. Plan your sequence thoughtfully to deliver value without overwhelming your contacts.
Configure the following settings for your automation:
* **Automation name**: Give your automation a descriptive name (e.g., "Onboarding Automation").
* **Unsubscribe group**: Select an appropriate unsubscribe group. Automations are treated as marketing emails and are required to include an unsubscribe link for compliance with email regulations.
* **Tracking**: Toggle tracking for:
* **Open rate**: An invisible image is appended to HTML emails to track if they have been opened.
* **Clicks**: AutoSend tracks clicks by rewriting links in your email. When clicked, they pass through an AutoSend server before redirecting to the original URL.
Once you've configured all settings and added your emails:
1. Review your automation flow to ensure everything is correct
2. Send test emails to verify content and personalization
3. Click **Activate** to make your automation live
Your automation is now active and will begin triggering for contacts who meet the trigger conditions.
The automation ends automatically once all emails have been sent or the contact no longer meets
the entry criteria. There's nothing to configure here. This is handled by default.
## Troubleshooting
- Verify the automation is in **Active** state, not Draft or Paused.
- Check that contacts are actually joining your trigger list or segment.
- Ensure your list or segment criteria are correctly configured.
- For property-based triggers, confirm the contact property exists and has the expected values.
- Contacts uploaded via CSV won't trigger automations. Only contacts updated/added via API or from the dashboard will be enrolled in automations.
- Verify the contact property name matches exactly (property names are case-sensitive).
-
For "matches with" triggers, check that the property value matches the condition you set.
-
For "changes from → to" triggers, ensure the contact's property actually transitions between
the specified values. Setting a value for the first time does not count as a change.
- Check that the contact belongs to the list you selected for the trigger.
-
Confirm the automation was in **Active** state before the event was sent. Events received
while the automation is in Draft or Paused are not replayed when you activate it later.
-
Check that the `eventName` in your API call matches the event definition exactly (names are
case-sensitive).
-
Verify the event identifies a contact that exists in your project. Events sent with an `email`
or `contactId` that doesn't resolve to a contact are rejected.
-
Make sure the event still exists. If it was deleted, the automation's trigger no longer
resolves and won't fire.
- Review your **Wait For** settings between each email.
-
Remember that timing starts from when the contact enters the automation, not from a specific
time of day.
- Check if they were removed from the trigger list/segment, or if their contact property no longer matches the trigger condition, or if the property changed away from the expected value.
- Verify the automation hasn't been paused.
- Confirm they haven't unsubscribed from these emails.
# Events
Source: https://docs.autosend.com/automations/events
Events are actions from your app, like signups or purchases, that you can use to trigger automations, create segments, and more.
Custom events are point-in-time signals you send to AutoSend from your application. Unlike contact properties, which describe a contact's current state, events describe something that just happened, and they can carry a typed payload (order total, plan name, country) that your automations can react to.
Use events when you want to:
* Trigger an automation the moment something happens (a signup, a purchase, a cart abandon).
* Branch contacts inside an automation based on data from the event itself, not from the contact record.
## What is an event?
An event definition belongs to a project and consists of:
* An **event name** (e.g. `order_completed`, `signup_completed`). Names are unique within a project and cannot be renamed once created.
* An optional **description**.
* A **property schema**: zero or more properties, each with a name and a type. Supported types are `string`, `number`, `date`, and `boolean`.
Properties of type `string` can optionally declare **suggested values**, a short list of expected values (e.g. `USD`, `EUR`, `GBP`). Suggested values power the dropdown shown in branch filters and grow automatically as new values are used (see Branching).
## Create an event
From your project dashboard, open the **Events** page from the left navigation.
The Events page is part of the marketing feature set. On transactional-only projects, you'll see an upgrade prompt instead.
Click **New Event**, then enter an **Event Name** (e.g. `order_completed`) and an optional description. Event names should be lowercase and use underscores for readability.
For each property you plan to send with the event, add a row with:
* **Property Name** (e.g. `order_total`, `currency`, `plan`).
* **Type**: `string`, `number`, `date`, or `boolean`.
* **Description** (optional).
* **Suggested values** (string properties only): expected values for the property.
Once an event is referenced by an automation, its property names and types are locked to prevent breaking saved branch filters. You can still extend the schema by adding new properties, and you can keep editing suggested values.
Click **Save**. The event is now available to send from your application and to select as a trigger inside an automation.
## Send an event
Once defined, send an event from your backend using the Send Event API. Identify the contact with either `email` or `contactId` (one is required), and pass the property values in `eventProperties`.
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/events/send \
--header 'Authorization: Bearer AS_your-project-api-key' \
--header 'Content-Type: application/json' \
--data '{
"eventName": "order_completed",
"email": "jane@example.com",
"eventProperties": {
"order_total": 129.50,
"currency": "USD"
}
}'
```
```javascript JavaScript theme={null}
await fetch('https://api.autosend.com/v1/events/send', {
method: 'POST',
headers: {
Authorization: 'Bearer AS_your-project-api-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventName: 'order_completed',
email: 'jane@example.com',
eventProperties: {
order_total: 129.5,
currency: 'USD',
},
}),
});
```
```python Python theme={null}
import requests
requests.post(
"https://api.autosend.com/v1/events/send",
headers={
"Authorization": "Bearer AS_your-project-api-key",
"Content-Type": "application/json",
},
json={
"eventName": "order_completed",
"email": "jane@example.com",
"eventProperties": {
"order_total": 129.50,
"currency": "USD",
},
},
)
```
You can identify the contact by `email` **or** `contactId`. Use `contactId` when you already have
the AutoSend contact ID stored in your application; it skips the email lookup and is the
recommended choice for high-volume event sources.
Example using `contactId`:
```json theme={null}
{
"eventName": "order_completed",
"contactId": "60d5ec49f1b2c72d9c8b8888",
"eventProperties": {
"order_total": 129.5,
"currency": "USD"
}
}
```
See the Send Event API reference for the full request schema, error codes, and language-specific examples.
## Events vs. contact properties
Both can drive automations, but they answer different questions:
* **Contact properties** describe who a contact is right now (`plan`, `country`, `verified`). They persist on the contact record. Use them when the trigger or branch logic depends on the contact's current state.
* **Events** describe what just happened, with a payload attached (`order_completed` with an `order_total`). They aren't stored on the contact. Use them when the trigger or branch logic depends on a specific occurrence and the data that came with it.
A common pattern is to use an event to start an automation, then branch on a property of that event, so a single automation can fan out (for example, different cart-recovery emails based on `cart_value`).
## Use events in automations
Once you've defined and started sending an event, you can:
* Trigger an automation from an event, so a sequence
starts the moment the event is received.
* Branch on event properties, so contacts
entering an event-triggered automation are routed by the data carried in the event.
# Download official AutoSend logos
Source: https://docs.autosend.com/brand-kit
Access official AutoSend brand assets including logos, icons, and brand guidelines for your integrations and marketing materials.
Download official AutoSend logos in SVG and PNG formats, available in both light and dark themes.
Use only the official colors for the wordmark.
# Changelog
Source: https://docs.autosend.com/changelog
The latest updates, new features, improvements, and fixes shipped to AutoSend.
## Tally Forms Integration
AutoSend now has a native [Tally Forms](https://tally.so) integration that automatically syncs your form submissions with contacts in AutoSend, with no code required.
Connect your Tally account with your API key, pick a form and the contact list its submissions should be saved to, and map the form fields to contact properties. From then on, every new submission is saved as a contact in the selected list, and existing contacts are updated instead of duplicated. Use those contacts to send campaigns, trigger automations like a welcome sequence, or build segments based on their form answers.
Read the [Tally Forms integration guide](/integrations/tally-forms) to get started.
## Inbound Email API
AutoSend now lets your application receive incoming emails. When a message lands on one of your receiving domains, AutoSend fires an `email.received` webhook with the message metadata, and your app calls a structured REST endpoint to fetch the full message including headers, plain text and HTML bodies, attachments, and verdicts.
Every project is provisioned with a default receiving domain in the form `{prefix}@{uniquesubdomain}.autosend.email`, so you can start receiving email immediately without any DNS setup. You can also enable Inbound on a verified custom domain.
Use it to give your AI agents a fully two-way email address, route customer replies into a support portal, log incoming emails into your CRM, attach candidate replies to your ATS, or build a custom email client.
Read how to setup and use the Inbound Email API and browse the API reference to learn more.
## Revamped Billing and Usage with Email Credits
The Billing and Usage pages have been redesigned, and we've replaced overage billing (additional emails) with **email credits**.
From **Billing**, you can manage your subscription, update payment methods, edit billing details, and download past invoices, all in one place. From **Usage**, you can track your sending for the current billing period, see usage broken down per project, purchase email credits, and configure auto-reload.
Previously, going over your plan limit was billed on your next invoice as additional emails. That's been replaced by email credits, which you purchase upfront. Credits roll over, never expire, and stack on top of your plan limit. Enable auto-reload to top up automatically when your balance drops to a threshold you set.
Read the Billing and Usage docs to learn more.
## Project-Specific SMTP Keys
The SMTP password is no longer your API key. Each project now has its own SMTP key that you create from the SMTP tab in Project Settings. API keys continue to work for REST API requests, but SMTP integrations need to be updated to use the new key.
Read the SMTP quickstart to learn more.
#### Other changes:
* \[feat] New billing page is now live with more detailed plan options
* \[feat] New integrations section on the Transactional Email landing page
* \[fix] Logged-out users are now redirected to the login page when hitting protected pages
## Official Convex component
We just shipped the official AutoSend Convex component, a drop-in integration for sending transactional email straight from your Convex backend. Call `sendEmail` from any mutation and the component handles queueing, idempotency, retries with backoff, webhook signature verification, and the full delivery lifecycle (`queued`, `sending`, `retrying`, `sent`, `failed`, `canceled`) for you.
It also supports templates, CC/BCC, attachments (base64 or URL), bulk sends with per-recipient merge fields, suppression groups, multi-project setups via Account API Keys, and a test sandbox that rewrites recipients while you're wiring things up.
The package is open source on [GitHub](https://github.com/autosendhq/autosend-convex) and published on [npm](https://www.npmjs.com/package/@autosend/convex) as `@autosend/convex`. The guide includes a paste-ready prompt so any AI coding assistant (Cursor, Claude Code, Copilot, Codex) can wire it up for you in one shot.
Read the Convex integration guide for setup, sending variants, status tracking, and configuration.
## Event-Triggered Automations and EU Region
Two big additions: a new trigger type that fires on events from your app, and a new sending region for EU customers.
#### Events in Email Automations
Until now, automations could only trigger based on contact properties or list membership. That works for things like "plan changed to pro" or "contact added to waitlist," but it can't react to something that just happened in your app.
Events fix that. You can now define custom events (like `signup_completed`, `project_published`, `team_member_invited`) with typed properties, send them from your backend via API, and use them to trigger automations the moment they fire.
The real power comes from combining events with branching. A single `signup_completed` event can branch contacts based on `plan` or `team_size`. Solo users get a self-serve onboarding sequence, teams get collaboration tips, and enterprise gets a white-glove welcome. One automation, multiple paths.
Read the Events in Automations documentation to learn more, and check the Send Event API reference to start firing events from your backend.
#### EU Region
You can now choose EU as your sending region. Select it when adding a new domain. For existing verified domains, you'll need to update your DNS records if you decide to switch the region.
Read the Domain documentation to learn more.
## Automation Branching
If you've been creating separate automations just to send different emails to different segments, that's no longer necessary. Branching is now live in AutoSend. One automation, multiple paths.
#### Branch by contact property
Split any automation into branches based on contact properties. Each branch gets its own filters, wait times, and email sequence.
For example, a new contact gets added to your list. Instead of sending everyone the same onboarding sequence, you branch based on their plan:
* Trial users get a sequence focused on activation and conversion
* Pro users get tips on getting the most out of their plan
* Business users get a white-glove onboarding with dedicated support info
#### Automations API
We also opened up API endpoints for Automations, so you can create and manage automations programmatically. List, create, get, update, pause, resume, and delete automations directly from your code.
Read the Email Automations documentation to learn more about branching, and check the Automations API reference to start managing automations programmatically.
## RBAC and Projects API
Two big additions to multi-project support: role-based access control for your team and a full Projects API.
#### Role-Based Access Control
Team permissions now have two distinct roles: **Admin** and **Member**.
* **Admins** have full access to the entire workspace, including all projects, domains, billing, and member management.
* **Members** have scoped access. Admins assign them to specific projects, and they can only see and operate within those projects. Members can also add or remove other members within their assigned projects.
Read the Team documentation for the full invite flow and FAQ.
#### Projects API
You can now manage projects programmatically using the Account Admin API key (`ASA_` prefix). Three endpoints are available:
* **List Projects** - Retrieve all projects in your organization.
* **Create Project** - Create a new project with a name. The project is ready to use immediately.
* **Delete Project** - Permanently delete a project and all its associated resources. This cannot be undone.
Check out the Projects API reference to get started.
## Introducing Projects
You can now create multiple Projects under a single AutoSend account. Each project is a fully isolated workspace with its own API keys, contacts, senders, templates, campaigns, automations, and webhooks. Your subscription and monthly email quota are shared across all projects.
#### Why Projects?
* Keep Production, Staging, and Development environments completely separate
* Run multiple independent products under one subscription and billing
* Build multi-tenant platforms (CRMs, Shopify apps, marketing tools) where each customer gets their own isolated project
* Manage multiple clients as an agency without any data mixing
#### Two types of API Keys:
Along with Projects, we're introducing a second API key type:
* **Project API Key** (`AS_...`): Scoped to a single project. Use this for sending emails, managing contacts, templates, and campaigns. The right key for most integrations.
* **Account API Key** (`ASA_...`): Cross-project scope. Use this to programmatically create, update, or delete projects. All requests with an Account API Key require an `x-project-id` header to specify the target project, except when creating a new project.
Read the Projects documentation to learn more, and the API Keys documentation for details on key types and authentication.
## NEW: 41 API Endpoints Added to API Reference
The API reference just got a major expansion. Seven new endpoint groups are now fully documented, covering everything from campaign management to domain configuration.
#### Here's what's new:
* **Custom Fields** (4 endpoints) - List, get by name, create, and delete custom contact fields for flexible segmentation.
* **Templates** (5 endpoints) - Get, create, update, delete, and search email templates programmatically.
* **Senders** (4 endpoints) - List, get, create, and delete verified sender identities.
* **Campaigns** (8 endpoints) - Full campaign lifecycle management: list, get, create, update, pause, resume, abort, and delete.
* **Domains** (5 endpoints) - List, get, add, verify, and delete sending domains.
* **Contact Lists** (7 endpoints) - List, get, create, and delete lists, plus get contacts, bulk add, and remove contacts.
* **Suppression Groups** (8 endpoints) - List, get, create, update, and delete groups, plus search entries, bulk suppress, and bulk unsuppress.
All endpoints include request/response examples in multiple languages. Check out the API Reference to get started.
## Email Automations v2.0: New Trigger Types + Visual Builder
Email automations just got a lot more powerful. You can now trigger automations based on contact properties, not just list or segment membership.
#### Three Trigger Types:
* **New contact is added to**: Trigger when a contact joins a specific list or segment. Same as before.
* **Contact property matches with**: Trigger when a contact's property matches a condition you define (e.g., `verified` equals `true`). Great for targeting contacts based on their current state.
* **Contact property changes from → to**: Trigger when a contact's property transitions from one value to another (e.g., `published` changes from `unpublished` to `published`). Perfect for reacting to real-time changes. Supports multiple conditions with AND/OR logic.
#### Brand new visual workflow builder
Build your automation sequences in a completely redesigned visual workflow builder with a cleaner interface and smoother experience. Do try the email drag-n-drop to rearrange emails in your sequence. That's our favorite part.
Read the updated Email Automations documentation to learn more.
## NEW: AutoSend MCP Server is here.
You can now manage your email campaigns, templates, contacts, and senders directly from AI assistants.
The **AutoSend MCP Server** connects to AI tools like Claude, Cursor, Codex, Antigravity, etc using the Model Context Protocol. Just authenticate with OAuth (no API keys to manage), and your AI assistant gets secure access to your AutoSend project.
It ships with **17 tools** across 5 categories: Lists & Segments, Templates, Senders, Suppression Groups, and Campaigns. Create drafts, search templates, duplicate campaigns, and more, all through natural language.
Check out the MCP Server documentation to get started.
## NEW: Domain warmup is now built into AutoSend.
If you've ever started sending on a new domain and wondered why your open rates were tanking, this is for you.
We just shipped **Gradual Send**. It's a new sending mode that automatically ramps up your email volume over time, so mailbox providers get familiar with you before you hit them with your full list.
You pick a starting volume, choose how fast you want to grow, set a start date, and that's it. AutoSend builds out your daily send schedule and shows you a chart of exactly what will go out and when. If your bounce or complaint rate spikes, it pauses automatically.
No manual scheduling. No spreadsheets. No sending five small campaigns just to warm up a domain.
You'll find it under "When to send?" the next time you set up a campaign. Highly recommend it if you're on a new domain or sending to a cold list for the first time.
Read our Domain Warmup guide to learn more about warming up your domain and improving deliverability.
## Quick Create Menu and Performance Improvements
We shipped quality-of-life improvements this week. Faster workflows, cleaner analytics, and a few important fixes.
#### Quick Create Menu
Create campaigns and automations directly from the dashboard. Click the "+" button in the top right corner without navigating away from your current page.
#### Faster Campaign and Analytics Loading
Dashboards load faster with caching for workflow analytics and streamlined campaign queries. Separated worker services for email sending, validation, and webhooks handle higher loads independently. Lazy loading for past and failed campaigns reduces initial page load times.
### Fixes:
* Fixed invited users being redirected to the welcome screen instead of their team's workspace
* Resolved analytics text alignment issues that made numbers hard to read
* Added SMTP rate limiting API to prevent sending spikes and improve deliverability
## Dashboard Redesign and Landing Page Refresh
The dashboard has been redesigned from the ground up and a fresh new landing page has launched.
#### Dashboard Redesign
Your dashboard now surfaces key metrics (sends, opens, clicks, bounces), active campaigns, and product updates right when you log in. No more digging through reports.
#### Fresh Landing Page
Completely redesigned with a modern look, interactive sections, smoother animations, and a clearer story about how AutoSend helps you send better emails.
#### Faster CSV Imports
Importing large contact lists is significantly faster. Upload your CSV and get back to work.
#### User ID Support in CSV Imports
Import user IDs alongside contact data to maintain consistent identifiers across systems.
#### List-Unsubscribe Header
The standard `List-Unsubscribe` header is now included in all emails, enabling one-click unsubscribe buttons in Gmail and Apple Mail. No configuration needed.
### Fixes:
* Fixed campaign analytics not refreshing when switching between campaigns
* Resolved dark mode styling issues with the payment alert banner
* Improved link formatting in the email builder
## CC and BCC Support
Native CC and BCC support is now available across AutoSend.
#### Send to Multiple Recipients
Add CC and BCC recipients to any email via API or SMTP relay without sending separate emails.
#### Full Email Activity Tracking
Every CC and BCC recipient is tracked individually. See opens, clicks, bounces, and complaints for each recipient separately.
#### Smart Suppression Handling
When a TO recipient is suppressed, CC and BCC recipients are automatically suppressed too. Bypass options are available when needed.
#### Proper Bounce and Complaint Attribution
Bounces and complaints from CC/BCC recipients are correctly attributed to the right email address.
#### Cleaner Campaign Dashboard
Active campaigns now automatically appear at the top and past campaigns are hidden to reduce visual clutter.
#### Filter Contacts by List ID
Added `contactListId` parameter to the contacts API for filtering contacts by specific lists.
#### Bypass Suppressions
Send an email to a suppressed recipient on a per-email basis for critical communications like password resets.
### Fixes:
* Fixed proration calculations for plan upgrades and downgrades
* Resolved issues with payment cancellations not reflecting correctly
* Fixed billing cycle date updates after plan changes
## Improvements and Bug Fixes
This release focuses on polishing the edges. Clearer errors, better analytics visibility, and a few fixes.
#### Email Automation Landing Page
A dedicated landing page for email automation explaining drip campaigns, onboarding sequences, and triggered emails.
#### Dynamic Validation Errors
Validation errors now tell you exactly what's wrong and where, with specific feedback for contacts, emails, SMTP settings, and suppression groups.
#### Unsubscribe Preferences
The unsubscribe preferences page now shows visible and hidden states for suppression groups, giving you control over what subscribers see.
#### Open Rate Analytics
Campaign and automation analytics now include dedicated open rate sections alongside clicks and deliveries.
#### SMTP API Key Flexibility
All API key types now work with SMTP authentication. Any valid API key works as your SMTP password.
### Fixes:
* Fixed webhook validation errors not displaying correctly when creating or editing webhooks
* Fixed pricing feature strikethrough styling for plans
* Fixed icon visibility issues in dark mode for email status indicators
## Introducing SMTP Support
You can now send emails through AutoSend using SMTP as well as the REST API.
#### Simple Setup
Go to Settings → SMTP to get your credentials. Host, port, and username are pre-configured. Generate an API key to use as your password.
#### Standard SMTP
Connect using `smtp.autosend.com` on port 587 with TLS. Works with any SMTP client, library, or platform that supports standard SMTP.
#### Same Deliverability
All the benefits of AutoSend's sending infrastructure: same reputation, same analytics, same reliability. Perfect for legacy systems or platforms with built-in SMTP support.
### Improvements:
* Campaign and automation analytics now show clicked status sections with cleaner empty states
* You can now sort contacts via the API for paginating large lists in a specific order
### Fixes:
* Fixed date filter equals not working correctly when filtering contacts by an exact date
* Fixed incorrect campaign counts in the campaigns list
## Introducing Spam Checker
Spam Checker analyzes your email content in real-time, flags potential issues, and tells you exactly what to change before you send.
#### Real-time spam analysis
As you write, Spam Checker scans your content for known spam triggers.
#### Severity scoring
Issues are categorized as safe, warning, or critical based on how likely they are to trigger spam filters.
#### Specific issue detection
Catches excessive caps, suspicious URLs, spam trigger words, urgency language, missing unsubscribe links, and more.
#### One-click fixes
Most issues come with suggested replacements. Click "Accept" and the fix is applied instantly.
#### Works everywhere
Available in the email builder for campaigns, automations, transactional templates, and compose.
### Fixes:
* Fixed styling issue in the test email modal
* Fixed alignment and null click issue in automation analytics
* Fixed contact counts not refreshing correctly when adding contacts to a list
* Fixed outline styling on automation sequence cards
## Platform Improvements
Major improvements to how you track campaign performance and how quickly emails reach inboxes.
#### Improved Analytics
Email status tooltips now explain what each status means and what action to take. Date/time formatting is consistent across all views for easier comparison.
#### Faster Campaign Delivery
Upgraded sending infrastructure improves speed and reliability. Campaigns reach inboxes faster with fewer delivery errors, especially for large contact lists.
### Fixes:
* Fixed images breaking email responsiveness on mobile devices
* Resolved a bug where inserting images from your library would fail silently
* Fixed segment evaluation queue causing segments to refresh incorrectly
## Email Builder, Webhooks, and Analytics
Major improvements to the email builder, better webhook tracking, new analytics capabilities, and platform stability fixes.
#### Global Text Color
Set a default text color for your entire email once and all text inherits that color.
#### Plain Text Support
The email builder now properly handles plain text versions of your emails for better deliverability.
#### Better State Management
Selecting buttons, dividers, images, and text blocks is smoother with clearer visual feedback.
#### Better Test Email Experience
Enter test values for email variables directly in the test email modal. Cleaner interface with better feedback.
#### Email Activity Analytics
Key metrics (total sent, delivered, opened, clicked, bounced, failed) are now visible at a glance on the email activities page.
#### Template Table View and Search
Email templates now have a proper table view. Search by name or subject line to find templates faster.
#### Webhook batchId and test field
Webhook events now include `batchId` (for grouping bulk email events) and a `test` boolean field for debugging.
#### Optimized webhook payloads
Reduced payload size by stripping unnecessary data. Faster processing, less bandwidth, and cleaner logs.
### Fixes:
* Email activity modal viewport
* Unsubscribe group undefined error
* Image link focus behavior
* Domain validation false positives
* Button sizing in modals
* Broken documentation links
* Template ID missing in HTML editor
* Workflow automation trigger edge cases
* Automation button state management
## Introducing Email Automations
Email automation is now live in AutoSend. Create triggered sequences with visual workflows, tracking, and unsubscribe group management.
#### Visual Builder
Design automation flows in a drag-and-drop interface. See your entire sequence with entry points, wait steps, and email blocks connected in order.
#### Flexible Timing
Set delays between emails: immediately, hours, or days.
#### Exit Conditions
Control when contacts leave sequences. Exit contacts immediately when they're removed from the triggering list or segment.
#### Unsubscribe Groups
Assign each automation to a specific unsubscribe group so contacts can opt out of individual sequences without unsubscribing from everything.
#### Tracking Built-in
Enable or disable open and click tracking per email. Analytics show open rates, click rates, and engagement metrics per message.
**Common use cases:** Welcome sequences, onboarding drips, re-engagement campaigns, trial conversion sequences, and waitlist nurturing.
## Introducing Teams
AutoSend now supports teams. Invite your team for full platform access with no permission levels to configure.
#### Invite Your Team
Head to [Settings → Team](https://autosend.com/settings/team) to invite as many people as you need.
#### Same Access for Everyone
No permission levels to configure. Everyone you invite can do everything you can.
#### Simple Invite Flow
Send an invite, they get a secure link via email, they click it, they're in. Invites are valid for 7 days. You can resend or cancel them anytime.
## Introducing Compose by AutoSend
A free email template builder with markdown support. Create responsive emails without coding.
#### Markdown support
Write emails the same way you write docs or READMEs. If you've written markdown before, you already know how to use Compose.
#### Visual blocks
Drag in buttons, columns, spacers, and dividers when markdown isn't enough.
#### Variables for personalization
Drop in `{{first_name}}` or `{{company_name}}` and your email is ready for dynamic content.
#### Responsive by default
Your emails automatically adapt to desktop, tablet, and mobile.
#### Clean HTML export
Production-ready HTML with no inline styles soup. Tested across Gmail, Outlook, Yahoo, and Apple Mail.
#### No signup required
Open [autosend.com/compose](https://autosend.com/compose) and start building. Works with any email provider.
## Email Activity Filters
Filter and analyze your email activity by status, templates, domain, and API key.
#### Status Filters
Filter by delivery lifecycle (Sent, Delivered, Bounced), engagement (Opened, Clicked), or issues (Complained, Suppressed, Failed).
#### Email Source
Filter by email template or campaign to see activity for specific email types.
#### Sending Domain
Filter emails sent from specific verified domains.
#### API Key
Filter emails sent using specific API keys for better tracking.
Combine multiple filters to narrow down your search and get precise insights into email activity.
## Webhooks and Email Attachments
Two of the most requested features: webhooks management and email attachments.
#### Webhooks
Listen to real-time email events directly in your app. Available on Pro 10k plans and above.
Events you can track:
* Delivery lifecycle: `email.sent`, `email.delivered`, `email.deferred`
* Engagement: `email.opened`, `email.clicked`
* Issues: `email.bounced`, `email.spam_reported`
* Preferences: `email.unsubscribed`, `email.group_unsubscribed`, `email.group_resubscribed`
* Contact events: `contact.created`, `contact.updated`, `contact.deleted`
#### Email Attachments
Attach files via base64 upload, S3 file paths, or external URLs. Up to 20 attachments per email, 40MB per file.
### Fixes:
* Fixed pricing page data issues during onboarding.
## Introducing Marketing Emails
You can now create and send marketing campaigns, manage contacts, and build engaged audiences, all from one platform.
#### Campaigns
Create and send one-time or scheduled marketing emails to contacts, lists, or segments. Features include an HTML email designer, variable personalization, real-time preview, send now or schedule, test emails, targeting options, and exclusion lists.
#### Contact Management
All contacts view, contact details, reserved fields, custom fields, CSV import with field mapping, and contact activity tracking.
#### Lists
Static collections of contacts grouped together. Perfect for fixed criteria like "Newsletter Subscribers."
#### Segments
Dynamic groups that update automatically based on conditions. Auto-update as contacts meet or stop meeting your defined criteria.
#### Sender Management
Add sender names, email addresses, and reply-to addresses. Only authenticated domains are allowed for better deliverability.
#### Unsubscribe Groups
Let recipients choose which types of emails they want to receive. Supports CAN-SPAM, GDPR, and CASL compliance.
# How to Add Sending Domain in AutoSend?
Source: https://docs.autosend.com/domain
Here's a step-by-step guide on how to verify and authenticate domain on AutoSend for sending emails.
Before you can send emails through AutoSend, you need to verify ownership of your sending domain. This ensures:
* **Email deliverability**: Verified domains have better inbox placement rates
* **Authentication**: Proves you own the domain you’re sending from
* **Security**: Prevents unauthorized use of your domain for sending emails
* **Compliance**: Meets industry standards for email authentication (SPF, DKIM, DMARC)
**Pro Tip: Use a Subdomain**
We recommend using a subdomain (like `mail.yourdomain.com` or `emails.yourdomain.com`) instead of your root domain. This provides better email deliverability and keeps your transactional emails separate from your main domain’s reputation.
1. Navigate to **Settings > Domains** in your AutoSend dashboard and click the **Add Domain** button.
2. Enter your sending domain **without** `http://` or `https://`
3. Choose the AutoSend region closest to your users for optimal email delivery performance. Available regions include:
* **US East (N. Virginia)** - `us-east-1`
* **US East (Ohio)** - `us-east-2`
* **Asia Pacific (Mumbai)** - `ap-south-1`
* **Europe (Frankfurt)** - `eu-central-1`
The region you select will be used to send your emails and should be chosen based on where most of your recipients are located.
If you want support for other regions, please contact us at
Once your domain is added, AutoSend will generate the required DNS records. You’ll need to add these records to your domain’s DNS settings. AutoSend requires two types of DNS records:
### DKIM and SPF Records
These records enable email authentication and prevent email spoofing.
* **Ownership Verification Record**: Verifies you own the domain
* **DKIM Record(s)**: Cryptographic signatures that prove email authenticity
* **Mail-From Record**: Specifies authorized email sending servers (SPF)
### DMARC Record
DMARC (Domain-based Message Authentication, Reporting & Conformance) adds an extra layer of email authentication and provides reporting on email authentication status.
Each record will have the following fields:
| **Field** | **Description** |
| ----------------- | -------------------------------------------- |
| **Type** | DNS record type (CNAME or TXT) |
| **Name** | The subdomain or host record |
| **Content/Value** | The record value to add |
| **Priority** | Used for MX records (usually not applicable) |
| **TTL** | Time To Live - set to “Auto” or 3600 |
**Note** - Copy the exact values from AutoSend - even small typos can cause verification to fail -
Some DNS providers add your domain name automatically - check your provider’s documentation - Use
the copy button next to each record value in AutoSend to avoid errors - All DKIM and SPF records
must be added for successful verification
After adding all DNS records to your DNS provider:
1. **Wait for DNS propagation** (typically 5-30 minutes, but can take up to 48 hours)
2. Navigate back to your domain details page in AutoSend
3. Click the **“Verify Ownership”** or **“Check Verification”** button
### Verification Statuses
Your domain will progress through these statuses:
| **Status** | **Badge Color** | **Description** | **Action Required** |
| ----------------------- | --------------- | ------------------------------------------- | ------------------------------------------- |
| **Unverified** | Red | No DNS records found | Add DNS records to your provider |
| **Pending** | Orange | DNS records found, verification in progress | Wait for DNS propagation (5 mins - 2 hours) |
| **Verified** | Green | Domain fully verified and ready to use | None - you can start sending! |
| **Verification Failed** | Red | DNS records incorrect or missing | Check your DNS records and try again |
When your domain shows a green “Verified” badge, you’re all set! You can now use this domain to
send emails through AutoSend’s API.
## Next Step: Warm Up Your Domain
If you're sending marketing emails, we strongly recommend warming up your new domain before sending to your full list. This builds sender reputation and improves deliverability.
Read our Domain Warmup Guide to learn how.
**New domains are limited to 300 emails/day.** Since your domain is new, sending too many emails too quickly can hurt your deliverability and trigger spam filters. This limit also protects against unauthorized use. As your domain builds a sending reputation, the limit will be lifted automatically - or you can reach out to to request an early removal.
## Troubleshooting
### Domain Not verifying?
If your domain verification is stuck or failing, try these steps:
DNS changes can take time to propagate globally. Use a DNS checker tool to verify your records are visible:
*
DNS Checker
*
WhatsMyDNS
Enter your domain and check if the records appear globally.
* Ensure you copied the **exact** values from AutoSend (including any underscores or periods) -
Check for extra spaces or hidden characters - Verify the record **Type** is correct (CNAME vs TXT)
Some DNS providers automatically append your domain name to records. For example:
* AutoSend shows: `_amazonses.mail.yourdomain.com`
* Your provider might only need: `_amazonses.mail` or `_amazonses`
Check your provider's documentation for the correct format.
DNS propagation typically takes:
* **5-30 minutes** for most providers
* **Up to 2 hours** for some providers
* **Up to 48 hours** in rare cases
Be patient and try verifying again after waiting.
If records still aren't working:
1. Delete all AutoSend-related DNS records
2. Wait 10-15 minutes
3. Re-add all records carefully
4. Wait for propagation
5. Try verification again
### Common DNS Provider Instructions
* Log in to Cloudflare dashboard
* Select your domain
* Go to **DNS** > **Records**
* Click **Add record**
* Set **Proxy status** to "DNS only" (gray cloud)
* Log in to GoDaddy
* Go to **My Products** > **DNS**
* Click **Add** to create new records
* Note: GoDaddy auto-appends your domain
* Log in to Namecheap
* Go to **Domain List** > **Manage**
* Go to **Advanced DNS** tab
* Click **Add New Record**
* Open Route53 console
* Select your hosted zone
* Click **Create Record**
* Use simple routing
# Send Emails with QStash
Source: https://docs.autosend.com/guides/QStash
Use Upstash QStash to queue, schedule, retry, and reliably deliver emails through AutoSend.
## Overview
[QStash](https://upstash.com/docs/qstash/overall/getstarted) is a serverless message queue by Upstash. It lets you publish HTTP requests that get delivered reliably with automatic retries, delays, scheduling, and dead-letter queues.
Because QStash can publish to any HTTP endpoint, it works with AutoSend out of the box. You do not need a dedicated integration. QStash forwards your request (including the `Authorization` header) directly to the AutoSend API.
**This is useful when you want to:**
* Send emails reliably without blocking your main request
* Retry failed email sends automatically
* Schedule emails for a future time (e.g., 24 hours after signup)
* Queue high-volume sends without overwhelming your app
## Prerequisites
Before you start, make sure you have:
* An **AutoSend account** with an API key - get one at [autosend.com](https://autosend.com)
* An **Upstash account** with a QStash token - get one at [upstash.com](https://upstash.com)
* Node.js 18+ (or any environment that supports `fetch`)
## How It Works
QStash acts as a proxy between your app and the AutoSend API. Instead of calling `https://api.autosend.com/v1/mails/send` directly, you publish the request to QStash. QStash then delivers it to AutoSend, handling retries and scheduling for you.
Your App → QStash → AutoSend API → Email delivered
Your `Authorization: Bearer ` header is forwarded to AutoSend using QStash's header-forwarding mechanism (`Upstash-Forward-*`).
## Installation
Install the QStash JavaScript SDK:
```bash theme={null}
npm install @upstash/qstash
```
## Quickstart
Add these to your `.env` file:
```bash .env theme={null}
QSTASH_TOKEN=your_qstash_token_here
AUTOSEND_API_KEY=your_autosend_api_key_here
```
Get your AutoSend API key from the API Keys page in your dashboard, and your QStash token from the [Upstash console](https://console.upstash.com/qstash).
Use `client.publishJSON` with the AutoSend mail send endpoint as the URL. Pass your AutoSend API key in the `Upstash-Forward-Authorization` header. QStash strips the `Upstash-Forward-` prefix before forwarding the request, so AutoSend receives a standard `Authorization: Bearer ...` header.
```javascript theme={null}
import { Client } from "@upstash/qstash";
const client = new Client({
token: process.env.QSTASH_TOKEN,
});
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
body: {
from: {
email: "hello@yourdomain.com",
name: "Your Company",
},
to: {
email: "user@example.com",
name: "User Name",
},
subject: "Welcome to our platform",
html: "Welcome!
Thanks for signing up.
",
},
});
console.log(res.messageId); // QStash message ID
```
`publishJSON` automatically sets `Content-Type: application/json` and serializes the body.
**Response from QStash:**
```json theme={null}
{
"messageId": "msg_xxxxxxxxxxxxxxxx"
}
```
This is the QStash message ID, not the AutoSend email ID. The email is queued and will be delivered asynchronously.
For bulk sends, use the bulk send endpoint and provide a `recipients` array instead of a single `to` field.
```javascript theme={null}
import { Client } from "@upstash/qstash";
const client = new Client({
token: process.env.QSTASH_TOKEN,
});
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/bulk",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
body: {
from: {
email: "hello@yourdomain.com",
name: "Your Company",
},
subject: "Your monthly newsletter",
html: "This month in product
Here's what shipped...
",
recipients: [
{ email: "alice@example.com", name: "Alice" },
{ email: "bob@example.com", name: "Bob" },
{ email: "carol@example.com", name: "Carol" },
],
},
});
console.log(res.messageId);
```
If you have a template configured in AutoSend, pass `templateId` and `dynamicData` instead of `html`. Learn more about email templates and template variables.
```javascript theme={null}
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
body: {
from: {
email: "hello@yourdomain.com",
name: "Your Company",
},
to: {
email: "user@example.com",
name: "User Name",
},
templateId: "your-template-id",
dynamicData: {
firstName: "John",
orderNumber: "ORD-12345",
trackingUrl: "https://track.example.com/ORD-12345",
},
},
});
```
## Scheduling and Retries
### Schedule a Delayed Email
Use the `delay` option to send an email at a future time. This is useful for follow-up sequences, onboarding drips, or reminders.
```javascript theme={null}
// Send 24 hours from now
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
delay: "24h", // supports: "10s", "5m", "2h", "1d"
body: {
from: {
email: "hello@yourdomain.com",
name: "Your Company",
},
to: {
email: "user@example.com",
},
subject: "Just checking in",
html: "It's been 24 hours since you signed up. Need any help?
",
},
});
```
You can also schedule for an exact Unix timestamp using `notBefore`:
```javascript theme={null}
const sendAt = Math.floor(Date.now() / 1000) + 3 * 24 * 60 * 60; // 3 days from now
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
notBefore: sendAt,
body: {
from: { email: "hello@yourdomain.com", name: "Your Company" },
to: { email: "user@example.com" },
subject: "Your trial ends soon",
html: "Your 7-day trial ends in 3 days.
",
},
});
```
### Configure Retries
By default, QStash retries failed requests 3 times. You can configure this:
```javascript theme={null}
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
retries: 5, // retry up to 5 times on failure
body: {
from: { email: "hello@yourdomain.com", name: "Your Company" },
to: { email: "user@example.com" },
subject: "Important notification",
html: "This message is important.
",
},
});
```
A retry is triggered when AutoSend returns a non-2xx HTTP response. QStash uses exponential backoff between retries.
**How responses are handled:**
* **2xx response** - Email successfully queued by AutoSend. No retry needed.
* **Non-2xx response** - QStash retries automatically based on your retry configuration.
Common errors that trigger retries include invalid API keys, unverified sender domains, missing required fields (`to`, `from`), and missing content (`html`, `text`, or `templateId`).
## Advanced Features
### Using Queues
If you want concurrency control, for example limiting how many emails are sent per second, use a QStash queue with `enqueueJSON`.
```javascript theme={null}
import { Client } from "@upstash/qstash";
const client = new Client({
token: process.env.QSTASH_TOKEN,
});
const queue = client.queue({ queueName: "email-queue" });
const res = await queue.enqueueJSON({
url: "https://api.autosend.com/v1/mails/bulk",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
body: {
from: { email: "hello@yourdomain.com", name: "Your Company" },
subject: "Campaign blast",
html: "Big announcement!
",
recipients: [
{ email: "alice@example.com", name: "Alice" },
{ email: "bob@example.com", name: "Bob" },
],
},
});
console.log(res.messageId);
```
Messages in a queue are processed in order. You can configure `parallelism` on the queue in your Upstash console.
### Using a Callback URL
If you want to know when QStash has successfully delivered the email request to AutoSend, you can pass a `callback` URL. QStash will POST the response from AutoSend to this URL after delivery.
```javascript theme={null}
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
callback: "https://yourapp.com/api/email-callback",
failureCallback: "https://yourapp.com/api/email-failure",
body: {
from: { email: "hello@yourdomain.com", name: "Your Company" },
to: { email: "user@example.com" },
subject: "Hello",
html: "Hello!
",
},
});
```
Your callback endpoint will receive a POST request containing the AutoSend API response body.
**Example callback handler (Next.js App Router):**
```javascript theme={null}
// app/api/email-callback/route.js
export async function POST(req) {
const body = await req.json();
// body is a QStash envelope, not the raw AutoSend response
let autosendResponse;
try {
autosendResponse = JSON.parse(atob(body.body));
} catch {
console.error("Failed to parse AutoSend response:", body.body);
return Response.json({ ok: false }, { status: 400 });
}
// autosendResponse = { success: true, data: { emailId: "em_xxx", ... } }
console.log("Email delivered:", autosendResponse);
return Response.json({ ok: true });
}
```
Always [verify the QStash signature](https://upstash.com/docs/qstash/features/security) on your callback endpoint to confirm the request came from QStash. The `@upstash/qstash` SDK provides a `Receiver` class for this.
### Using Deduplication
If your app might accidentally publish the same email twice (e.g., due to a retry on your side), use a `deduplicationId` to prevent duplicates.
```javascript theme={null}
const res = await client.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
deduplicationId: `welcome-email-user-${userId}`, // unique per email intent
body: {
from: { email: "hello@yourdomain.com", name: "Your Company" },
to: { email: userEmail },
subject: "Welcome!",
html: "Welcome to our platform!
",
},
});
```
If a message with the same `deduplicationId` was already published in the last 24 hours, QStash will reject the duplicate silently.
## Complete Examples
A full example of a Next.js API route that queues a welcome email when a user signs up.
```javascript expandable theme={null}
// app/api/signup/route.js
import { Client } from "@upstash/qstash";
const qstash = new Client({
token: process.env.QSTASH_TOKEN,
});
export async function POST(req) {
const { email, name } = await req.json();
// 1. Save user to your database
// await db.users.create({ email, name });
// 2. Queue welcome email via QStash -> AutoSend
try {
const res = await qstash.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
deduplicationId: `welcome-${email}`,
retries: 3,
body: {
from: {
email: "hello@yourdomain.com",
name: "Your Company",
},
to: {
email,
name,
},
subject: `Welcome, ${name}!`,
html: `
Welcome aboard, ${name}!
We're excited to have you.
If you have any questions, just reply to this email.
`,
},
});
console.log("Email queued:", res.messageId);
} catch (err) {
// QStash publish failed - log and continue, don't block signup
console.error("Failed to queue welcome email:", err);
}
return Response.json({ success: true });
}
```
Queue multiple emails at different delays when a user signs up.
```javascript expandable theme={null}
import { Client } from "@upstash/qstash";
const qstash = new Client({
token: process.env.QSTASH_TOKEN,
});
async function queueOnboardingSequence(user) {
const emailBase = {
from: { email: "hello@yourdomain.com", name: "Your Company" },
to: { email: user.email, name: user.name },
};
const emails = [
{
subject: "Welcome!",
html: `Hi ${user.name}, welcome to our platform!
`,
deduplicationId: `onboard-day0-${user.id}`,
},
{
delay: "1d", // day 1
subject: "Getting started tips",
html: `Hi ${user.name}, here are 3 things to try today...
`,
deduplicationId: `onboard-day1-${user.id}`,
},
{
delay: "3d", // day 3
subject: "How's it going?",
html: `Hi ${user.name}, just checking in. Need any help?
`,
deduplicationId: `onboard-day3-${user.id}`,
},
{
delay: "7d", // day 7
subject: "Your first week",
html: `You've been with us for a week, ${user.name}. Here's what's new...
`,
deduplicationId: `onboard-day7-${user.id}`,
},
];
await Promise.all(
emails.map(({ delay, subject, html, deduplicationId }) =>
qstash.publishJSON({
url: "https://api.autosend.com/v1/mails/send",
headers: {
"Upstash-Forward-Authorization": `Bearer ${process.env.AUTOSEND_API_KEY}`,
},
delay,
deduplicationId,
body: {
...emailBase,
subject,
html,
},
})
)
);
}
```
## Header Reference
| Header | Description |
| ------------------------------- | ---------------------------------------------------------------------------------------------- |
| `Upstash-Forward-Authorization` | Forwards `Authorization: Bearer ` to AutoSend. Required for authentication. |
| `Upstash-Forward-Content-Type` | Forwards `Content-Type` to AutoSend. The SDK sets this automatically when using `publishJSON`. |
Any header prefixed with `Upstash-Forward-` is stripped of the prefix and forwarded to the destination URL. This is how QStash passes your AutoSend API key without exposing it as a QStash-level credential.
## When to Use This Integration
Use QStash with AutoSend when you need:
* **Scheduled email delivery** - send emails at a specific time without a cron job
* **Retry handling without custom logic** - QStash retries failed requests automatically
* **Async processing for heavy workloads** - offload email sending from your main request
* **Decoupled architecture** - separate email delivery from your application logic
## Best Practices
`publishJSON` returns a `messageId` as soon as QStash accepts your message. AutoSend is called asynchronously after that. Avoid doing heavy processing before publishing to QStash.
QStash may retry requests on transient failures. To avoid sending duplicate emails, always include a unique `deduplicationId` tied to the email intent (e.g., `welcome-${userId}`). See the [deduplication section](#using-deduplication) for details.
Always send your AutoSend API key via the `Upstash-Forward-Authorization` header. Never expose it in client-side code or include it in request bodies.
Keep request payloads lightweight. Store large files externally and reference them when possible. Attachments must stay within AutoSend's size limits.
Prefer `templateId` with `dynamicData` instead of large inline HTML payloads. Templates are more performant, easier to maintain, and can be updated without code changes. Learn more about email templates.
## What QStash Does Not Handle
QStash handles reliable delivery to the AutoSend API. The following are handled by AutoSend, not QStash:
* Whether the email was actually delivered to the recipient's inbox
* Bounce and complaint handling
* Unsubscribe tracking
* Open and click tracking
For those, refer to the webhooks documentation.
## Troubleshooting
Check the QStash dashboard in your Upstash console. You can see message status, retry attempts, and the response body from AutoSend. If the message is in the Dead Letter Queue (DLQ), the AutoSend API returned a persistent error.
**Solution:** Review the message details in the [Upstash console](https://console.upstash.com/qstash) and check your Email Activity dashboard for delivery status.
Make sure you are passing `Upstash-Forward-Authorization` (not `Authorization`) in the headers. The `Authorization` header goes to QStash for QStash authentication. `Upstash-Forward-Authorization` is forwarded to AutoSend.
**Solution:** Verify your header name and API key value. Check your key in the API Keys dashboard.
Double-check your `url` is `https://api.autosend.com/v1/mails/send` for single sends or `https://api.autosend.com/v1/mails/bulk` for bulk sends.
**Solution:** Refer to the API Reference for the correct endpoint URLs.
Use a `deduplicationId` that is unique per email intent (e.g., `welcome-${userId}`). See the [deduplication section](#using-deduplication) above.
**Solution:** Add a unique `deduplicationId` to every `publishJSON` call where duplicate sends are possible.
## Next Steps
Create reusable, personalized email templates for transactional emails.
Use webhooks to notify your application about email and contact events in real-time.
Verify and authenticate your domain on AutoSend for sending emails.
Track delivery, performance, and troubleshoot issues for your transactional emails.
# Send emails from Auth0 with a Custom Email Provider Action
Source: https://docs.autosend.com/guides/auth0-custom-action
Use the AutoSend API to deliver Auth0 authentication emails when SMTP is not an option.
If SMTP isn't working for your Auth0 tenant, or you'd rather call the AutoSend API directly, you can configure Auth0's [Custom Email Provider Action](https://auth0.com/docs/customize/email/smtp-email-providers/custom/configure-action) to send authentication emails through the AutoSend Send Mail API.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a new API key for the Custom Action or use an existing one.
## Configuration
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/)
2. Go to **Branding** in the sidebar
3. Click on **Email Provider**
Toggle **Use my own email provider** to enable the custom provider configuration.
Select **Custom Provider** as your email provider type. Auth0 will create a Custom Email Provider Action that runs every time an authentication email needs to be sent.
Replace the default action code with the snippet below. It forwards every Auth0 notification to the AutoSend Send Mail API.
```javascript Auth0 Custom Email Provider Action theme={null}
exports.onExecuteCustomEmailProvider = async (event, api) => {
const { notification } = event;
try {
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${event.secrets.AUTOSEND_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: {
name: 'AutoSend',
email: notification.from || 'no-reply@yourdomain.com',
},
to: { email: notification.to || event.user.email || 'receiver@yourdomain.com' },
subject: notification.subject,
html: notification.html,
text: notification.text
})
});
if (!response.ok) {
const error = await response.text();
api.notification.drop(`Send failed: ${error}`);
return;
}
} catch (error) {
console.error(`Error sending email: ${error.message}`);
api.notification.drop(`An unexpected error occurred. Error: ${error.message}`);
}
};
```
Update the `from.email` to an address on your verified domain as a falls back to `notification.from` value which is sent by Auth0.
You'll need an AutoSend API key for the next step. Create one from the API Keys page in your dashboard, or follow the API reference for details on authentication.
In the Action editor, you can click the key icon to add secrets on the left panel and add a new secret named `AUTOSEND_API_KEY` with your AutoSend API key (`AS_xxx`) as the value. The action reads it via `event.secrets.AUTOSEND_API_KEY`.
Never paste the API key directly into the action code. Secrets keep the key out of the action source and out of execution logs.
Click **Deploy**/**Save** in the top right of the Action editor to publish your changes. Auth0 will start routing authentication emails through the action immediately.
After saving, click the **Send Test Email** button next to the Save button to send a test email and verify your configuration is working.
The test email is delivered to the address in the **last field** of the `to` json object `'receiver@yourdomain.com'`. The other fields (`notification.from`, `notification.subject`, etc.) are populated by Auth0 with sample values; only the recipient address controls where the message lands.
You can also check the Email Activity dashboard to verify the email was sent through AutoSend.
## Viewing Logs
Auth0 captures every Custom Action execution, which is the fastest way to debug send failures.
Go to [Dashboard > Monitoring > Logs](https://manage.auth0.com/#/logs) to view log events for your tenant. See Auth0's [View Log Events](https://auth0.com/docs/deploy-monitor/logs/view-log-events#view-logs) guide for filtering options.
For the Custom Email Provider Action specifically, you can also inspect per-invocation output (including `console.error` and any `api.notification.drop` reasons) from the action editor under **Actions** → **Library** → your action → **Logs** tab.
## Email Templates
Auth0 still owns the email content when you use a Custom Action - the action only delivers what Auth0 hands you in `notification.subject`, `notification.html`, and `notification.text`. To customize the content, edit the templates under **Branding** → **Email Templates**:
* **Verification Email** - Sent when users need to verify their email address
* **Welcome Email** - Sent after successful signup
* **Change Password** - Sent for password reset requests
* **Blocked Account** - Sent when an account is blocked
* **Passwordless Email** - Sent for passwordless authentication (magic links/codes)
Auth0 email templates use Liquid syntax for dynamic content. Variables like `{{ user.email }}` and `{{ url }}` are replaced with actual values before the rendered HTML reaches your action.
## Troubleshooting
AutoSend rejects sends from unverified domains. Update the `from.email` in the action to an address on a domain you've verified in Domain Settings.
The `AUTOSEND_API_KEY` secret is missing, misspelled, or revoked. Re-check the secret name in the action and confirm the key is active in your API Keys dashboard.
1. Check the action **Logs** tab for `Send failed` entries.
2. Look up the recipient in the Email Activity dashboard.
3. Confirm the address isn't in Suppressions.
Only the last field of Auth0's test form controls the recipient. The other fields are sample values used to populate `notification.*` and don't change where the message is sent.
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Reference for the AutoSend Send Mail endpoint used by the action
# Send emails with Better Auth
Source: https://docs.autosend.com/guides/better-auth
Learn how to send authentication emails using AutoSend with Better Auth for email verification, password reset, and OTP.
This guide shows you how to integrate AutoSend with [Better Auth](https://www.better-auth.com) to send authentication emails. Better Auth is a framework-agnostic authentication library for TypeScript that supports email verification, password reset, and OTP-based authentication.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a new API key for SMTP authentication or use the existing one.
## Installation
Install Better Auth in your project if you haven't already:
```bash theme={null}
npm install better-auth
```
## Integration
Add the required environment variables to your `.env` file:
```bash .env theme={null}
AUTOSEND_API_KEY=your_autosend_api_key_here
FROM_EMAIL=noreply@yourdomain.com
FROM_NAME=Your App Name
```
Get your API key from the [API Keys](https://autosend.com/settings/api-key) page in your AutoSend dashboard.
Create a helper function to send emails via the AutoSend API using templates:
```typescript lib/autosend.ts theme={null}
const AUTOSEND_API_KEY = process.env.AUTOSEND_API_KEY!;
const FROM_EMAIL = process.env.FROM_EMAIL || "noreply@yourdomain.com";
const FROM_NAME = process.env.FROM_NAME || "Your App";
interface SendEmailOptions {
to: string;
templateId: string;
dynamicData: Record;
}
export async function sendEmail({ to, templateId, dynamicData }: SendEmailOptions) {
const response = await fetch("https://api.autosend.com/v1/mails/send", {
method: "POST",
headers: {
Authorization: `Bearer ${AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: {
email: FROM_EMAIL,
name: FROM_NAME,
},
to: {
email: to,
},
templateId,
dynamicData,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to send email");
}
return response.json();
}
```
Create email templates in your [AutoSend dashboard](https://app.autosend.com) for each authentication flow:
| Template | Suggested ID | Variables |
| ------------------ | --------------------- | ------------------------------------- |
| Email verification | `tmpl_verify_email` | `{{userName}}`, `{{verificationUrl}}` |
| Password reset | `tmpl_password_reset` | `{{userName}}`, `{{resetUrl}}` |
| Sign-in OTP | `tmpl_otp_signin` | `{{otp}}` |
| Verification OTP | `tmpl_otp_verify` | `{{otp}}` |
| Password reset OTP | `tmpl_otp_reset` | `{{otp}}` |
Learn more about template variables.
Wire up your Better Auth configuration to send emails through AutoSend:
```typescript lib/auth.ts theme={null}
import { betterAuth } from "better-auth";
import { emailOTP } from "better-auth/plugins";
import { sendEmail } from "./autosend";
export const auth = betterAuth({
// ... your database and other configuration
emailVerification: {
sendOnSignUp: true,
sendVerificationEmail: async ({ user, url }) => {
sendEmail({
to: user.email,
templateId: "tmpl_verify_email",
dynamicData: {
userName: user.name || "there",
verificationUrl: url,
},
});
},
},
emailAndPassword: {
enabled: true,
sendResetPassword: async ({ user, url }) => {
sendEmail({
to: user.email,
templateId: "tmpl_password_reset",
dynamicData: {
userName: user.name || "there",
resetUrl: url,
},
});
},
},
plugins: [
emailOTP({
sendVerificationOTP: async ({ email, otp, type }) => {
const templateIds = {
"sign-in": "tmpl_otp_signin",
"email-verification": "tmpl_otp_verify",
"forget-password": "tmpl_otp_reset",
};
sendEmail({
to: email,
templateId: templateIds[type],
dynamicData: { otp },
});
},
}),
],
});
```
Don't await email sending to prevent timing attacks. This ensures response time doesn't reveal whether an email was sent.
For client-side setup (sending OTPs, verifying codes), see the [Better Auth Email OTP documentation](https://www.better-auth.com/docs/plugins/email-otp).
## Serverless Considerations
When running on serverless platforms (Vercel, AWS Lambda, Cloudflare Workers), ensure email sending completes before the function terminates.
Use `waitUntil` to ensure the email is sent:
```typescript theme={null}
import { waitUntil } from '@vercel/functions';
import { sendEmail } from './autosend';
// In your Better Auth config
sendVerificationEmail: async ({ user, url }) => {
waitUntil(
sendEmail({
to: user.email,
templateId: 'tmpl_verify_email',
dynamicData: {
userName: user.name || 'there',
verificationUrl: url,
},
})
);
};
```
Use the `ctx.waitUntil` method:
```typescript theme={null}
sendVerificationEmail: async ({ user, url }) => {
// Access ExecutionContext from your request handler
ctx.waitUntil(
sendEmail({
to: user.email,
templateId: 'tmpl_verify_email',
dynamicData: {
userName: user.name || 'there',
verificationUrl: url,
},
})
);
};
```
## Troubleshooting
If you receive a "Domain not verified" error, ensure your sending domain is properly configured in AutoSend.
**Solution:** Go to your [Domain Settings](https://autosend.com/settings/domains) and complete the DNS verification process.
A 401 error indicates your API key is invalid or missing.
**Solution:**
1. Check your `.env` file has the correct `AUTOSEND_API_KEY`
2. Verify the key in your [API Keys](https://autosend.com/settings/api-key) dashboard
3. Ensure the key hasn't been revoked
If you're sending too many emails, you may hit rate limits.
**Solution:** Review the Rate Limits documentation and implement appropriate throttling in your application.
If emails aren't being delivered:
1. Check the [Email Activity](https://autosend.com/email-activities) dashboard for delivery status
2. Verify the recipient email is valid
3. Check if the email is in [Suppressions](https://autosend.com/suppressions/global)
4. Review the Troubleshooting Guide for common issues
## Next Steps
Create reusable, personalized email templates for transactional emails.
Use webhooks to notify your application about email and contact events in real-time.
Here's a step-by-step guide on how to verify and authenticate domain on AutoSend for sending
emails.
Track delivery, performance, and troubleshoot issues for your transactional emails.
# Send emails with Convex
Source: https://docs.autosend.com/guides/convex
Use the official AutoSend Convex component to send transactional emails with built-in queueing, retries, and webhook tracking.
## Overview
The official [AutoSend Convex component](https://www.convex.dev/components/autosend/convex) is a drop-in integration for sending transactional email from your Convex backend. It wraps the AutoSend API with queueing, deterministic idempotency, automatic retries, webhook verification, and full delivery lifecycle tracking, so you only need to call `sendEmail` from a mutation and the component handles the rest.
The package is open source on [GitHub](https://github.com/autosendhq/autosend-convex) and published on [npm](https://www.npmjs.com/package/@autosend/convex) as `@autosend/convex`.
The component runs entirely inside your Convex deployment. Email jobs, retry state, and webhook
events are stored in Convex tables, so you do not need any extra infrastructure to track delivery.
## Install with your AI assistant
If you use an AI coding assistant (Cursor, Claude Code, Copilot, Codex, etc.), paste the prompt below into your editor. It points the assistant at the official Convex component docs and `llms.txt`, then asks it to produce a setup checklist tailored to your project.
```text Install prompt [expandable] theme={null}
Help me install the AutoSend component.
Package: @autosend/convex
Install: npm install @autosend/convex
Documentation:
- https://www.convex.dev/components/autosend/convex/convex.md
- https://www.convex.dev/components/autosend/convex/llms.txt
Please:
1. Retrieve the install command and documentation
2. Generate an exact setup checklist for this component
3. List any required environment variables
4. Provide verification steps
```
Prefer to wire it up yourself? Skip ahead to Install and follow the manual
steps.
## What you get
* **Queue-first sending.** `sendEmail` and `sendBulk` enqueue jobs and automatically trigger queue processing.
* **Idempotency.** Duplicate requests resolve to the same `emailId`, so retried mutations never double-send.
* **Retries with backoff.** Network errors, `429`, and `5xx` responses are retried on a configurable schedule.
* **Lifecycle tracking.** Every email moves through `queued`, `sending`, `retrying`, `sent`, `failed`, or `canceled`.
* **Webhook ingestion.** HMAC SHA-256 signature verification, timestamp skew protection, and per-delivery dedupe out of the box.
* **Templates, CC/BCC, attachments,** and unsubscribe groups are all supported.
* **Test sandbox.** Optional `sandboxTo` rewrites recipients while you are still wiring things up.
## Prerequisites
Before you begin, make sure you have:
* An [AutoSend account](https://autosend.com/) with an API key
* A verified sending domain
* A [Convex](https://www.convex.dev/) project (Convex CLI installed and `npx convex dev` runnable)
* Node.js 18+ installed locally
## Install
Add the component and the Convex SDK to your project:
```bash npm theme={null}
npm install @autosend/convex convex
```
```bash pnpm theme={null}
pnpm add @autosend/convex convex
```
```bash yarn theme={null}
yarn add @autosend/convex convex
```
## Setup
Add the component to your Convex app definition.
```ts convex/convex.config.ts theme={null}
import { defineApp } from "convex/server";
import autosend from "@autosend/convex/convex.config.js";
const app = defineApp();
app.use(autosend, { name: "autosend" });
export default app;
```
Wrap the generated component reference in an `AutoSend` instance you can reuse from any Convex function.
```ts convex/email.ts theme={null}
import { AutoSend } from "@autosend/convex";
import { components } from "./_generated/api";
export const autosend = new AutoSend(components.autosend);
```
Push your AutoSend API key and webhook signing secret to the Convex deployment environment. Both are read by the component at runtime.
```bash theme={null}
npx convex env set AUTOSEND_API_KEY
npx convex env set AUTOSEND_WEBHOOK_SECRET
```
The webhook secret is shown when you create a webhook endpoint in the AutoSend dashboard. See Verifying webhook requests for details.
The component keeps its own config in a Convex table so it can read settings from queries, mutations, and actions. Call `setConfig` once from a mutation to write it.
```ts convex/admin.ts theme={null}
import { mutation } from "./_generated/server";
import { autosend } from "./email";
export const configureAutosend = mutation({
args: {},
handler: async (ctx) => {
await autosend.setConfig(ctx, {
config: {
autosendApiKey: process.env.AUTOSEND_API_KEY!,
webhookSecret: process.env.AUTOSEND_WEBHOOK_SECRET!,
defaultFrom: "noreply@yourdomain.com",
testMode: true,
sandboxTo: ["you@yourdomain.com"],
},
});
},
});
```
Run the mutation once from the Convex dashboard, or call it from a setup script. Subsequent calls merge new values into the existing config unless you pass `replace: true`.
Leave `testMode: true` while you are wiring things up. Every send will be rewritten to the addresses in `sandboxTo`, so you cannot accidentally email real users. Flip it to `false` when you are ready to go live.
The component ships an HTTP handler that verifies AutoSend webhook signatures and updates the email status table. Register it on your Convex HTTP router.
```ts convex/http.ts theme={null}
import { httpRouter } from "convex/server";
import { registerRoutes } from "@autosend/convex";
import { components } from "./_generated/api";
const http = httpRouter();
registerRoutes(http, components.autosend);
export default http;
```
The route is mounted at `/webhooks/autosend` by default. After your next `npx convex deploy`, point your AutoSend webhook endpoint at:
```
https://YOUR-DEPLOYMENT.convex.site/webhooks/autosend
```
Pass `{ path: "/custom/path" }` as a third argument to `registerRoutes` if you need a different URL.
## Send your first email
Call `sendEmail` from any Convex mutation. The component enqueues the job and triggers the processor automatically, so you do not need to schedule anything yourself.
```ts convex/sendWelcome.ts theme={null}
import { mutation } from './_generated/server';
import { v } from 'convex/values';
import { autosend } from './email';
export const sendWelcome = mutation({
args: { email: v.string(), name: v.string() },
handler: async (ctx, { email, name }) => {
const { emailId } = await autosend.sendEmail(ctx, {
to: [email],
toName: name,
subject: 'Welcome to Acme',
html: `Hi ${name}
Thanks for signing up.
`,
});
return emailId;
},
});
```
If you omit `from`, the component uses the `defaultFrom` you set in config. The returned `emailId` is the handle you use to query status and webhook events later.
## Sending variants
Reference an AutoSend email template by ID and pass merge fields via `dynamicData`.
```ts theme={null}
await autosend.sendEmail(ctx, {
to: ["jane@example.com"],
templateId: "tmpl_welcome",
dynamicData: {
firstName: "Jane",
loginUrl: "https://app.example.com/login",
},
});
```
When you use `templateId`, `subject` and `html` are optional. See template variables for the syntax supported in your template.
```ts theme={null}
await autosend.sendEmail(ctx, {
to: ["customer@example.com"],
cc: [{ email: "team@example.com", name: "Support Team" }],
bcc: [{ email: "archive@example.com" }],
replyTo: "support@example.com",
replyToName: "Acme Support",
subject: "Your order",
html: "Thanks for your purchase.
",
});
```
Attach files inline as base64 or by URL.
```ts theme={null}
await autosend.sendEmail(ctx, {
to: ["customer@example.com"],
subject: "Your invoice",
html: "Invoice attached.
",
attachments: [
{
filename: "invoice.pdf",
fileUrl: "https://files.example.com/invoice.pdf",
contentType: "application/pdf",
},
{
filename: "summary.csv",
content: "base64-encoded-content",
contentType: "text/csv",
},
],
});
```
Provide either `fileUrl` or `content`, never both.
Send the same payload to up to 100 recipients in one call.
```ts theme={null}
await autosend.sendBulk(ctx, {
recipients: ["alice@example.com", "bob@example.com"],
subject: "Product update",
html: "We shipped new features this week.
",
});
```
Each recipient is queued as a separate email, so retries and webhook events are tracked per recipient.
Pass `recipientData` keyed by email to interpolate `{{placeholders}}` into `subject`, `html`, and `text` per recipient.
```ts theme={null}
await autosend.sendBulk(ctx, {
recipients: ["alice@example.com", "bob@example.com"],
recipientData: {
"alice@example.com": { name: "Alice", role: "admin" },
"bob@example.com": { name: "Bob", role: "member" },
},
subject: "Welcome, {{name}}",
html: "Hi {{name}}, you are now a {{role}}.
",
});
```
Per-recipient values are also forwarded as `dynamicData` for that recipient, overriding any shared `dynamicData` you pass.
Pass `idempotencyKey` to guarantee a single send for a given logical operation. If your mutation runs twice (Convex retries on conflict), the second call returns the same `emailId` with `deduped: true`.
```ts theme={null}
await autosend.sendEmail(ctx, {
to: ["customer@example.com"],
subject: "Order #1234 confirmation",
html: "Thanks for your order.
",
idempotencyKey: `order:1234:confirmation`,
});
```
Without an explicit key, the component derives one from the payload, so identical payloads also deduplicate.
## Track status and events
The component stores every email and its webhook events in Convex, so you can query them from any Convex query.
```ts convex/emailStatus.ts theme={null}
import { query } from './_generated/server';
import { v } from 'convex/values';
import { autosend } from './email';
export const getEmailStatus = query({
args: { emailId: v.string() },
handler: async (ctx, { emailId }) => {
const email = await autosend.status(ctx, { emailId });
const events = await autosend.listEvents(ctx, { emailId, limit: 20 });
return { email, events };
},
});
```
Other status helpers:
```ts theme={null}
// Batch status for multiple emails in one call
const statuses = await autosend.statusBatch(ctx, {
emailIds: [id1, id2, id3],
});
// Cancel an email that has not been sent yet
const { canceled } = await autosend.cancelEmail(ctx, { emailId });
```
`cancelEmail` only works while the email is in `queued` or `retrying`. Once it reaches `sending`, `sent`, or `failed`, it is locked in.
### Email lifecycle
| Status | Meaning |
| ---------- | --------------------------------------------------- |
| `queued` | Accepted and waiting to be claimed by the processor |
| `sending` | Currently being sent by the queue processor |
| `retrying` | Previous attempt failed and a retry is scheduled |
| `sent` | Accepted by AutoSend |
| `failed` | Retries exhausted or non-retryable error |
| `canceled` | Canceled via `cancelEmail` before send |
Default retry policy is 4 attempts total with delays of 5s, 10s, and 20s. Both are tunable via `maxAttempts` and `retryDelaysMs` in config.
## Configuration
`setConfig` accepts these fields. Anything you omit keeps its existing value (unless you pass `replace: true`).
| Field | Type | Default | Description |
| ----------------- | ---------- | -------------------------- | ------------------------------------------------------ |
| `autosendApiKey` | `string` | unset | Bearer token for the AutoSend API |
| `webhookSecret` | `string` | unset | HMAC secret used to verify incoming webhooks |
| `defaultFrom` | `string` | unset | Fallback sender address when `from` is omitted |
| `defaultReplyTo` | `string` | unset | Fallback reply-to address |
| `testMode` | `boolean` | `true` | Rewrites every recipient to `sandboxTo` |
| `sandboxTo` | `string[]` | `[]` | Recipients used when `testMode` is enabled |
| `rateLimitRps` | `number` | `2` | Max sends per queue run |
| `retryDelaysMs` | `number[]` | `[5000, 10000, 20000]` | Delay schedule between retries |
| `maxAttempts` | `number` | `4` | Total attempts including the first try |
| `sendBatchSize` | `number` | `25` | Max queue items processed per run |
| `autosendBaseUrl` | `string` | `https://api.autosend.com` | API base URL (override for staging) |
| `projectId` | `string` | unset | Required when using an Account API Key (`ASA_` prefix) |
To read the current config (without leaking secrets), use `getConfig`. It returns every value above except `autosendApiKey` and `webhookSecret`, plus `hasApiKey` and `hasWebhookSecret` booleans you can use in admin UIs.
```ts theme={null}
const safeConfig = await autosend.getConfig(ctx);
```
AutoSend supports both Project API Keys (`AS_` prefix, scoped to one project) and Account API Keys
(`ASA_` prefix, cross-project). If you use an Account API Key, set `projectId` in config so every
request includes the matching project header.
## Go live checklist
Before flipping `testMode` to `false`:
1. Verify your sending domain in the AutoSend dashboard (domain setup).
2. Deploy your Convex backend so the webhook route is reachable.
3. Add the webhook URL `https://YOUR-DEPLOYMENT.convex.site/webhooks/autosend` to your AutoSend project and copy the signing secret into `AUTOSEND_WEBHOOK_SECRET`.
4. Send a test email with `testMode: true` and confirm the status moves to `sent` and you see `email.delivered` in `listEvents`.
5. Update config with `testMode: false` and a real `defaultFrom`.
## Troubleshooting
`sendEmail` triggers the processor automatically, but if a deploy was interrupted you can sweep the queue manually:
```ts theme={null}
await autosend.processQueue(ctx, { batchSize: 25 });
```
You can also schedule it as a Convex cron job to act as a safety net.
Make sure the `webhookSecret` in your component config matches the secret shown in the AutoSend dashboard for that exact endpoint. The component validates HMAC SHA-256 over the raw body and rejects timestamps older than 2 minutes. See verifying webhook requests for the full spec.
You are using an Account API Key (`ASA_` prefix) without setting `projectId`. Either switch to a Project API Key (`AS_` prefix), or add `projectId` to your config.
`testMode` is still `true`. Every send is being rewritten to `sandboxTo`. Update your config with `testMode: false` to send to real recipients.
## Resources
* [Component listing on Convex](https://www.convex.dev/components/autosend/convex)
* [Source code on GitHub](https://github.com/autosendhq/autosend-convex)
* [`@autosend/convex` on npm](https://www.npmjs.com/package/@autosend/convex)
* [Live demo](https://convex-autosend.vercel.app/)
# How to send emails from Auth0 with SMTP
Source: https://docs.autosend.com/guides/smtp/auth0
Configure Auth0 to send authentication emails through AutoSend SMTP.
Auth0 uses email for authentication flows like password resets, email verification, and passwordless login. By default, Auth0 uses its built-in email provider, but you can configure it to use AutoSend SMTP for better deliverability and email tracking.
If SMTP doesn't work for your Auth0 tenant, you can call the AutoSend API directly from a Custom Email Provider Action instead. See Send emails from Auth0 with a Custom Email Provider Action.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/)
2. Go to **Branding** in the sidebar
3. Click on **Email Provider**
Toggle **Use my own email provider** to enable custom SMTP configuration.
Select **SMTP Provider** as your email provider type.
Fill in the following settings:
| Field | Value |
| ----------------- | ---------------------------------------------------------- |
| **From** | `noreply@yourdomain.com` (must match your verified domain) |
| **SMTP Host** | `smtp.autosend.com` |
| **SMTP Port** | `587` |
| **SMTP Username** | `autosend` |
| **SMTP Password** | Your AutoSend SMTP key (AS\_xxx) |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Click **Save** to apply your SMTP settings.
After saving, click the **Send Test** button next to the Save button to send a test email and verify your configuration is working.
You can also check the Email Activity dashboard to verify the email was sent through AutoSend.
## Email Templates
Auth0 allows you to customize email templates for different authentication scenarios. Navigate to **Branding** → **Email Templates** to modify:
* **Verification Email** - Sent when users need to verify their email address
* **Welcome Email** - Sent after successful signup
* **Change Password** - Sent for password reset requests
* **Blocked Account** - Sent when an account is blocked
* **Passwordless Email** - Sent for passwordless authentication (magic links/codes)
Auth0 email templates use Liquid syntax for dynamic content. Variables like `{{ user.email }}` and `{{ url }}` are replaced with actual values when the email is sent.
## Troubleshooting
* Verify your SMTP credentials are correct - Check that your sender email domain is verified in
AutoSend - Ensure the From address matches a verified domain - Check Auth0 logs for SMTP
connection errors (**Monitoring** → **Logs**)
* Double-check your SMTP key is correct - Ensure you're using `autosend` as the username - Verify
your SMTP key is active in the SMTP Settings
* Verify the hostname is `smtp.autosend.com` - Try using port `465` with implicit TLS if port
`587` fails - Check if your network blocks outbound SMTP connections
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC) - Use a professional sender name,
not just "noreply" - Check your domain reputation in AutoSend
If SMTP fails after exhausting the steps above, switch to the API-based approach using Auth0's Custom Email Provider Action. See Send emails from Auth0 with a Custom Email Provider Action.
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Learn more about AutoSend SMTP configuration
# How to send emails from Customer.io with SMTP
Source: https://docs.autosend.com/guides/smtp/customerio
Configure Customer.io to send emails through AutoSend SMTP.
Customer.io is a messaging platform for marketing, transactional emails, push notifications, and in-app messages. By default, Customer.io uses its built-in email service, but you can configure it to use AutoSend SMTP for better deliverability and email tracking.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
1. Log in to your [Customer.io Dashboard](https://fly.customer.io/)
2. Go to **Settings** > **Workspace Settings**
3. Click on **Email**
1. Click on **Custom SMTP**
2. Select **Add Custom SMTP Server**
Select **Other SMTP** as your provider type and click **Continue to set up**.
Fill in the following settings:
| Field | Value |
| ------------------ | --------------------------------- |
| **Address** | `smtp.autosend.com` |
| **Port** | `587` (or `465` for implicit TLS) |
| **Authentication** | Select `Plain` |
| **Username** | `autosend` |
| **Password** | Your AutoSend SMTP key (AS\_xxx) |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Customer.io does not support port 25. Use port 587, 465, or 2525 instead.
Click **Finish set up** to save your configuration.
Before sending emails, add a sending domain in Customer.io that matches your verified domain in AutoSend.
1. Go to **Settings** → **Workspace Settings** → **Email**
2. Under **Sending Domains**, add your domain (e.g., `yourdomain.com`)
Create a test campaign or broadcast to verify your SMTP configuration is working correctly.
You can also check the Email Activity dashboard to verify the email was sent through AutoSend.
## Troubleshooting
* Verify your SMTP credentials are correct - Check that your sender email domain is verified in
AutoSend - Ensure you've added a sending domain in Customer.io - Check Customer.io's delivery
logs for SMTP connection errors
* Double-check your SMTP key is correct - Ensure you're using `autosend` as the username - Verify
your SMTP key is active in the SMTP Settings
* Verify the server address is `smtp.autosend.com` - Try using port `465` or `2525` if port
`587` fails - Remember that port 25 is not supported by Customer.io
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC) - Use a professional sender name
and address - Check your domain reputation in AutoSend
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Learn more about AutoSend SMTP configuration
# How to send emails from Descope with SMTP
Source: https://docs.autosend.com/guides/smtp/descope
Configure Descope to send authentication emails through AutoSend SMTP.
Descope uses email for authentication flows like OTP verification, magic links, and password resets. By default, Descope uses its built-in email delivery, but you can configure it to use AutoSend SMTP for better deliverability and email tracking.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
1. Go to your [Descope Console](https://app.descope.com/)
2. Navigate to **Connectors** in the sidebar.
1. Search for **SMTP**
2. Click the SMTP card to create a new SMTP connector.
Fill in the following settings:
| Field | Value |
| ------------------- | ---------------------------------------------------------- |
| **Connector Name** | `AutoSend` (or your preferred name) |
| **Server Hostname** | `smtp.autosend.com` |
| **SMTP Port** | `587` |
| **Username** | `autosend` |
| **Password** | Your AutoSend SMTP key (AS\_xxx) |
| **Sender Address** | `noreply@yourdomain.com` (must match your verified domain) |
| **Sender Name** | Your app name (e.g., "MyApp") |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Click the **Test** button to verify your SMTP configuration is working correctly. Check the Test Results panel for success or error details.
Click **Create** to save your SMTP connector.
1. Navigate to **Flows** in Descope
2. Select or create your authentication flow
3. Add a connector action where email sending is needed
4. Select your AutoSend SMTP connector
5. Configure the email recipient (typically `{{user.email}}`)
## Dynamic Values
Descope supports dynamic values in sender fields using template syntax:
* **Sender Address/Name**: Use `{{options_}}` format for dynamic sender configuration
* **Recipient**: Set the `To` field dynamically with `{{user.email}}`
Refer to Descope's [Dynamic Values documentation](https://docs.descope.com/flows/dynamic-keys) to learn how to set dynamic values when using messaging connectors.
## Troubleshooting
* Verify your SMTP credentials are correct
* Check that your sender email domain is verified in AutoSend
* Ensure the sender address matches a verified domain
* Check Descope connector test results for specific errors
* Double-check your SMTP key is correct
* Ensure you're using `autosend` as the username
* Verify your SMTP key is active in the SMTP Settings
* Verify the hostname is `smtp.autosend.com`
* Try using port `465` with implicit TLS if port `587` fails
* Check if your network blocks outbound SMTP connections
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC)
* Use a professional sender name, not just "noreply"
* Check your domain reputation in AutoSend
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Learn more about AutoSend SMTP configuration
# How to send emails with Nodemailer and AutoSend SMTP
Source: https://docs.autosend.com/guides/smtp/nodemailer
Send transactional emails from Node.js applications using Nodemailer with AutoSend SMTP.
[Nodemailer](https://nodemailer.com/) is the most popular email sending library for Node.js. By integrating Nodemailer with AutoSend SMTP, you can send transactional emails from your Node.js applications with reliable delivery, full tracking, and detailed analytics.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
Install the Nodemailer package using your preferred package manager:
```bash npm theme={null}
npm install nodemailer
```
```bash yarn theme={null}
yarn add nodemailer
```
```bash pnpm theme={null}
pnpm add nodemailer
```
```bash bun theme={null}
bun add nodemailer
```
Configure the Nodemailer transporter with your AutoSend SMTP credentials:
```javascript theme={null}
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.autosend.com',
port: 465,
secure: true,
auth: {
user: 'autosend',
pass: 'AS_xxx', // Your AutoSend SMTP key
},
});
```
| Field | Value |
| ------------ | ----------------------------------------- |
| **Host** | `smtp.autosend.com` |
| **Port** | `465` (recommended) or `587` |
| **Username** | `autosend` |
| **Password** | Your AutoSend SMTP key (AS\_xxx) |
| **Secure** | `true` for port 465, `false` for port 587 |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Use the `sendMail` method to send your email:
```javascript theme={null}
const info = await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
html: 'Welcome!
This email was sent via Nodemailer and AutoSend SMTP.
',
});
console.log('Message sent:', info.messageId);
```
The `from` address must use a domain that is verified in AutoSend. For example, if you verified `yourdomain.com`, use an address like `sender@yourdomain.com`.
Check the Email Activity dashboard to verify your email was sent successfully. You'll see delivery status, opens, clicks, and other engagement metrics.
***
## Complete Examples
```javascript JavaScript (ES Modules) expandable theme={null}
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.autosend.com',
port: 465,
secure: true,
auth: {
user: 'autosend',
pass: 'AS_xxx',
},
});
async function sendEmail() {
const info = await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
html: 'Welcome!
This email was sent via Nodemailer and AutoSend SMTP.
',
});
console.log('Message sent:', info.messageId);
}
sendEmail().catch(console.error);
```
```javascript JavaScript (CommonJS) expandable theme={null}
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtp.autosend.com',
port: 465,
secure: true,
auth: {
user: 'autosend',
pass: 'AS_xxx',
},
});
async function sendEmail() {
const info = await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
html: 'Welcome!
This email was sent via Nodemailer and AutoSend SMTP.
',
});
console.log('Message sent:', info.messageId);
}
sendEmail().catch(console.error);
```
```typescript TypeScript expandable theme={null}
import nodemailer from 'nodemailer';
import type { Transporter } from 'nodemailer';
const transporter: Transporter = nodemailer.createTransport({
host: 'smtp.autosend.com',
port: 465,
secure: true,
auth: {
user: 'autosend',
pass: 'AS_xxx',
},
});
async function sendEmail(): Promise {
const info = await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
html: 'Welcome!
This email was sent via Nodemailer and AutoSend SMTP.
',
});
console.log('Message sent:', info.messageId);
}
sendEmail().catch(console.error);
```
***
## Advanced Usage
### Sending with Attachments
```javascript theme={null}
await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Email with Attachment',
html: 'Please find the attached file.
',
attachments: [
{
filename: 'document.pdf',
path: './files/document.pdf',
},
{
filename: 'image.png',
content: imageBuffer,
},
],
});
```
### Sending to Multiple Recipients
```javascript theme={null}
await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient1@example.com, recipient2@example.com',
cc: 'cc@example.com',
bcc: 'bcc@example.com',
subject: 'Hello Everyone',
html: 'This email is sent to multiple recipients.
',
});
```
### Plain Text and HTML
```javascript theme={null}
await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
text: 'This is the plain text version of the email.',
html: 'Hello!
This is the HTML version of the email.
',
});
```
## Troubleshooting
* Verify your SMTP credentials are correct - Check that your sender email domain is verified in
AutoSend - Ensure the `from` address matches a verified domain - Check your application logs for
SMTP connection errors
* Double-check your SMTP key is correct - Ensure you're using `autosend` as the username - Verify
your SMTP key is active in the SMTP Settings - Make sure there are no extra spaces in the
credentials
* Verify the hostname is `smtp.autosend.com` - Try using port `587` with `secure: false` if port
`465` fails - Check if your network or firewall blocks outbound SMTP connections - Test
connectivity: `telnet smtp.autosend.com 465`
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC) - Use a professional sender name
and address - Check your domain reputation in AutoSend - Avoid spam trigger words in your email
content
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Learn more about AutoSend SMTP configuration
# How to send emails from Supabase with SMTP
Source: https://docs.autosend.com/guides/smtp/supabase
Configure Supabase Auth to send authentication emails through AutoSend SMTP.
Supabase uses email for authentication flows like sign-up confirmations, password resets, and magic links. By default, Supabase uses its built-in email provider, but it has rate limit. For production apps, you can configure it to use AutoSend SMTP for better deliverability.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
1. Go to your [Supabase dashboard](https://supabase.com/dashboard)
2. Select your project
3. Navigate to **Authentication**
4. Under Notifications, you will see **Email** Section.
5. Go to **SMTP Settings** tab.
Toggle **Enable Custom SMTP** to enable the SMTP configuration form.
Fill in the following settings:
| Field | Value |
| ---------------- | ---------------------------------------------------------- |
| **Sender email** | `noreply@yourdomain.com` (must match your verified domain) |
| **Sender name** | Your app name (e.g., "MyApp") |
| **Host** | `smtp.autosend.com` |
| **Port number** | `587` |
| **Username** | autosend |
| **Password** | Your AutoSend SMTP key (AS\_xxx) |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Click **Save** to apply your SMTP configuration.
Send a test email to verify your configuration:
1. Go to **Authentication** → **Users**
2. Click **Invite user**
3. Enter a test email address
4. Click **Invite**
Check the recipient's inbox to confirm the email was delivered through AutoSend.
## Email Templates
Supabase allows you to customize the email templates used for authentication. You can modify these in **Authentication** → **Email Templates**.
Available templates:
* **Confirm signup** - Sent when a user signs up
* **Invite user** - Sent when inviting a user to your project
* **Magic link** - Sent for passwordless login
* **Change email address** - Sent when a user requests an email change
* **Reset password** - Sent for password recovery
Supabase email templates use Go templating syntax. Variables like `{{ .ConfirmationURL }}` are replaced with actual values when the email is sent.
### Example Custom Template
Here's an example of a customized signup confirmation template:
```html theme={null}
Welcome to MyApp!
Thanks for signing up. Please confirm your email address by clicking the button below:
Confirm Email
If you didn't create an account, you can safely ignore this email.
Thanks,
The MyApp Team
```
## Rate Limits
Supabase applies rate limits to authentication emails to prevent abuse. These limits are:
* **30 emails per hour** per user
* **4 emails per hour** for the same action (e.g., password reset)
AutoSend's rate limits are separate and typically higher. Check your plan limits for details.
## Troubleshooting
* Verify your SMTP credentials are correct - Check that your sender email domain is verified in
AutoSend - Ensure the sender email matches a verified domain - Check Supabase logs for SMTP
connection errors
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC) - Use a professional sender name,
not "noreply" - Avoid spam trigger words in your email templates - Check your domain reputation
in AutoSend
* Supabase may have firewall restrictions on certain IP ranges - Try using port 465 instead of
587 - Contact Supabase support if the issue persists
* Double-check your SMTP key is correct - Ensure the username is `autosend` and the password is your
SMTP key - Verify your SMTP key is active in the SMTP Settings
## Alternative: Supabase Edge Functions
For more advanced use cases, you can use Supabase Edge Functions with the AutoSend API instead of SMTP. This gives you access to features like:
* Email templates with dynamic variables
* Detailed delivery tracking
* Webhook notifications
Learn how to send emails from Supabase Edge Functions using the AutoSend API
# How to send emails from WordPress with SMTP
Source: https://docs.autosend.com/guides/smtp/wordpress
Configure WordPress to send emails through AutoSend SMTP.
WordPress relies on the PHP `mail()` function to send emails by default, which often results in poor deliverability or emails not being sent at all. By configuring WordPress to use AutoSend SMTP, you ensure reliable delivery for password resets, user notifications, contact form submissions, and WooCommerce order emails.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## Configuration
Install and activate the [WP Mail SMTP](https://wordpress.org/plugins/wp-mail-smtp/) plugin from your WordPress dashboard.
Navigate to **WP Mail SMTP** > **Settings** in the WordPress admin sidebar.
In the **Mail** section, configure your sender details:
| Field | Value |
| -------------- | ---------------------------------------------------------- |
| **From Email** | Your verified email address (e.g., `hello@yourdomain.com`) |
| **From Name** | Your sender name (e.g., `Your Company`) |
Enable **Force From Email** to ensure WordPress always uses this address.
The From Email must use a domain that is verified in AutoSend. For example, if you verified `yourdomain.com`, use an address like `hello@yourdomain.com`.
Scroll down to the **Mailer** section and select **Other SMTP**.
Fill in the following SMTP settings:
| Field | Value |
| ------------------ | -------------------------------- |
| **SMTP Host** | `smtp.autosend.com` |
| **Encryption** | TLS |
| **SMTP Port** | `587` (or `465` for SSL) |
| **Authentication** | On |
| **SMTP Username** | `autosend` |
| **SMTP Password** | Your AutoSend SMTP key (AS\_xxx) |
The SMTP password is no longer your API key. Create a project-specific SMTP key from the SMTP tab in Project Settings.
Click **Save Settings** to save your configuration.
1. Go to **WP Mail SMTP** > **Tools** > **Email Test**
2. Enter a recipient email address
3. Click **Send Email**
You can also check the Email Activity dashboard to verify the email was sent through AutoSend.
## Troubleshooting
* Verify your SMTP credentials are correct - Check that your sender email domain is verified in
AutoSend - Ensure **Force From Email** is enabled in WP Mail SMTP settings - Check if your
hosting provider blocks outgoing SMTP connections
* Double-check your SMTP key is correct - Ensure you're using `autosend` as the username - Verify
your SMTP key is active in the SMTP Settings - Make sure there are no extra spaces in the
credentials
* Verify the server address is `smtp.autosend.com` - Try using port `465` with SSL if port `587`
fails - Contact your hosting provider to ensure SMTP ports are not blocked - Some shared hosts
block outgoing SMTP; consider upgrading to a VPS
* Deactivate other SMTP or email plugins that may interfere - Check for caching plugins that
might cache email settings - Test with a default WordPress theme to rule out theme conflicts
* Ensure your domain has proper DNS records (SPF, DKIM, DMARC) - Use a professional sender name
and address - Check your domain reputation in AutoSend - Avoid spam trigger words in your email
content
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Monitor your email delivery and engagement
Set up webhooks to track email events in real-time
Learn more about AutoSend SMTP configuration
# Send emails with Supabase Edge Functions
Source: https://docs.autosend.com/guides/supabase-edge-functions
Learn how to send transactional emails from Supabase Edge Functions using AutoSend.
## Overview
This guide shows you how to integrate AutoSend with Supabase Edge Functions to send emails for authentication, notifications, and other transactional use cases. Edge Functions run at the edge, close to your users, making them perfect for handling email operations with low latency.
AutoSend has an official [integration with
Supabase](https://supabase.com/partners/integrations/AutoSend). For a less technical setup, you
can also use [SMTP to integrate with Supabase](/guides/smtp/supabase).
## Prerequisites
Before you begin, make sure you have:
* An [AutoSend account](https://autosend.com/) with an API key
* A [Supabase project](https://supabase.com/)
* [Supabase CLI](https://supabase.com/docs/guides/cli#installation) installed (v1.0 or later)
* Node.js 18+ installed locally
## Quickstart
Initialize your Supabase project and create a new Edge Function:
```bash theme={null}
# Login to Supabase
supabase login
# Initialize your project (if not already done)
supabase init
# Create a new Edge Function
supabase functions new autosend
```
This creates a new function in `supabase/functions/autosend/index.ts`.
Edge Functions support importing from standard URLs. No package installation needed!
Replace the content of `supabase/functions/autosend/index.ts` with:
```tsx expandable theme={null}
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { corsHeaders } from '../_shared/cors.ts'
serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const { to, from, subject, html, templateId, dynamicData } = await req.json()
// Get AutoSend API key from environment variables
const autoSendApiKey = Deno.env.get('AUTOSEND_API_KEY')
if (!autoSendApiKey) {
throw new Error('AUTOSEND_API_KEY not configured')
}
// Prepare email payload
const emailPayload: any = {
to: {
email: to.email,
name: to.name || undefined
},
from: {
email: from.email,
name: from.name || undefined
}
}
// Add template or HTML content
if (templateId) {
emailPayload.templateId = templateId
if (dynamicData) {
emailPayload.dynamicData = dynamicData
}
} else {
emailPayload.subject = subject
emailPayload.html = html
}
// Send email via AutoSend API
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${autoSendApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(emailPayload)
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || 'Failed to send email')
}
return new Response(
JSON.stringify({
success: true,
emailId: data.data.emailId,
status: data.data.status
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
},
)
} catch (error) {
return new Response(
JSON.stringify({
success: false,
error: error.message
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
},
)
}
})
```
Create a file at `supabase/functions/_shared/cors.ts`:
```tsx theme={null}
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
```
Create a `.env` file in your project root (for local development):
```bash theme={null}
AUTOSEND_API_KEY=your_autosend_api_key_here
```
For production, set secrets using the Supabase CLI:
```bash theme={null}
supabase secrets set AUTOSEND_API_KEY=your_autosend_api_key_here
```
Test your function locally before deploying:
```bash theme={null}
supabase functions serve autosend --env-file .env --no-verify-jwt
```
The function will be available at: `http://localhost:54321/functions/v1/autosend`
Send a test request using cURL:
```bash theme={null}
curl -i --location --request POST 'http://localhost:54321/functions/v1/autosend' \
--header 'Authorization: Bearer YOUR_SUPABASE_ANON_KEY' \
--header 'Content-Type: application/json' \
--data '{
"to": {
"email": "[email protected]",
"name": "John Doe"
},
"from": {
"email": "[email protected]",
"name": "Your App"
},
"subject": "Welcome to our platform!",
"html": "Hello John!
Welcome aboard!
"
}'
```
Once tested, deploy your function:
```bash theme={null}
supabase functions deploy autosend
```
Your function will be available at:
```
https://YOUR_PROJECT_REF.supabase.co/functions/v1/autosend
```
## Usage Examples
```jsx theme={null}
const { data, error } = await supabase.functions.invoke('autosend', {
body: {
to: {
email: '[email protected]',
name: 'Jane Smith'
},
from: {
email: '[email protected]',
name: 'MyApp'
},
subject: 'Welcome!',
html: 'Welcome to MyApp!
We are excited to have you.
'
}
})
if (error) {
console.error('Error sending email:', error)
} else {
console.log('Email sent:', data)
}
```
```jsx theme={null}
const { data, error } = await supabase.functions.invoke('autosend', {
body: {
to: {
email: '[email protected]',
name: 'Jane Smith'
},
from: {
email: '[email protected]',
name: 'MyApp'
},
templateId: 'tmpl_welcome_email',
dynamicData: {
firstName: 'Jane',
loginUrl: 'https://app.example.com/login'
}
}
})
```
```jsx theme={null}
const sendEmail = async () => {
const response = await fetch(
'https://YOUR_PROJECT_REF.supabase.co/functions/v1/autosend',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${supabaseAnonKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: {
email: '[email protected]',
name: 'John Doe'
},
from: {
email: '[email protected]',
name: 'MyApp'
},
subject: 'Order Confirmation',
html: 'Your order has been confirmed!
'
})
}
)
const data = await response.json()
return data
}
```
## Integration with Supabase Auth
You can use AutoSend to send custom authentication emails by integrating with Supabase Auth Hooks.
```bash theme={null}
supabase functions new send-auth-email
```
```tsx expandable theme={null}
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
serve(async (req) => {
try {
const payload = await req.json()
const { event_type, user, token_hash, redirect_to } = payload
const autoSendApiKey = Deno.env.get('AUTOSEND_API_KEY')
let templateId: string
let dynamicData: any = {}
// Map event types to templates
switch (event_type) {
case 'user.signup':
templateId = 'tmpl_signup_confirmation'
dynamicData = {
confirmationUrl: `${redirect_to}?token_hash=${token_hash}&type=signup`,
email: user.email
}
break
case 'user.password_recovery':
templateId = 'tmpl_password_reset'
dynamicData = {
resetUrl: `${redirect_to}?token_hash=${token_hash}&type=recovery`,
email: user.email
}
break
case 'user.email_change':
templateId = 'tmpl_email_change'
dynamicData = {
confirmationUrl: `${redirect_to}?token_hash=${token_hash}&type=email_change`,
newEmail: user.new_email
}
break
default:
throw new Error(`Unsupported event type: ${event_type}`)
}
// Send email via AutoSend
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${autoSendApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: {
email: user.email
},
from: {
email: '[email protected]',
name: 'Your App'
},
templateId,
dynamicData
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || 'Failed to send auth email')
}
return new Response(
JSON.stringify({ success: true }),
{
headers: { 'Content-Type': 'application/json' },
status: 200,
},
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
headers: { 'Content-Type': 'application/json' },
status: 400,
},
)
}
})
```
1. Go to **Authentication > Hooks** in your Supabase dashboard
2. Enable the "Send Email" hook
3. Set the hook URL to your deployed function:
```
https://YOUR_PROJECT_REF.supabase.co/functions/v1/send-auth-email
```
4. Configure the secret (optional but recommended)
Now all Supabase Auth emails will be sent through AutoSend!
## Error Handling
Implement robust error handling for production:
```tsx expandable theme={null}
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
serve(async (req) => {
try {
const payload = await req.json();
// Validate required fields
if (!payload.to?.email) {
throw new Error('Recipient email is required');
}
if (!payload.from?.email) {
throw new Error('Sender email is required');
}
const autoSendApiKey = Deno.env.get('AUTOSEND_API_KEY');
if (!autoSendApiKey) {
throw new Error('AutoSend API key not configured');
}
const response = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${autoSendApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const data = await response.json();
// Handle AutoSend API errors
if (!response.ok) {
console.error('AutoSend API Error:', data);
switch (response.status) {
case 400:
throw new Error(`Validation error: ${data.message}`);
case 401:
throw new Error('Invalid API key');
case 403:
throw new Error('Domain not verified or insufficient permissions');
case 429:
throw new Error('Rate limit exceeded. Please try again later.');
default:
throw new Error(data.message || 'Failed to send email');
}
}
return new Response(
JSON.stringify({
success: true,
emailId: data.data.emailId,
status: data.data.status,
}),
{
headers: { 'Content-Type': 'application/json' },
status: 200,
},
);
} catch (error) {
console.error('Function Error:', error);
return new Response(
JSON.stringify({
success: false,
error: error.message,
}),
{
headers: { 'Content-Type': 'application/json' },
status: 400,
},
);
}
});
```
## Best Practices
Always use environment variables for sensitive data:
```tsx theme={null}
// ✅ Good
const apiKey = Deno.env.get('AUTOSEND_API_KEY')
// ❌ Bad - Never hardcode
const apiKey = 'as_123456789'
```
Validate email formats before sending:
```tsx theme={null}
function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
if (!isValidEmail(payload.to.email)) {
throw new Error('Invalid email address')
}
```
Protect your function from abuse:
```tsx theme={null}
// Use Supabase Rate Limiting or implement custom logic
const userRateLimit = await checkUserRateLimit(userId)
if (!userRateLimit.allowed) {
throw new Error('Rate limit exceeded')
}
```
Always send from verified domains in AutoSend to ensure deliverability. \
[Learn how to verify domains →](/domain)
## Testing
### Test Function Locally
```bash theme={null}
# Start local development server
supabase functions serve send-email --env-file .env
# Test with curl
curl -i --location --request POST 'http://localhost:54321/functions/v1/send-email' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' \
--header 'Content-Type: application/json' \
--data '{
"to": { "email": "[email protected]" },
"from": { "email": "[email protected]" },
"subject": "Test Email",
"html": "This is a test
"
}'
```
# Inbound Email API
Source: https://docs.autosend.com/inbound/introduction
Receive incoming emails on AutoSend through webhooks, then read, parse, or reply to them programmatically with the Inbound Email API.
The AutoSend Inbound Email API lets your application receive incoming emails programmatically. When a message lands on one of your receiving domains, AutoSend fires an `email.received` webhook carrying the message metadata (message ID, sender, recipients, subject, receiving domain, and threading). Your application then calls a structured REST endpoint with that message ID to fetch the full message, including headers, plain text and HTML bodies, attachments, and verdicts. Use it to build support inboxes, parse customer replies, route email into internal tools, or store conversations alongside the rest of your data.
Jump straight to the [Inbound Emails API reference](/api-reference/inbound-emails/list-messages)
for endpoints, request schemas, and response samples.
## How the Inbound Email API works
Create a new webhook or reuse an existing one, and subscribe it to the `email.received` event. You can set this up from the Webhooks section of the dashboard. See the Webhooks introduction if you need a refresher on creating endpoints.
When an email arrives on one of your receiving domains, AutoSend delivers an `email.received` webhook to your endpoint. The payload includes the message ID, sender, recipient, subject, receiving domain, and threading metadata. See the full payload in the event types reference.
Use the message ID from the webhook payload to fetch the complete structured JSON, including headers, text and HTML bodies, attachments metadata, and verdicts, via the Get Message endpoint.
To browse every inbound message your project has received, open the [Email Activity > Inbound tab](https://autosend.com/email-activities/inbound) in the dashboard.
***
## Receiving domains
Every project comes with a default receiving domain. You can also enable Inbound on a custom domain if you need email to arrive at an address you control.
### Default receiving domain
Each project is provisioned with a unique receiving subdomain in the form `{prefix}@{uniquesubdomain}.autosend.email`. You pick any prefix and immediately start receiving email at that address. **No DNS setup is required**.
The default receiving domain is the fastest way to get started and is the recommended option for
most projects.
### Use your own domain
You can also enable Inbound on a domain you've already verified for sending on AutoSend, or add a brand-new domain. Follow the MX record instructions shown in the dashboard to point incoming mail to AutoSend.
If your domain is already serving inboxes through Gmail, Outlook, or another mail provider, you
cannot also use it for receiving on AutoSend. MX records can only point to one provider at a time.
To work around this, set up [email forwarding](https://support.google.com/mail/answer/10957) from
your existing mail provider to your AutoSend default receiving address.
***
## Reading and replying to messages
Once an `email.received` webhook fires, you can use the Inbound Emails API to read the full message, download attachments, and reply in the same thread.
Paginate, filter, and search messages received on your project's inbound-enabled domains.
Fetch a single message with full body, headers, attachments metadata, and threading.
Download a single attachment by its zero-based index.
Send a threaded reply via In-Reply-To and References headers using a verified sending domain.
***
## Common use cases
* **AI agent inbox**: Pair Inbound with the AutoSend sending API to give your AI agents a fully two-way email address. Your agents can both send outbound messages and receive replies in the same project, enabling real email conversations driven by an LLM.
* **Support portal or helpdesk**: Route customer replies into your ticketing system and let agents respond from a unified inbox.
* **CRM email logging**: Capture incoming emails as activities on the matching contact, lead, or deal record.
* **Applicant tracking system (ATS)**: Parse candidate replies to recruiting outreach and attach them to the right pipeline stage.
* **Custom email client or inbox**: Build a fully featured mailbox without running your own mail server, MX records, or IMAP plumbing.
***
## Frequently asked questions
Yes. The AutoSend Inbound Email API delivers incoming messages to your application in two steps.
First, an `email.received` webhook notifies you when a new email arrives on one of your
receiving domains. Second, a REST endpoint returns the full structured message, including
headers, plain text and HTML bodies, attachments, and threading metadata.
The Inbound Email API is available on the **Starter 10k** plan and above. Free and trial plans
do not include inbound receiving. Upgrade your project from the Billing page to enable it.
Pick or enable a receiving domain in the dashboard, create a webhook subscribed to the
`email.received` event, and call the Get Message
endpoint with the message ID from the webhook payload to fetch the full email.
No. Every project is provisioned with a default receiving domain in the form `{prefix}@ {uniquesubdomain}.autosend.email` and you can start receiving email immediately without any DNS
setup. You can also enable Inbound on a verified custom domain when you need email to arrive at
an address you control.
Yes. Use the Reply to Message endpoint to send
a threaded reply. AutoSend sets the `In-Reply-To` and `References` headers automatically so the
reply lands in the same conversation in the recipient's inbox.
The Get Message response includes attachment
metadata for every message. Download the binary content of a specific attachment by its
zero-based index via the
Download Attachment endpoint.
***
## Related resources
Full webhook payload reference for inbound notifications.
Set up a webhook endpoint and verify requests.
Configure receiving domains and view incoming messages in the dashboard.
Verify a domain or add a new one to use with Inbound.
# What is AutoSend?
Source: https://docs.autosend.com/index
AutoSend is an email platform for developers and marketers to send transactional and marketing emails, receive inbound emails via API, and integrate with AI agents.
## Transactional Emails
Learn how to send transactional emails like magic links, verification codes, and signup confirmations using the AutoSend Email API.
Learn how to send Transactional emails using the AutoSend email API.
Learn how to send emails using AutoSend's SMTP relay service.
## Marketing Emails
Learn how to send marketing emails like newsletters, product updates, and announcements using AutoSend’s campaign tools
Learn how to create and send marketing campaigns using AutoSend.
Learn how to create contact-triggered email automations in AutoSend.
Learn about contact lists and dynamic segments in Autosend.
Learn how to warm up your domain to build sender reputation and improve deliverability.
## Inbound Emails
Receive incoming emails on AutoSend through webhooks, then read, parse, or reply to them programmatically with the Inbound Email API.
Learn how to receive and process incoming emails on AutoSend using the Inbound Email API.
Browse the Inbound Emails API endpoints, request schemas, and response samples.
## Agentic Integration
Use AutoSend with AI coding agents and no-code AI tools to send emails, manage contacts, and trigger automations from AI-built apps.
Connect AutoSend's MCP server to build email campaigns and templates with AI tools using natural
language.
Install the AutoSend skill so AI agents can seamlessly integrate AutoSend's email API into your
code.
## Migration Guides
Migrate your existing email infrastructure to AutoSend smoothly.
## Need Help ?
***
Download official AutoSend logos, icons, and brand assets for your integrations and marketing
materials.
# Sync Tally form submissions with AutoSend
Source: https://docs.autosend.com/integrations/tally-forms
Learn how to connect Tally Forms with AutoSend to automatically sync form submissions with your contact lists.
## Overview
[Tally](https://tally.so) is a form builder for creating signup forms, waitlists, surveys, and lead capture forms. AutoSend has a native Tally Forms integration that automatically syncs your form submissions with contacts in AutoSend, with no code required.
Once synced, you can use those contacts to send campaigns, trigger automations like a welcome sequence, or build segments based on their form answers.
## How it works
1. You connect your Tally account to AutoSend with your Tally API key.
2. You pick a Tally form and the contact list its submissions should be saved to.
3. You map the form fields to contact properties in AutoSend.
4. From then on, every new submission is automatically saved as a contact in the selected list. If a contact with the same email already exists, it is updated instead of duplicated.
Email is a mandatory field for a contact to be stored in AutoSend. Make sure your Tally form
includes an email field, and that it is mapped to the contact's email property.
## Prerequisites
Before you begin, make sure you have:
* An [AutoSend account](https://autosend.com/)
* A [Tally](https://tally.so) account with at least one form that collects an email address
## Connect Tally Forms with AutoSend
Go to [Integrations](https://autosend.com/integrations) in your AutoSend dashboard and click
**Connect** on the **Tally Forms** card.
In the modal that opens, enter your Tally API key. You can create one from [Tally's API keys
settings](https://tally.so/settings/api-keys). The key starts with `tly-`.
Click **Connect**. Tally Forms is now connected to AutoSend, and you will see it marked as
**Connected**.
## Sync a form with a contact list
Next, connect a specific Tally form to a contact list in AutoSend.
On the Tally Forms integration page, click **Add Form**.
Select the Tally form whose submissions you want to sync, and choose the AutoSend contact list to save contacts in.
Contacts are saved to **All Contacts** by default. Choosing a specific list also triggers any live automations attached to that list, which is useful for sending a welcome email to new signups.
Match each Tally form field to the correct contact property in AutoSend. The email field is required, and other answers can be mapped to properties like first name, last name, or your own custom properties.
A contact is only stored in AutoSend if the submission includes an email address. Fields you
leave unmapped are not synced.
Save the connection. From now on, every new submission to this form is synced as a contact in AutoSend.
You can repeat these steps to sync multiple forms, each with its own contact list and field mapping. The integration page shows all connected forms along with the list they save to and their sync status.
## Test the integration
Submit a test response to your Tally form, then check the Contacts ↗ page in your AutoSend dashboard. The respondent should appear as a contact in the selected list within a few seconds.
## Troubleshooting
### Contacts are not appearing in AutoSend
* Make sure the submission includes an email address. Submissions without an email are not stored as contacts.
* Check that the form's status on the Tally Forms integration page is active.
* Confirm your Tally API key is still valid. If you revoked it in Tally, reconnect the integration with a new key.
### Form answers are missing on the contact
* Only mapped fields are synced. Open the form's field mapping and make sure each answer you want stored is mapped to a contact property.
* To store answers that don't match a built-in property, map them to custom contact properties.
## Next steps
Organize synced respondents into lists for targeted campaigns.
Trigger a welcome sequence when a contact joins a list.
Build segments from form answers stored as contact properties.
Send one-off marketing emails to your synced contacts.
# Product Hunt Launch Toolkit
Source: https://docs.autosend.com/launch-toolkit
Ready-to-use posts and media assets to support our Product Hunt launch
Thanks for supporting our Product Hunt launch! We've put together some ready-to-use posts and media assets to make sharing easy. Feel free to use these as-is or tweak them to match your voice.
## Product Hunt Link
Upvote and support our launch
```
https://www.producthunt.com/products/autosend-2
```
## Sample Posts
**Option 1:**
```
My friends at @AutoSendEmail just launched on Product Hunt. It's an email platform that charges by volume, not contacts. Check it out: [link]
```
**Option 2:**
```
If you're tired of email providers charging you for contacts sitting in your database, check out @AutoSendEmail. They just launched on Product Hunt: [link]
```
**Option 3:**
```
.@AutoSendEmail is live on Product Hunt today. Finally an email platform with pricing that makes sense. [link]
```
**Option 1:**
```
AutoSend just launched on Product Hunt. If you're building something and need an email provider that doesn't charge you for contacts you're not even emailing, give them a look. [link]
```
**Option 2:**
```
A friend of mine just launched AutoSend on Product Hunt. It's an email delivery platform for developers with volume-based pricing instead of contact-based. Simple idea, but it changes a lot. Check it out if you get a chance. [link]
```
**Option 3:**
```
AutoSend is live on Product Hunt today. I've been using it for my email infrastructure and the experience has been solid. If you're looking for an alternative to the usual suspects, worth a look. [link]
```
**Option 4:**
```
Product Hunt launch day for AutoSend. It's an email platform built for developers with a pricing model that actually makes sense: pay for what you send, not how many contacts you have sitting in a database. Rooting for them. [link]
```
**Option 5:**
```
Congrats to the AutoSend team on their Product Hunt launch. If you're a developer or running a growing product, their volume-based pricing is a breath of fresh air compared to most email providers. [link]
```
## Media Assets
Download logos, banners, and images sized for different platforms.
## Thank You
Your support means a lot to us. If you have any questions or want to share something custom, reach out or tag us on Twitter.
@AutoSendEmail
# Campaigns
Source: https://docs.autosend.com/marketing-emails/campaigns
Create and send one-time or scheduled marketing emails to your contacts, lists, or segments.
## What are Campaigns in AutoSend?
Campaigns in **AutoSend** are one-time or scheduled marketing emails that you send to your contacts, lists, or segments. These are typically newsletters, announcements, or promotional emails designed to engage your audience.
## Creating a Campaign in AutoSend
Create your email using HTML with inline CSS for styling. AutoSend's email designer supports responsive design, so make sure to write HTML that renders well across different email clients and devices.
* Write responsive HTML with inline CSS for maximum compatibility.
* Use variables anywhere in the HTML to personalize content dynamically, for example: `{{first_name}}`, `{{company_name}}`, `{{email}}`, etc.
* Variables are wrapped in double curly braces and will be replaced with actual values when the email is sent.
* Keep your HTML clean and well-structured to ensure consistent rendering across email clients.
* Test your template thoroughly in both desktop and mobile views before deployment.
**Preview and Devices**
The **Email Preview** on the right reflects changes from the composer in real time. Switch between **Desktop** and **Mobile** via the device icons to confirm responsive behavior and visual consistency. Links are disabled in preview to avoid accidental navigation.
After designing your email, fill in the campaign details. The mandatory fields are marked with a red asterisk (\*).
| **Field** | **Mandatory** | **Description** |
| -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Subject \* | Yes | The subject line of your email. This is what your recipients will see in their inbox. |
| Preview Text | No | The short snippet that appears next to the subject line. (If left blank, AutoSend will automatically display the first few lines of your email.) |
| From Sender \* | Yes | The sender name and email address your recipients will see. |
| Reply To | No | The email address where replies will be sent. |
| To \* | Yes | Select who will receive the campaign — this can be All Contacts, a specific List, or a Segment. |
| Exclude | No | Select lists or segments you'd like to exclude from receiving this campaign. |
| Unsubscribe Group \* | Yes | Choose the group contacts will be added to when they unsubscribe from this campaign. |
Once the campaign details are filled, select when you want to send the campaign.
* Click **Send Now** to send the campaign immediately.
* Or, **Schedule** for later to pick a specific date and time.
Review all the details of your campaign. Make sure everything is accurate.
Once reviewed, Click on **Send Now** or **Schedule Campaign** to send or schedule for later respectively.
Your campaign will be made live accordingly.
## Testing your Campaign
You can send a test email to check how your campaign appears across devices.
In the modal, add up to 10 comma separated emails and click **Send Test.**
# Contact Properties
Source: https://docs.autosend.com/marketing-emails/contacts/contact-properties
Store additional contact information and personalize your emails with contact properties.
Contact properties were previously called **custom fields**. The name has changed, but everything works the same way. The legacy `/custom-fields` API endpoints continue to work as an alias.
## What are Contact Properties in AutoSend?
Contact properties allow you to store additional information about your contacts (eg. company name, signup source, plan type, etc). These properties can be used to personalize emails or segment contacts.
**You can use these properties in your campaign using variables like:**
```
Hey {{firstName}},
```
## How to Create Contact Properties in AutoSend
1. From the Marketing Emails section, click on the **Contacts**
2. Go to **Contact Properties** **tab**.
3. Click **New Property**.
4. Choose the property type (text, number, boolean or date) and name it.
5. Click **Add.** The property will now be available for all contacts.
# Import Contacts via CSV
Source: https://docs.autosend.com/marketing-emails/contacts/import-csv
Bulk import contacts into AutoSend using a CSV file.
1. From the Marketing Emails section, click on the **Contacts** tab
2. Go to **Lists and Segments.**
3. Click on **Add Contacts > Import via CSV**.
1. Select your CSV file.
2. In **Save in,** chose which list you want to add these contacts to. If needed, you can create a new list directly from this dropdown.
3. Click **Next**
AutoSend will show you a preview of your CSV columns.
Map each column to the corresponding contact field — e.g., “First Name” → `first_name`.
If a field doesn’t exist yet, you can create a new contact property directly from this screen.
Once mapping is complete, click **Start Importing**.
Your import will begin processing in the background.
You’ll receive an confirmation email once the import is complete, including:
* Total Contacts in the CSV
* Contacts successfully imported
* Contacts failed to import
* Contacts skipped
* Duplicates
# Contacts
Source: https://docs.autosend.com/marketing-emails/contacts/introduction
Manage your email contacts, organize them into lists and segments, and personalize your emails.
Contacts are the people you send emails to in AutoSend. Each contact has a unique email address and associated data like name, location, or any other information you’ve added through fields.
Contacts can be organized using **Lists** or **Segments** for better targeting.
## All Contacts
The **All Contacts** list shows every contact you’ve ever added to your account.
Contacts can come from:
* Manual additions
* Imported via CSV
* API integrations
* Sign-ups or form submissions
You can filter and search contacts, and view detailed insights for each.
## Contact Details
Clicking on any contact opens their **Contact Details** page, where you can view:
* **Details**: Basic information (email, name, etc.), Reserved Fields and Contact Properties.
* **Lists & Segments**: All the lists and segments the contact belongs to
* **Unsubscribes:** The kind of emails a particular user has unsubscribed from.
* **Email Activity**: The email history of a particular user.
### Reserved Fields
Reserved fields are system-defined fields that AutoSend uses to store core contact information. You can’t rename or delete them.
| **Field** | **Description** |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| email | The contact's primary email address. This field is required and unique for each contact. |
| firstName | The contact's first name. Often used for personalizing emails (e.g., Hey `{{firstName}}`). |
| first\_name | The contact's first name. Often used for personalizing emails (e.g., Hey `{{firstName}}`). |
| lastName | The contact's last name. |
| last\_name | The contact's last name. |
| mobile | The contact's mobile phone number. |
| unsubscribe | Boolean indicating if the contact has unsubscribed from all emails. |
| unsubscribed | Boolean indicating if the contact has unsubscribed from all emails. |
| unsubscribe\_groups | Array of specific email groups/categories the contact has unsubscribed from. |
| unsubscribe\_preferences | Object containing detailed unsubscribe preferences for different email types. |
| userId | Your internal user ID for tracking contacts across your systems. |
| externalId | External identifier from third-party integrations or CRM systems. |
| createdAt | Timestamp when the contact was first added to AutoSend. |
| updatedAt | Timestamp when the contact was last modified. |
| contactLists | Array of list IDs that this contact belongs to. |
| address | The contact's full address (single field). |
| address\_line\_1 | First line of the contact's address (street address). |
| address\_line\_2 | Second line of the contact's address (apartment, suite, etc.). |
| city | The contact's city. |
| state | The contact's state or province. |
| zip | The contact's postal or ZIP code. |
| country | The contact's country. |
### Contact Properties
Contact properties
let you store additional information beyond the reserved fields - for example, “Company,” “Plan Type,”
or “Signup Date.”
They can be text, number, boolean, or date properties.
You can use these in both **marketing campaigns** and **transactional emails** to personalize your content using variables like `{{property_name}}`.
***
## Integrations
If you collect signups or leads through other tools, you can use integrations to automatically store them as contacts in AutoSend, with no code required.
Sync your Tally form submissions with contacts in AutoSend.
# Lists
Source: https://docs.autosend.com/marketing-emails/contacts/lists
Organize contacts into static groups for targeted email campaigns.
## What are Lists in AutoSend
A list is a collection of contacts grouped together based on contact properties. By default, all your contacts are stored in the `All Contacts` list.
Lists are great for grouping contacts based on fixed criteria. E.g., “Newsletter Subscribers” or “Event Attendees.”
## How to create a List in AutoSend
1. From the Marketing Emails section, click on the **Contacts** tab
2. Go to **Lists and Segments.**
3. From the **Create** dropdown, select **New List**.
4. In the modal, name and create your list.
5. Once created you can add contacts to your list by **adding them manually** or by **uploading a CSV file.**
# Segments
Source: https://docs.autosend.com/marketing-emails/contacts/segments
Create dynamic groups of contacts based on conditions and filters that update automatically.
## What are Segments in AutoSend
**Segments** are dynamic groups of contacts created based on conditions or filters (e.g., “account\_type=pro,” “country = United States,” etc.). They update automatically as contacts meet or stop meeting the criteria.
Segments helps you target your audience based on real-time data. For example, you can create a segment of users who signed up last month or those who haven’t opened your last three campaigns.
## How to Create a Segment in AutoSend
1. From the Marketing Emails section, click on the **Contacts** tab
2. Go to **Lists and Segments.**
3. From the **Create** dropdown, select **New Segment**.
1. Name your segment, and select the list that you are creating this segment from.
2. Set your segment criteria (field, condition, value)
3. Save the segment.
The segment will auto-update as contacts meet your defined criteria.
# Domain Warmup with AutoSend
Source: https://docs.autosend.com/marketing-emails/domain-warmup
Learn how to warm up your domain to build sender reputation and improve email deliverability using Gradual Send in AutoSend.
## What is Domain Warmup?
Domain warmup is the process of gradually increasing your email sending volume over time so mailbox providers (Gmail, Outlook, Yahoo, etc.) can learn to trust your domain before you send to your full list. Skipping this step is one of the most common reasons new senders end up in the spam folder.
Gradual Send is AutoSend's built-in solution for this. It handles the entire warmup process automatically, so you don't have to manage multiple campaigns or track daily limits manually.
## Why domain warmup matters
When you send from a new domain, mailbox providers have no history to judge you by. No reputation. No trust signals. Sending thousands of emails on day one looks suspicious and triggers spam filters, no matter how good your content is.
Warming up your domain builds that trust gradually. A small volume on day one, slightly more on day two, and so on. By the time you're sending at full volume, providers have seen consistent, healthy engagement from your domain and are far more likely to deliver your emails to the inbox.
You should warm up your domain if you are:
* Sending from a brand new domain for the first time.
* Recently migrated to AutoSend and want to rebuild your reputation.
* Resuming sending after a long period of inactivity.
* Sending to a large cold list for the first time.
## Setting up Gradual Send
Set up your campaign as you normally would. Write your email, add subject, preview text (optional), and once everything looks good, click on Save & Next in the top-right of the page to go to the Campaign Details page.
On the campaign details screen, in **When to send?** section you'll see the sending options: Now, Schedule, and Gradual Send. Select **Gradual Send**.
To see the sending schedule graph, you must first select a contact list in the **To
(list/segment)** input in the **Email Details** section above.
Pick any date and time in the future you want the warmup to begin. You can schedule up to 30 days in advance. AutoSend will use your browser timezone for scheduling.
This is how many emails will go out on Day 1. If you're on a brand new domain, start conservatively.
| Starting Volume | When to use |
| --------------- | -------------------------------------- |
| 25 emails | Very new domain, extra caution |
| 50 emails | New domain, small list |
| 100 emails | Domain with some prior sending history |
| 250 emails | Established domain, moderate list size |
| 500 emails | Established domain, larger list |
When in doubt, start lower. A slower warmup is always safer than a faster one.
This controls how fast your sending volume grows each day.
| Increment | What it means | Best for |
| --------- | --------------------- | -------------------------------------------- |
| Fixed | Same volume every day | Very cautious senders, small lists |
| 1.25x | Grows by 25% each day | Recommended for most new domains |
| 1.5x | Grows by 50% each day | Conservative ramp |
| 1.75x | Grows by 75% each day | Moderate ramp |
| 2x | Doubles each day | Larger lists with some prior sending history |
For most new domains, **1.25x** is the safest starting point. If you have a larger list and some prior sending history, **2x** will get you to full volume faster without compromising deliverability.
Once you've set your inputs, AutoSend generates a bar chart showing exactly how many emails will go out each day. Review this before confirming.
Below the chart, a summary line tells you the exact date your full list will be reached. For example: *"Full list of 5,000 contacts reached by March 18 (Day 8)."*
If anything looks off, adjust your starting volume or increment and the chart will update in real time.
Choose how aggressively AutoSend should monitor your sending health and pause if something goes wrong.
| Preset | Auto-pause if bounce rate exceeds | Auto-pause if complaint rate exceeds |
| ------------ | --------------------------------- | ------------------------------------ |
| Conservative | 2% | 0.05% |
| Balanced | 3% | 0.07% |
| Aggressive | 5% | 0.1% |
**Balanced** is the default and works well for most senders. Choose **Conservative** if you're on a brand new domain or are unsure about your list quality.
Once you're happy with your schedule, click **Schedule** in top-right of the screen. AutoSend will begin sending on your chosen start date and continue the ramp automatically each day.
## What happens during the warmup
You don't need to do anything once Gradual Send is running. AutoSend manages the daily volume increases and monitors your sending health in the background.
You'll receive a daily email summary each morning with:
* How many emails went out yesterday
* Your cumulative send count so far
* Current bounce and complaint rates
## What happens if sending pauses
If your bounce or complaint rate crosses the threshold you set, AutoSend pauses the warmup
immediately. You'll receive an alert email and a link to review the campaign.
When you open the campaign, you'll see a status card explaining what happened. You have two options:
* **Resume Gradual Send**: if you've identified the issue (for example, removed bad addresses from your list) and are confident it's safe to continue.
* **Adjust Settings**: if you want to lower your increment, change your safety threshold, or make other changes before resuming.
AutoSend will never resume automatically. You always stay in control.
## Tips for a successful warmup
Sending to invalid or inactive addresses is the fastest way to spike your bounce rate. Run your
list through an email verification tool before starting a warmup.
The emails you send during warmup are setting the tone for your domain's reputation. High
engagement (opens, clicks) during warmup sends strong positive signals to mailbox providers.
We do not recommend tracking open rates during warmup, as it is measured by adding a tracking
pixel to each email, some inbox providers may flag this as suspicious behavior. You can disable
open tracking in the Email Details section of your campaign settings.
A slower warmup adds a few extra days but significantly reduces your risk of landing in spam. If
your bounce or complaint rates start climbing, lower your increment before resuming.
Getting delivered is only half the battle. If recipients aren't opening your emails during
warmup, that's a signal worth paying attention to.
## Frequently asked questions
It depends on your list size, starting volume, and increment. With 1.25x growth starting at 50
emails, a list of 5,000 contacts typically takes around 30 days. With 2x growth starting at 100
emails, the same list can be reached in under 10 days.
Each Gradual Send is tied to a specific campaign and contact list. For multiple domains, set up
separate campaigns to separate contact lists and run Gradual Send on each one.
AutoSend automatically caps the daily send at your total list size. The chart will stop at the
day the full list is reached.
Yes. If you need to adjust your increment or safety threshold mid-warmup, pause the campaign,
make your changes, and resume.
Gradual Send is designed for marketing campaigns sent to a contact list. Transactional emails
(password resets, receipts, etc.) should not be throttled and do not need warmup.
# Senders
Source: https://docs.autosend.com/marketing-emails/sender
Configure sender email addresses that appear in the From field of your marketing campaigns.
## What is a Sender in AutoSend?
A sender email is the address that appears in the **“From”** field of your campaign — for example, `david@acme.com`. It helps recipients identify who the email is from and builds trust in your communications.
## How to add a Sender Email in AutoSend
- In the Marketing Emails Section, click on the **Senders** tab
- Click **Add Sender.**
- Enter the **Name,** **Email address,** and a **Reply-to** email.
AutoSend only allows you to add emails with authenticated domains.
# Migrate from Resend to AutoSend
Source: https://docs.autosend.com/migration/resend
Use the Resend Migration API to move templates, contacts, audiences, custom fields, topics, and unsubscribes from Resend into AutoSend.
## Overview
AutoSend provides a set of public API endpoints to migrate your Resend assets into your AutoSend project:
* Templates
* Contacts and audiences (contact lists)
* Contact properties (custom fields)
* Topics (suppression groups)
* Unsubscribed contacts
All endpoints are authenticated with your **AutoSend project API key** sent as a Bearer token. The Resend API key required to read from your Resend account is passed in the request body for each call. It is never persisted in plaintext - it is encrypted while the background job is queued.
## Prerequisites
Before you start, make sure you have:
1. An **AutoSend project API key** - create one on the **API Keys** page. Keys look like `AS_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx`.
2. A **Resend API key** - generate one in Resend under *API Keys*. The key needs read access to templates, contacts, audiences, contact properties, and topics. Keys look like `re_xxxxxxxxxxxxxxxxxxxxxxxx`.
3. The base URL for all requests: `https://api.autosend.com/v1`
### Authentication
Every request uses two headers:
| Header | Value |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer ` |
| `Content-Type` | `application/json` |
## How Resend Maps to AutoSend
Resend and AutoSend model their assets a little differently. The migration applies these mappings:
| Resend | AutoSend | Notes |
| -------------------- | ------------------------ | ------------------------------------------------------------------------------------- |
| Template | Template | HTML, subject, and name are migrated. Templates using `#each` are flagged for review. |
| Contact property | Custom field | Type is mapped (`string`/`text` to string, `number`, `boolean`, `date`). |
| Audience | Contact list | List metadata is created; members are associated in the contacts phase. |
| Topic | Suppression group | Created as an **empty** group - Resend's API exposes no per-topic subscription state. |
| Unsubscribed contact | Global suppression entry | Resend has no unsubscribe groups, so unsubscribed contacts are suppressed globally. |
## Recommended Flow
Call `POST /migrations/resend/plan` to confirm the Resend key is valid and see what will be migrated.
Call `POST /migrations/resend/migrate` with `migrateAll: true` or with specific IDs you want to import.
Use `POST /migrations/resend/template` to migrate selected templates only.
## Endpoints
### Get migration plan
Fetches a preview of everything that can be migrated from your Resend account: templates, contact properties (custom fields), audiences, topics, the total contact count, and the count of unsubscribed contacts.
**`POST /v1/migrations/resend/plan`**
#### Request body
| Field | Type | Required | Description |
| -------------- | ------ | -------- | ------------------- |
| `resendApiKey` | string | yes | Your Resend API key |
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/resend/plan \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"resendApiKey": "re_your_resend_api_key"
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/resend/plan", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
resendApiKey: process.env.RESEND_API_KEY,
}),
});
const data = await res.json();
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/resend/plan",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={"resendApiKey": os.environ["RESEND_API_KEY"]},
)
data = res.json()
```
#### Response `200`
```json expandable theme={null}
{
"success": true,
"message": "Migration plan fetched successfully",
"data": {
"contactsCount": 12453,
"unsubscribedCount": 87,
"templatesCount": 24,
"templates": [
{
"templateId": "tmpl_abc123",
"templateName": "Welcome email",
"status": "active",
"updatedAt": "2025-08-12T10:24:11Z"
}
],
"customFields": [
{
"resendFieldId": "1",
"resendFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
],
"audiences": [
{
"id": "aud-uuid-1",
"name": "Newsletter subscribers"
}
],
"audiencesCount": 5,
"topics": [
{
"id": "topic-uuid-1",
"name": "Product updates",
"description": "Monthly product newsletter",
"defaultSubscription": "opt_in",
"visibility": "public"
}
],
"topicsCount": 3
}
}
```
### Run the full migration
Kicks off a background migration job that imports the selected Resend assets into your AutoSend project. Returns a `bulkOperationId` immediately; the work continues in the background.
**`POST /v1/migrations/resend/migrate`**
#### Request body
| Field | Type | Required | Default | Description |
| --------------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `resendApiKey` | string | yes | - | Your Resend API key |
| `migrateAll` | boolean | no | `false` | If `true`, migrate everything returned by `/plan`. Per-type ID arrays below are populated from the plan when empty. |
| `templateIds` | string\[] | no | `[]` | Specific Resend template IDs to migrate |
| `audienceIds` | string\[] | no | `[]` | Specific Resend audience IDs to migrate (each becomes an AutoSend contact list) |
| `topicIds` | string\[] | no | `[]` | Specific Resend topic IDs to migrate (each becomes an AutoSend suppression group) |
| `customFieldMappings` | object\[] | no | `[]` | Resend to AutoSend custom field mappings (see schema below) |
| `ignoreTemplates` | boolean | no | `false` | Skip templates phase |
| `ignoreCustomFields` | boolean | no | `false` | Skip custom fields phase |
| `ignoreAudiences` | boolean | no | `false` | Skip audiences (contact lists) phase |
| `ignoreTopics` | boolean | no | `false` | Skip topics (suppression groups) phase |
| `ignoreContacts` | boolean | no | `false` | Skip contacts phase |
| `ignoreSuppressions` | boolean | no | `false` | Skip the global unsubscribe (suppression) phase |
`customFieldMappings` item schema:
```json theme={null}
{
"resendFieldId": "1",
"resendFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
```
`fieldType` must be one of: `string`, `number`, `boolean`, `date`.
#### Migration phases
The job runs in this order:
1. **Custom fields** - creates AutoSend custom field definitions from the mappings.
2. **Templates** - fetches each Resend template's HTML and subject and creates the matching AutoSend template. Templates containing `#each` are flagged for manual review.
3. **Audiences to contact lists** - creates the AutoSend list metadata for each audience.
4. **Contacts** - imports all global Resend contacts, then associates each audience's members with the contact list created in step 3. Contact properties are mapped onto custom fields via `customFieldMappings`.
5. **Topics to suppression groups** - creates a matching (empty) suppression group for each topic.
6. **Suppressions** - globally suppresses every Resend contact marked as `unsubscribed`.
#### Example A - migrate everything
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/resend/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"resendApiKey": "re_your_resend_api_key",
"migrateAll": true
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/resend/migrate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
resendApiKey: process.env.RESEND_API_KEY,
migrateAll: true,
}),
});
const { data } = await res.json();
console.log(data.bulkOperationId);
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/resend/migrate",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={
"resendApiKey": os.environ["RESEND_API_KEY"],
"migrateAll": True,
},
)
print(res.json()["data"]["bulkOperationId"])
```
#### Example B - migrate selected assets only
```bash theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/resend/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"resendApiKey": "re_your_resend_api_key",
"templateIds": ["tmpl_abc123", "tmpl_def456"],
"audienceIds": ["aud-uuid-1"],
"customFieldMappings": [
{
"resendFieldId": "1",
"resendFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
],
"ignoreTopics": true
}'
```
#### Example C - contacts only, skip everything else
```bash theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/resend/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"resendApiKey": "re_your_resend_api_key",
"migrateAll": true,
"ignoreTemplates": true,
"ignoreCustomFields": true,
"ignoreAudiences": true,
"ignoreTopics": true,
"ignoreSuppressions": true
}'
```
#### Response `202 Accepted`
```json theme={null}
{
"success": true,
"message": "Resend migration initiated successfully",
"data": {
"bulkOperationId": "65fa1d2b8c9a4f1234567890",
"status": "PENDING",
"message": "Resend migration initiated successfully"
}
}
```
Hold on to the `bulkOperationId`. Your AutoSend dashboard shows progress against it, and progress is also published in real time over the project's Pusher channel as the job moves through each phase.
#### Common errors
| HTTP | Code | Meaning |
| ---- | ---------------------------------- | ----------------------------------------------------------------------- |
| 400 | `RESEND_MIGRATION_IN_PROGRESS` | A migration is already running for this project - wait for it to finish |
| 400 | `RESEND_NO_ITEMS_TO_MIGRATE` | Nothing selected and `migrateAll` was not set |
| 500 | `RESEND_FAILED_TO_FETCH_PLAN` | Resend API key is invalid or rejected |
| 500 | `RESEND_MIGRATION_CREATION_FAILED` | Could not schedule the migration job - retry shortly |
### Migrate one or more templates
Migrate specific Resend templates without touching anything else. Useful for one-off moves or for syncing a template after edits in Resend.
Always test migrated templates that contain complex Handlebars expressions such as `#each` before sending production traffic to them.
**`POST /v1/migrations/resend/template`**
#### Request body
| Field | Type | Required | Default | Description |
| ------------------- | --------- | -------- | ------- | ----------------------------------------------------------------------------------------- |
| `resendApiKey` | string | yes | - | Your Resend API key |
| `resendTemplateIds` | string\[] | yes | - | Resend template IDs to migrate (min 1) |
| `onExistUpdateHTML` | boolean | no | `false` | If a template with the same ID already exists in AutoSend, overwrite its HTML and subject |
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/resend/template \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"resendApiKey": "re_your_resend_api_key",
"resendTemplateIds": ["tmpl_abc123", "tmpl_def456"],
"onExistUpdateHTML": true
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/resend/template", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
resendApiKey: process.env.RESEND_API_KEY,
resendTemplateIds: ["tmpl_abc123", "tmpl_def456"],
onExistUpdateHTML: true,
}),
});
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/resend/template",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={
"resendApiKey": os.environ["RESEND_API_KEY"],
"resendTemplateIds": ["tmpl_abc123", "tmpl_def456"],
"onExistUpdateHTML": True,
},
)
```
#### Response `200`
```json theme={null}
{
"success": true,
"message": "Template migrated successfully",
"data": [
{
"templateId": "tmpl_abc123",
"templateName": "Welcome email",
"subject": "Welcome to Acme",
"templateType": "TRANSACTIONAL",
"createdAt": "2026-05-14T08:12:11.234Z"
}
]
}
```
## FAQ
The key is held in memory for the lifetime of the migration request and encrypted at rest while the background job is queued. It is never logged or stored in plaintext.
Resend's contact object exposes no per-topic subscription state, so the migration can create the group but cannot derive its members from the API. Globally unsubscribed contacts are still captured in the separate suppressions phase.
Yes. Templates are upserted by their Resend `templateId` when `onExistUpdateHTML: true`. Contacts and suppressed emails are upserted by email address, so duplicates are safe. Re-creating an already-existing custom field will fail that one creation and be reported in the metrics, but the run continues. Only one migration can run per project at a time.
Templates and topics are usually done in seconds. Contacts depend on how many you have. Resend's API is rate-limited to roughly 2 requests/second, and the migration spaces requests out and retries on `429` to stay within that limit, so large contact counts take proportionally longer. The request returns immediately - the job keeps running after your HTTP connection closes.
During the contacts phase, each Resend contact's `properties` are matched against your `customFieldMappings` (by `resendFieldId`, falling back to `resendFieldName`) and written onto the AutoSend contact's custom fields. Include the mappings in `/migrate` (or use `migrateAll`) to populate them.
## Support
If a migration fails or produces unexpected results, send us:
1. The `bulkOperationId` returned by `/migrate`
2. Your AutoSend project ID
3. The approximate time of the request
Email [support@autosend.com](mailto:support@autosend.com) with these details and we'll investigate.
## Next Steps
Manage the templates you just migrated from Resend.
Browse and segment the contacts imported from Resend.
Review the suppression groups created during migration.
Verify your sending domain so migrated templates can start sending.
# Migrate from SendGrid to AutoSend
Source: https://docs.autosend.com/migration/sendgrid
Use the SendGrid Migration API to move dynamic templates, contacts, lists, custom fields, and unsubscribe groups from SendGrid into AutoSend.
## Overview
AutoSend provides a set of public API endpoints to migrate your SendGrid assets into your AutoSend project:
* Dynamic templates
* Contacts and contact lists
* Custom fields
* Unsubscribe groups and their suppressed emails
All endpoints are authenticated with your **AutoSend project API key** sent as a Bearer token. The SendGrid API key required to read from your SendGrid account is passed in the request body for each call. It is never persisted in plaintext.
## Prerequisites
Before you start, make sure you have:
1. An **AutoSend project API key** - create one on the **API Keys** page. Keys look like `AS_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx`.
2. A **SendGrid API key** - generate one in SendGrid under *Settings → API Keys*. The key should have at least these scopes:
* `template_engine.read` - fetch templates
* `marketing.read` - fetch contacts, lists, and custom field definitions
* `suppressions.read` - fetch unsubscribe groups and suppressed emails
3. The base URL for all requests: `https://api.autosend.com/v1`
### Authentication
Every request uses two headers:
| Header | Value |
| --------------- | -------------------------------- |
| `Authorization` | `Bearer ` |
| `Content-Type` | `application/json` |
## Recommended Flow
Call `POST /migrations/sendgrid/plan` to confirm the SendGrid key is valid and see what will be migrated.
Call `POST /migrations/sendgrid/migrate` with `migrateAll: true` or with specific IDs you want to import.
Use `POST /migrations/sendgrid/sg-template` to migrate selected templates, or `POST /migrations/sendgrid/sg-unsubscribe-group` to migrate a single unsubscribe group.
## Endpoints
### Get migration plan
Fetches a preview of everything that can be migrated from your SendGrid account: templates, unsubscribe groups, custom fields, contact lists, and the total contact count.
**`POST /v1/migrations/sendgrid/plan`**
#### Request body
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | --------------------- |
| `sendgridApiKey` | string | yes | Your SendGrid API key |
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/plan \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key"
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/sendgrid/plan", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sendgridApiKey: process.env.SENDGRID_API_KEY,
}),
});
const data = await res.json();
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/sendgrid/plan",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={"sendgridApiKey": os.environ["SENDGRID_API_KEY"]},
)
data = res.json()
```
#### Response `200`
```json expandable theme={null}
{
"success": true,
"message": "Migration plan fetched successfully",
"data": {
"contactsCount": 12453,
"templatesCount": 24,
"templates": [
{
"templateId": "d-abc123",
"templateName": "Welcome email",
"subject": "Welcome to Acme",
"htmlContent": "...",
"thumbnailUrl": "https://...",
"updatedAt": "2025-08-12T10:24:11Z"
}
],
"unsubscribeGroups": [
{
"groupId": "12345",
"name": "Product updates",
"description": "Monthly product newsletter",
"isActive": true,
"unsubscribeCount": 42
}
],
"customFields": [
{
"sendgridFieldId": "e1_T",
"sendgridFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
],
"reservedFields": [
{
"sendgridFieldId": "_rf0_T",
"sendgridFieldName": "first_name",
"fieldType": "string"
}
],
"contactLists": [
{ "id": "list-uuid-1", "name": "Newsletter subscribers", "contactCount": 8123 }
],
"contactListsCount": 5
}
}
```
### Run the full migration
Kicks off a background migration job that imports the selected SendGrid assets into your AutoSend project. Returns a `bulkOperationId` immediately; the work continues in the background.
**`POST /v1/migrations/sendgrid/migrate`**
#### Request body
| Field | Type | Required | Default | Description |
| ------------------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `sendgridApiKey` | string | yes | - | Your SendGrid API key |
| `migrateAll` | boolean | no | `false` | If `true`, migrate everything returned by `/plan`. Per-type ID arrays below are populated from the plan when empty. |
| `templateIds` | string\[] | no | `[]` | Specific SendGrid template IDs to migrate |
| `unsubscribeGroupIds` | string\[] | no | `[]` | Specific SendGrid unsubscribe group IDs |
| `contactListIds` | string\[] | no | `[]` | Specific SendGrid contact list IDs |
| `customFieldMappings` | object\[] | no | `[]` | SendGrid to AutoSend custom field mappings (see schema below) |
| `ignoreTemplates` | boolean | no | `false` | Skip templates phase |
| `ignoreUnsubscribeGroups` | boolean | no | `false` | Skip unsubscribe groups phase |
| `ignoreContactLists` | boolean | no | `false` | Skip contact lists phase |
| `ignoreCustomFields` | boolean | no | `false` | Skip custom fields phase |
| `ignoreContacts` | boolean | no | `false` | Skip contacts phase |
`customFieldMappings` item schema:
```json theme={null}
{
"sendgridFieldId": "e1_T",
"sendgridFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
```
`fieldType` must be one of: `string`, `number`, `date`.
#### Migration phases
The job runs in this order:
1. **Custom fields** - creates AutoSend custom field definitions from the mappings.
2. **Templates** - fetches each SendGrid template's active version. Images hosted on `cdn.mcauto-images-production.sendgrid.net` are downloaded and re-uploaded to your AutoSend media library, and the HTML is rewritten to point at the new URLs.
3. **Unsubscribe groups** - creates the matching suppression group in AutoSend and imports its suppressed email list.
4. **Contact lists** - creates the AutoSend list metadata.
5. **Contacts** - uses SendGrid's Export Contacts API to import all global contacts, then associates them with the lists created in step 4.
#### Example A - migrate everything
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key",
"migrateAll": true
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/sendgrid/migrate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sendgridApiKey: process.env.SENDGRID_API_KEY,
migrateAll: true,
}),
});
const { data } = await res.json();
console.log(data.bulkOperationId);
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/sendgrid/migrate",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={
"sendgridApiKey": os.environ["SENDGRID_API_KEY"],
"migrateAll": True,
},
)
print(res.json()["data"]["bulkOperationId"])
```
#### Example B - migrate selected assets only
```bash theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key",
"templateIds": ["d-abc123", "d-def456"],
"contactListIds": ["list-uuid-1"],
"customFieldMappings": [
{
"sendgridFieldId": "e1_T",
"sendgridFieldName": "company",
"autosendFieldName": "company",
"fieldType": "string"
}
],
"ignoreUnsubscribeGroups": true
}'
```
#### Example C - contacts only, skip everything else
```bash theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/migrate \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key",
"migrateAll": true,
"ignoreTemplates": true,
"ignoreUnsubscribeGroups": true,
"ignoreCustomFields": true
}'
```
#### Response `202 Accepted`
```json theme={null}
{
"success": true,
"message": "SendGrid migration initiated successfully",
"data": {
"bulkOperationId": "65fa1d2b8c9a4f1234567890",
"status": "PENDING",
"message": "SendGrid migration initiated successfully"
}
}
```
Hold on to the `bulkOperationId`. Your AutoSend dashboard shows progress against it.
#### Common errors
| HTTP | Code | Meaning |
| ---- | ------------------------------------ | ----------------------------------------------------------------------- |
| 400 | `SENDGRID_MIGRATION_IN_PROGRESS` | A migration is already running for this project - wait for it to finish |
| 400 | `SENDGRID_NO_ITEMS_TO_MIGRATE` | Nothing selected and `migrateAll` was not set |
| 500 | `SENDGRID_FAILED_TO_FETCH_PLAN` | SendGrid API key is invalid or rejected |
| 500 | `SENDGRID_MIGRATION_CREATION_FAILED` | Could not schedule the migration job - retry shortly |
### Migrate one or more templates
Migrate specific SendGrid dynamic templates without touching anything else. Useful for one-off moves or for syncing a template after edits in SendGrid.
Always test migrated templates that contain complex Handlebars expressions before sending production traffic to them.
**`POST /v1/migrations/sendgrid/sg-template`**
#### Request body
| Field | Type | Required | Default | Description |
| --------------------- | --------- | -------- | ------- | ----------------------------------------------------------------------------------------- |
| `sendgridApiKey` | string | yes | - | Your SendGrid API key |
| `sendgridTemplateIds` | string\[] | yes | - | SendGrid dynamic template IDs (e.g. `d-abc123`) |
| `onExistUpdateHTML` | boolean | no | `false` | If a template with the same ID already exists in AutoSend, overwrite its HTML and subject |
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/sg-template \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key",
"sendgridTemplateIds": ["d-abc123", "d-def456"],
"onExistUpdateHTML": true
}'
```
```javascript NodeJS theme={null}
const res = await fetch("https://api.autosend.com/v1/migrations/sendgrid/sg-template", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sendgridApiKey: process.env.SENDGRID_API_KEY,
sendgridTemplateIds: ["d-abc123", "d-def456"],
onExistUpdateHTML: true,
}),
});
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/sendgrid/sg-template",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={
"sendgridApiKey": os.environ["SENDGRID_API_KEY"],
"sendgridTemplateIds": ["d-abc123", "d-def456"],
"onExistUpdateHTML": True,
},
)
```
#### Response `200`
```json theme={null}
{
"success": true,
"message": "Template migrated successfully",
"data": [
{
"templateId": "d-abc123",
"templateName": "Welcome email",
"subject": "Welcome to Acme",
"templateType": "TRANSACTIONAL",
"createdAt": "2026-05-14T08:12:11.234Z"
}
]
}
```
Images referenced from `cdn.mcauto-images-production.sendgrid.net` are automatically downloaded and re-hosted on AutoSend so your templates continue to render after you turn SendGrid off.
### Migrate one unsubscribe group
Migrate a single SendGrid unsubscribe group (ASM group), and optionally its suppressed email addresses.
**`POST /v1/migrations/sendgrid/sg-unsubscribe-group`**
#### Request body
| Field | Type | Required | Default | Description |
| ---------------------------- | ---------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `sendgridApiKey` | string | yes | - | Your SendGrid API key |
| `sendgridUnsubscribeGroupId` | string \| number | yes | - | The SendGrid unsubscribe group ID |
| `addEmailsInSuppression` | boolean | no | `true` | Also import all currently suppressed email addresses for the group |
| `groupExist` | boolean | no | `false` | If `true`, skip creating a new AutoSend suppression group and only import the emails into the existing group with the matching `groupId` |
```bash cURL theme={null}
curl --request POST \
--url https://api.autosend.com/v1/migrations/sendgrid/sg-unsubscribe-group \
--header 'Authorization: Bearer AS_YOUR_AUTOSEND_KEY' \
--header 'Content-Type: application/json' \
--data '{
"sendgridApiKey": "SG.your_sendgrid_api_key",
"sendgridUnsubscribeGroupId": 12345,
"addEmailsInSuppression": true
}'
```
```javascript NodeJS theme={null}
const res = await fetch(
"https://api.autosend.com/v1/migrations/sendgrid/sg-unsubscribe-group",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTOSEND_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sendgridApiKey: process.env.SENDGRID_API_KEY,
sendgridUnsubscribeGroupId: 12345,
addEmailsInSuppression: true,
}),
}
);
```
```python Python theme={null}
import os
import requests
res = requests.post(
"https://api.autosend.com/v1/migrations/sendgrid/sg-unsubscribe-group",
headers={
"Authorization": f"Bearer {os.environ['AUTOSEND_API_KEY']}",
"Content-Type": "application/json",
},
json={
"sendgridApiKey": os.environ["SENDGRID_API_KEY"],
"sendgridUnsubscribeGroupId": 12345,
"addEmailsInSuppression": True,
},
)
```
#### Response `200`
When the group was newly created and emails were imported:
```json theme={null}
{
"success": true,
"message": "Unsubscribe group migrated successfully",
"data": {
"groupId": "12345",
"addedCount": 42,
"entries": [
{ "id": "65fa...", "email": "user@example.com", "groupId": "12345" }
]
}
}
```
If the group already existed and `addEmailsInSuppression` was `false`, the response is the suppression group DTO instead.
## FAQ
The key is held in memory for the lifetime of the migration job and encrypted at rest while the background job is queued.
Yes. Templates are upserted by their SendGrid `templateId` when `onExistUpdateHTML: true`. Contacts and suppressed emails are upserted by email address, so duplicates are safe. Custom fields are not deduplicated - re-running custom-field migration will fail the duplicate creations and report them in the metrics, but the run continues.
Templates and unsubscribe groups are usually done in seconds. Contacts use SendGrid's Export Contacts API, which takes around 1 minute per 100k contacts on SendGrid's side, plus the time to import on our side. The request returns immediately - the job keeps running after your HTTP connection closes.
SendGrid-hosted images (`cdn.mcauto-images-production.sendgrid.net`) are automatically downloaded and re-uploaded to AutoSend's media library, and the HTML is rewritten to point at the new URLs.
`/plan` returns whatever the scopes allow. If a scope is missing (e.g. you didn't grant `suppressions.read`), that section comes back empty and the corresponding migration phase silently skips it. Grant the missing scope and re-run if needed.
## Support
If a migration fails or produces unexpected results, send us:
1. The `bulkOperationId` returned by `/migrate`
2. Your AutoSend project ID
3. The approximate time of the request
Email [support@autosend.com](mailto:support@autosend.com) with these details and we'll investigate.
## Next Steps
Manage the templates you just migrated from SendGrid.
Browse and segment the contacts imported from SendGrid.
Review the suppression groups created during migration.
Verify your sending domain so migrated templates can start sending.
# Billing
Source: https://docs.autosend.com/others/account/billing
Manage your AutoSend subscription, payment methods, and invoices. Billing is handled through Stripe and is accessible to workspace Admins only.
AutoSend uses [Stripe](https://stripe.com) to process payments. Your subscription, payment methods, and invoice history are all managed from one place in the dashboard.
Billing is restricted to workspace **Admins**. Members do not see the Billing page. See [Team
roles and permissions](/others/team) for the full breakdown.
## Access your billing details
Open Account Settings > Billing to view and manage everything related to your account's billing.
From this page you can:
* **Review your current plan** and the limits included with it
* **Upgrade, downgrade, or cancel** your subscription
* **Update payment methods** (add a new card, change the default card, or remove an old one)
* **Update billing details** such as company name, billing address, and tax ID
* **Download past invoices** as PDF for accounting or reimbursement
## Invoices
Every charge generates an invoice that is emailed to the billing contact on file and stored in your Billing page. Invoices include your subscription charges, any add-ons, and any [email credits](/others/account/usage#email-credits) you've purchased.
If you need to update the email address that receives invoices, change it from the billing details section on the same page.
## Payment methods
You can store multiple payment methods and choose which one to use as the default. AutoSend charges the default payment method automatically at the start of each billing cycle.
If a charge fails, AutoSend retries it a few times. If the payment continues to fail, **sending
will be paused after 7 days until the invoice is cleared.** Make sure your default card is valid
and has sufficient funds.
## Plans and pricing
For a full comparison of available plans and what each one includes, visit the pricing page.
To understand how plan limits work, when sending is paused, and how email credits extend your sending capacity, see [Usage](/others/account/usage).
## Account inactivity
If your account does not have an active subscription, it is considered inactive. Inactive accounts are **queued for permanent deletion after 60 days**, including all projects, contacts, templates, campaigns, and sending history.
To keep your account and data, resubscribe to a paid plan before the 60-day window ends.
For full details, see the Account Inactivity and Data Deletion section of our Terms.
## FAQs
Only workspace **Admins** can access billing. Members do not see this page. See [Team roles and permissions](/others/team) for details.
Open
Account Settings > Billing
and edit your billing details from there. Updates apply to all future invoices.
All invoices are listed on the Billing page. Open any invoice to view it in your browser or
download the PDF.
AutoSend will retry the payment a few times and notify the billing contact by email. If the
payment continues to fail, sending will be paused on your account after 7 days until the invoice
is cleared.
Yes. You can upgrade or downgrade your plan from the Billing page. Upgrades take effect immediately. Downgrades take effect at the end of your current billing cycle.
Accounts without an active subscription are considered inactive and are queued for permanent
deletion after 60 days, along with all associated data. Resubscribe before the 60-day window ends
to keep your data. See the
Account Inactivity and Data Deletion
section of our Terms for the full policy.
# Usage
Source: https://docs.autosend.com/others/account/usage
Track how many emails you have sent against your plan limit, get notified before you run out, and top up with email credits or auto-reload.
Open Account Settings > Usage to see how many emails you have sent in the current billing cycle, how close you are to your plan limit, and to purchase or manage email credits.
## Usage Limits
Every AutoSend plan includes a monthly email sending limit. You can send emails up to your plan limit during the billing cycle. Once you reach the limit, sending is **paused** until the next cycle begins or until you add more capacity through [email credits](#email-credits).
### Notifications
AutoSend emails workspace Admins automatically as you approach your limit:
* **At 90% of your plan limit**: a heads-up so you have time to upgrade or top up before sending is paused.
* **At 100% of your plan limit**: confirmation that sending is paused on your account.
### What gets paused
When you hit your limit, both transactional and marketing emails stop sending. Active automation will pause sending until capacity is available again.
### How to keep sending
You have two options when you reach your plan limit:
* **Upgrade your plan**: move to a higher tier to unlock a larger monthly limit. See the [pricing page](https://autosend.com/account/compare-plans).
* **Buy email credits**: top up your account with [email credits](#email-credits) that add to your sending capacity for the current cycle and beyond.
Your effective sending limit each cycle is **plan limit + email credit balance**. As long as the
total is above zero, sending continues.
## Email Credits
Email credits let you purchase additional sending capacity in advance, on top of your plan. They are useful when you need extra headroom for a campaign, want a buffer against unexpected spikes, or simply prefer to pay for usage upfront.
Buy credits from Account Settings > Usage in the AutoSend dashboard.
### How email credits work
* **Top-up in advance**: buy credits whenever you want from the Usage page.
* **Rollover and never expire**: unused credits stay on your account indefinitely and carry over from one billing cycle to the next.
* **Stack with your plan**: credits are consumed only after your plan's included emails for the current cycle are used.
* **Non-refundable**: credits cannot be refunded once purchased.
* **Non-transferable**: credits are tied to your workspace and cannot be moved to another account.
Email credits are non-refundable and non-transferable. Purchase the amount you expect to use.
### Auto-reload
Auto-reload tops up your email credits automatically when your balance gets low, so sending never pauses unexpectedly.
You can enable auto-reload from the Email Credits section on the Usage page. When configured, AutoSend will:
1. Watch your remaining sending capacity (plan limit + credit balance).
2. Trigger a top-up as soon as your balance drops to the **threshold** you set.
3. Charge your default payment method and add the configured amount of credits to your account.
Auto-reload uses the default payment method on file in [Billing](/others/account/billing). Make
sure your card is valid to avoid failed reloads.
## FAQs
Sending is paused on your account until the next billing cycle begins or until you add capacity through an upgrade or email credits. AutoSend notifies Admins by email at 90% and 100% of the limit.
No. Plan emails reset at the start of each billing cycle. Only **email credits** roll over and
never expire.
Email credits are consumed only after you exhaust the emails included with your plan for the
current billing cycle.
No. Email credits are non-refundable. They also cannot be transferred to another workspace.
Auto-reload triggers when your remaining sending capacity drops to the threshold you set. The
configured amount of credits is then purchased using your default payment method.
Only workspace **Admins** can purchase credits, enable auto-reload, or change billing details. See [Team roles and permissions](/others/team) for the full role breakdown.
# Encrypted Payloads (JWE)
Source: https://docs.autosend.com/others/encrypted-payloads
Encrypt your API request bodies end-to-end with JWE before sending them to AutoSend. Encryption is fully opt-in, per request.
AutoSend optionally accepts **JWE-encrypted request bodies** on the public API. You encrypt your JSON payload with AutoSend's public key, and the backend decrypts it transparently. Your business logic and the API response are unchanged.
Plaintext requests keep working exactly as before, so encryption is fully opt-in, per request.
Transport is already protected by HTTPS/TLS. JWE adds an **application-layer** of protection so the payload stays encrypted end-to-end (for example, through logs, proxies, or intermediaries) until it reaches AutoSend.
***
## How it works
Get AutoSend's public key from the JWKS endpoint and cache it. The recommended (active) key is always listed first.
Encrypt the complete JSON body into a JWE compact string using `RSA-OAEP-256` for key management and `A256GCM` for content encryption.
POST `{ "encryptedData": "" }` to any public endpoint with the `X-Payload-Encryption: jwe` and `X-Key-Id` headers.
***
## Encryption standard
| Field | Value |
| -------------------------- | -------------- |
| Key management (`alg`) | `RSA-OAEP-256` |
| Content encryption (`enc`) | `A256GCM` |
| Serialization | JWE Compact |
| Key size | RSA 2048-bit |
***
## Public key endpoint (JWKS)
Fetch AutoSend's public key from the JWKS endpoint:
```http theme={null}
GET https://api.autosend.com/v1/jwks.json
```
```json theme={null}
{
"keys": [
{
"kty": "RSA",
"n": "…",
"e": "AQAB",
"kid": "key_2026_06",
"alg": "RSA-OAEP-256",
"use": "enc"
}
]
}
```
Fetch the JWKS periodically and cache it. Use the `kid` of the key you encrypt with. AutoSend supports multiple keys at once so keys can rotate without breaking in-flight requests.
***
## Node.js example
This example fetches the public key, encrypts a contact payload, and calls the contacts API. It uses the [`jose`](https://github.com/panva/jose) library.
```bash Install theme={null}
npm install jose
```
```js encrypt-and-send.js expandable theme={null}
import { importJWK, CompactEncrypt } from 'jose';
const API_BASE = 'https://api.autosend.com';
const API_KEY = 'AS_xxx.yyy'; // your AutoSend API key
const JWE_ALG = 'RSA-OAEP-256';
const JWE_ENC = 'A256GCM';
// 1. Fetch AutoSend's public key from the JWKS endpoint.
async function fetchPublicKey() {
const res = await fetch(`${API_BASE}/v1/jwks.json`);
if (!res.ok) throw new Error(`Failed to fetch JWKS: ${res.status}`);
const { keys } = await res.json();
const jwk = keys[0]; // the active key is listed first
const publicKey = await importJWK(jwk, JWE_ALG);
return { publicKey, kid: jwk.kid };
}
// 2. Encrypt the JSON payload into a JWE compact string.
async function encryptPayload(payload, publicKey, kid) {
const data = new TextEncoder().encode(JSON.stringify(payload));
return new CompactEncrypt(data)
.setProtectedHeader({ alg: JWE_ALG, enc: JWE_ENC, kid })
.encrypt(publicKey);
}
// 3. Send the encrypted request to the contacts API.
async function createEncryptedContact(contact) {
const { publicKey, kid } = await fetchPublicKey();
const encryptedData = await encryptPayload(contact, publicKey, kid);
const res = await fetch(`${API_BASE}/v1/contacts/email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
'X-Payload-Encryption': 'jwe',
'X-Key-Id': kid,
},
body: JSON.stringify({ encryptedData }),
});
return res.json();
}
// Usage
const result = await createEncryptedContact({
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe',
contactProperties: {
isVerified: false,
industry: 'Telecommunications',
},
});
console.log(result);
```
Any language with a JWE library works. Equivalent libraries exist for Python
(`jwcrypto` / `python-jose`), Go, Java, Ruby, and more. Always use `alg =
RSA-OAEP-256` and `enc = A256GCM`.
***
## Request format
Send the JWE as `encryptedData`, with the encryption headers:
```http Encrypted theme={null}
POST /v1/contacts/email
Authorization: Bearer AS_xxx.yyy
Content-Type: application/json
X-Payload-Encryption: jwe
X-Key-Id: key_2026_06
{ "encryptedData": "eyJhbGciOiJSU0EtT0FFUC0yNTYi..." }
```
```http Plaintext (default) theme={null}
POST /v1/contacts/email
Authorization: Bearer AS_xxx.yyy
Content-Type: application/json
{ "email": "john@example.com", "firstName": "John" }
```
Set to `jwe` for encrypted requests. Omit (or use `none`) for plaintext.
The `kid` of the public key you encrypted with. If omitted, AutoSend reads the `kid` from the JWE protected header.
The JWE compact string containing your complete JSON payload.
***
## Works with any public endpoint
The encrypted request format is the same for every `/v1` endpoint. Just put the JWE inside `{ "encryptedData": "…" }` and add the headers. For example, to send an email:
```http theme={null}
POST /v1/mails/send
Authorization: Bearer AS_xxx.yyy
Content-Type: application/json
X-Payload-Encryption: jwe
X-Key-Id: key_2026_06
{ "encryptedData": "eyJhbGciOiJSU0EtT0FFUC0yNTYi..." }
```
***
## Error handling
If the encrypted payload is malformed, the key id is unknown, or decryption fails, the API responds with **HTTP 400**:
```json theme={null}
{
"success": false,
"error": {
"message": "Invalid encrypted payload",
"code": "INVALID_ENCRYPTED_PAYLOAD",
"status": 400
}
}
```
The `encryptedData` value is not a valid JWE compact string. Re-encrypt the payload with the public key from the JWKS endpoint.
The `kid` doesn't match any active AutoSend key. Refresh the JWKS and use the `kid` from the returned key.
The payload was encrypted with the wrong key or a different algorithm. Use `RSA-OAEP-256` + `A256GCM` and AutoSend's current public key.
***
## FAQ
No. It's optional and per request. Endpoints accept plaintext bodies exactly as before. Only requests with the `jwe` header or an `encryptedData` field are decrypted.
Cache it and refresh periodically (for example, daily or weekly). AutoSend supports key rotation, so always encrypt with the `kid` from the latest JWKS.
Any language with a JWE library. The example uses Node.js with `jose`; equivalent libraries exist for Python (`jwcrypto` / `python-jose`), Go, Java, Ruby, and more. Use `alg = RSA-OAEP-256` and `enc = A256GCM`.
Yes. JWE complements transport security, it does not replace it. Always call AutoSend over HTTPS and add JWE on top when you need application-layer encryption.
***
## Related Resources
Create and manage the API keys you authenticate requests with.
Explore every public endpoint you can send encrypted payloads to.
The contacts endpoint used in the encryption example above.
Send transactional emails, with or without an encrypted body.
# Suppressions
Source: https://docs.autosend.com/others/suppressions
Suppressions in AutoSend help ensure that your emails are only sent to valid, consenting recipients.
Suppressions in AutoSend help ensure that your emails are only sent to valid, consenting recipients. By automatically maintaining these suppression lists, AutoSend improves your deliverability, protects your sender reputation, and ensures compliance with email regulations.
You can access all suppression records from the Suppressions ↗ tab in the side-panel. Each record includes:
* **Email address**
* **Suppression reason**
* **Date added**
You can filter, export, or manually remove suppressions if necessary.
***
## Types of Suppressions
### Global Unsubscribes
Global unsubscribes apply across your entire AutoSend account.
When a contact globally unsubscribes, they are removed from all future sends—both marketing campaigns and transactional emails (unless explicitly required by law, such as password resets or receipts).
**Example:** If someone unsubscribes from your newsletter and it’s a global unsubscribe, they won’t receive any further emails from your domain, regardless of list or segment.
***
### Group Unsubscribes
Group unsubscribes apply only to a specific email list or category.
They give recipients more control over the kind of emails they receive, rather than unsubscribing from everything.
**Example:** A contact may unsubscribe from *promotional offers* but stay subscribed to *product updates* or *security alerts.*
***
### Reported Spam
If a recipient marks your email as spam, AutoSend automatically adds them to your suppression list.
This ensures you don’t email them again, protecting your sender reputation and reducing spam complaints.
Multiple spam complaints can lead to domain or IP blacklisting, so monitoring this suppression
type is essential.
***
### Bounces
A bounce occurs when an email cannot be delivered to a recipient’s inbox.
**Types of Bounces:**
* **Hard Bounce:** Permanent delivery failure (invalid or non-existent address). The address is automatically suppressed.
* **Soft Bounce:** Temporary issue (like a full inbox or server problem). AutoSend retries for a short period before suppressing if the issue persists.
***
### Invalid emails
Invalid emails are addresses that fail basic syntax or DNS checks and can never receive messages.
AutoSend detects these during list imports or send attempts and **suppresses them immediately** to protect your domain reputation, deliverability, and avoid spam traps.
Common causes include typos (e.g., gmial.com), non-existent domains, malformed formats, or disposable addresses that no longer exist.
# Team
Source: https://docs.autosend.com/others/team
Collaborate with your team by inviting members to your AutoSend workspace. This guide covers how to invite, manage, and remove team members.
## Overview
AutoSend's team feature lets you invite colleagues to collaborate within your workspace. With the new roles system, you have granular control over what each person can access. There are two roles available: **Admin** and **Member**.
* **Admins** have full access to the entire workspace — all projects, domains, members, billing, and usage.
* **Members** have scoped access. They can only access specific projects that an Admin has granted them, and can add or remove other members within those projects.
## Roles & Permissions
| Capability | Admin | Member |
| --------------------------------------- | ----- | ---------------------- |
| Access all projects | Yes | Assigned projects only |
| Manage projects | Yes | No |
| Manage billing & usage | Yes | No |
| Invite & remove workspace members | Yes | No |
| Add/remove members in assigned projects | Yes | Yes |
## How to Invite Someone (as an Admin)
Admins can invite new people from either [**Account Settings > Team** ](https://autosend.com/account/team)or from within a specific [**Project Settings > Team**](https://autosend.com/settings/team).
Go to **Settings > Team** from the sidebar
Enter the following information about the team member you want to invite:
**Required Fields:**
* **First Name** - The team member's first name
* **Last Name** - The team member's last name
* **Email** - A valid email address where the invitation will be sent
The email address should not be associated with any other account or workspace on AutoSend. Disposable email addresses may be blocked.
Select **Admin** for full workspace access, or **Member** for project-scoped access.
If you selected **Member**, select which projects this person should have access to.
After successfully sending the invitation:
* The invitee will receive an email with an invitation link.
* The invitation will appear in your team list with a **"Pending"** status.
* You can resend or cancel the invitation.
**Admins** can update a member's project access at any time from **Account Settings > Team**.
## How to Invite Someone (as a Member)
Members can add other people directly to their project from [**Project Settings > Team**](https://autosend.com/settings/team).
Select the appropriate project from the dropdown
Enter the following information about the team member you want to invite:
After successfully sending the invitation:
* The invitee will receive an email with an invitation link.
* The invitation will appear in your team list with a **"Pending"** status.
* You can resend or cancel the invitation.
**Required Fields:**
* **First Name** - The team member's first name
* **Last Name** - The team member's last name
* **Email** - A valid email address where the invitation will be sent
When a Member sends an invite, the new person is automatically added as a Member scoped to that project. There is no option to choose a role or assign additional projects. That can only be done by an Admin.
## FAQs
Yes, Admins can update a team member's role from [**Account Settings > Team**](https://autosend.com/account/team) at any time.
Yes, Admins can edit a Member's project access from [**Account Settings > Team**](https://autosend.com/account/team).
No. When a Member invites someone, they are automatically added as a Member scoped to that
project. Only Admins can assign the Admin role.
There is no limit on the number of team members you can invite.
Yes, invitations expire after 7 days. If an invitation expires, simply send a new one to the same
email address.
Admins can remove any team member from [**Account Settings > Team**](https://autosend.com/account/team). Members can remove other members from [**Project Settings > Team**](https://autosend.com/settings/team) for their assigned projects.
No, a user can accept invitation only from single workspace. They have to use a different email if
they want to join another workspace.
The invitation link becomes invalid immediately, and the invitee will not be able to join your
workspace using that link. You can send a new invitation if needed.
No, you cannot edit an invitation after it's sent. If you made a mistake, cancel the invitation and send a new one with the correct information.
# Unsubscribe Groups
Source: https://docs.autosend.com/others/unsubscribe-groups
Unsubscribe Groups help you categorize your emails based on their purpose or audience.
Unsubscribe Groups in **AutoSend** allow you to organize your email communications so recipients can choose which type of emails they want to stop receiving, without unsubscribing from everything.
Unsubscribe Groups are essential for respecting user preferences and maintaining healthy deliverability.
***
## How do they work?
Unsubscribe Groups help you categorize your emails based on their purpose or audience.
For example, you might create groups like:
* **Product Updates** – for new feature announcements
* **Newsletters** – for regular content updates
* **Promotions** – for sales or special offers
* **Event Invites** – for webinar or meetup invitations
When a recipient clicks “Unsubscribe” from an email linked to a specific group, they’ll only be removed from that group’s emails and not from all your communications.
This ensures a better user experience and lets you continue sending other relevant messages to your audience.
***
## How to Create an Unsubscribe Group
and click on the "**+ Create**" button to create a new Unsubscribe Group"
**Example:**\
Name: Product Announcements\
Description: Get updates about new features and product improvements
Toggle to hide / show this unsubscribe group on your email preferences page.
It's now available to associate with your marketing or transactional emails.
***
## Adding Unsubscribe Links in Email Templates
To stay compliant and let recipients opt out, every marketing email should include an unsubscribe link. AutoSend provides two merge tags you can use in any template:
* `{{unsubscribe}}` inserts a one-click unsubscribe link that removes the recipient from the **specific unsubscribe group** assigned to that email.
* `{{unsubscribe_preference}}` inserts a link to the AutoSend hosted **preference page**, where the recipient can manage all their unsubscribe groups in one place.
Both tags resolve automatically based on the unsubscribe group selected when sending the email, so you don't need to generate or manage these URLs yourself.
These tags output a URL only. Always wrap them in an `` tag (or use the email builder's link input) so recipients have clickable text to interact with.
### Using the Email Builder
You have two ways to add an unsubscribe link in the AutoSend email builder:
1. **Slash command** — Type `/` in the editor and select **Unsubscribe link** or **Unsubscribe preference** from the menu. AutoSend inserts the link with default text you can edit.
2. **Link input** — Select any existing text, open the link input in the right panel, and paste `{{unsubscribe}}` or `{{unsubscribe_preference}}` as the URL.
### Using Custom HTML Templates
If you're writing your own HTML email templates or sending via the API, add the merge tag inside the `href` attribute of an anchor tag:
```html theme={null}
Don't want to receive these emails?
Unsubscribe
or
manage your preferences.
```
AutoSend replaces the merge tag with the correct unsubscribe URL at send time, scoped to the unsubscribe group attached to that email.
***
## Benefits of Unsubscribe Groups
1. **Improved User Control**
Let recipients choose what kind of emails they want to stop receiving instead of opting out of all emails.
2. **Reduced Global Unsubscribes**
Prevent users from unsubscribing from every type of email just because one category isn’t relevant.
3. **Higher Deliverability and Engagement**
By allowing users to manage preferences, you keep your lists cleaner and your engagement rates higher.
4. **Compliance and Transparency**
Supports compliance with email regulations like CAN-SPAM, GDPR, and CASL by providing clear unsubscribe options.
5. **Better Insights**
Track unsubscribes per group to understand which types of content your audience values most.
***
## Best Practices for Unsubscribe Groups
1. **Create Clear and Logical Groups**
Keep your groups simple and easy to understand. Too many groups can confuse users.
2. **Use Descriptive Names**
“Weekly Newsletter” or “Product Tips” works better than vague labels like “List 1.”
3. **Always Assign an Unsubscribe Group**
Every marketing or promotional email must belong to at least one unsubscribe group to comply with regulations.
4. **Provide Context in Descriptions**
Explain what kind of emails the group includes and how often they’re sent.
5. **Monitor and Adjust Regularly**
Review unsubscribe rates to see which types of content may need improvement.
6. **Avoid Overlapping Groups**
Make sure each group serves a distinct purpose to prevent confusion and duplicate unsubscribes.
# Event Types
Source: https://docs.autosend.com/others/webhooks/event-type
Complete list of supported webhook event types and their payloads.
## Overview
AutoSend webhooks support five categories of events:
1. **Email Lifecycle Events** - Track email sending and delivery
2. **Email Engagement Events** - Track how recipients interact with your emails
3. **Email Subscription Events** - Track unsubscribe and resubscribe actions
4. **Contact Events** - Track changes to your contact database
5. **Inbound Email Events** - Notify your application when emails arrive at your receiving domains
All webhook payloads follow this structure:
```json theme={null}
{
"type": "event.type",
"createdAt": "2025-11-12T10:30:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Email Subject",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
// Additional event-specific fields
}
}
```
**Common Fields (for most email events):**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object containing email and name
* `subject` - Email subject line
* `test` - Boolean indicating if this is a test email (true) or production (false)
* `campaignId` - Campaign identifier (optional)
* `templateId` - Template identifier (optional)
* `workflowAutomationId` - Workflow automation identifier (optional)
* `batchId` - Batch identifier (optional)
**Note:** Subscription events (`email.unsubscribed`, `email.group_unsubscribed`, `email.group_resubscribed`) and contact events have different structures for privacy reasons. See individual event details below.
## Supported Event Types
**Email Lifecycle:**
* `email.sent` - Email successfully sent to the recipient's mail server
* `email.delivered` - Email successfully delivered
* `email.deferred` - Email delivery temporarily delayed
* `email.bounced` - Email failed to deliver
**Email Engagement:**
* `email.opened` - Recipient opened the email
* `email.clicked` - Recipient clicked a link in the email
* `email.spam_reported` - Recipient marked email as spam
**Email Subscription:**
* `email.unsubscribed` - Recipient unsubscribed globally from all emails
* `email.group_unsubscribed` - Recipient unsubscribed from a specific email group
* `email.group_resubscribed` - Recipient resubscribed to a specific email group
**Contact Management:**
* `contact.created` - New contact created
* `contact.updated` - Contact information updated
* `contact.deleted` - Contact deleted
**Inbound Email:**
* `email.received` - An inbound email was received on one of your receiving domains
***
## Email Lifecycle Events
### `email.sent`
Triggered when an email is successfully sent to the recipient's mail server.
**Sample Payload:**
```json theme={null}
{
"type": "email.sent",
"createdAt": "2025-11-12T10:00:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if sent as part of a campaign)
* `templateId` - Template ID used for the email (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
**Use Cases:**
* Track when emails are successfully accepted by recipient server
* Monitor email sending activity
* Trigger follow-up workflows after email dispatch
***
### `email.delivered`
Triggered when AutoSend successfully delivers an email to the recipient's mail server.
**Sample Payload:**
```json theme={null}
{
"type": "email.delivered",
"createdAt": "2025-11-12T10:15:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
**Use Cases:**
* Confirm successful email delivery
* Update delivery status in your database
* Track delivery times and patterns
***
### `email.deferred`
Triggered when email delivery is temporarily delayed by the recipient's mail server.
**Sample Payload:**
```json theme={null}
{
"type": "email.deferred",
"createdAt": "2025-11-12T10:10:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6",
"delayType": "InternalFailure",
"delayReason": "Temporary connection failure",
"expirationTime": "2025-11-12T14:10:00.000Z"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
* `delayType` - Type of delay, e.g., "InternalFailure", "MailboxFull" (optional)
* `delayReason` - Human-readable explanation of the delay (optional)
* `expirationTime` - ISO 8601 timestamp when delivery attempts will stop (optional)
**Use Cases:**
* Monitor temporary delivery issues
* Track mail server responsiveness
* Alert on persistent deferral patterns
***
### `email.bounced`
Triggered when an email bounces (fails to deliver).
**Sample Payload:**
```json theme={null}
{
"type": "email.bounced",
"createdAt": "2025-11-12T10:20:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "invalid@example.com",
"name": "Invalid User"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6",
"bounceType": "Permanent",
"bounceSubType": "General",
"reason": "550 5.1.1 The email account that you tried to reach does not exist"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
* `bounceType` - Bounce classification: "Permanent" or "Transient" (optional)
* `bounceSubType` - Detailed bounce category, e.g., "General", "NoEmail", "Suppressed" (optional)
* `reason` - Human-readable bounce reason from the mail server (optional)
**Bounce Types:**
* `Permanent`: Hard bounce - invalid email address, domain doesn't exist
* `Transient`: Soft bounce - mailbox full, server temporarily unavailable
**Use Cases:**
* Remove hard bounced emails from your list
* Retry soft bounces later
* Monitor bounce rates for sender reputation
***
## Email Engagement Events
### `email.opened`
Triggered when a recipient opens an email.
**Sample Payload:**
```json theme={null}
{
"type": "email.opened",
"createdAt": "2025-11-12T10:30:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
**Privacy Note:** This event does NOT include `ipAddress`, `userAgent`, `device`, or `location` information for privacy protection.
**Use Cases:**
* Track email engagement rates
* Identify best send times
* Segment engaged vs unengaged contacts
***
### `email.clicked`
Triggered when a recipient clicks a link in an email.
**Sample Payload:**
```json theme={null}
{
"type": "email.clicked",
"createdAt": "2025-11-12T10:35:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"clickedUrl": "https://example.com/product",
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `clickedUrl` - The URL that was clicked (optional)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
**Privacy Note:** This event does NOT include `ipAddress`, `userAgent`, or `device` information for privacy protection.
**Use Cases:**
* Track which links are most popular
* Identify highly engaged contacts
* Trigger follow-up campaigns based on clicked links
***
### `email.spam_reported`
Triggered when a recipient marks an email as spam or files a complaint.
**Sample Payload:**
```json theme={null}
{
"type": "email.spam_reported",
"createdAt": "2025-11-12T10:45:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": "sender@yourdomain.com",
"to": {
"email": "user@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6",
"complaintType": "abuse"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity
* `from` - Sender email address
* `to` - Recipient object with email and name
* `subject` - Email subject line
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional, if applicable)
* `templateId` - Template ID used (optional)
* `workflowAutomationId` - Workflow automation ID (optional, if sent from automation)
* `batchId` - Batch ID (optional, if sent as part of a batch)
* `complaintType` - Type of complaint: "abuse", "fraud", "virus", "other" (optional)
**Use Cases:**
* Automatically suppress emails that file complaints
* Monitor spam complaint rates
* Improve email content and targeting to reduce complaints
* Protect sender reputation
***
## Email Subscription Events
### `email.unsubscribed`
Triggered when a recipient globally unsubscribes from **all** emails.
**Sample Payload:**
```json theme={null}
{
"type": "email.unsubscribed",
"createdAt": "2025-11-12T10:40:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"test": false,
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity (optional - only present if triggered from an email)
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `campaignId` - Campaign ID (optional - only present if triggered from a campaign email)
* `templateId` - Template ID (optional - only present if triggered from an email)
* `workflowAutomationId` - Workflow automation ID (optional - only present if triggered from automation email)
* `batchId` - Batch ID (optional - only present if triggered from batch email)
**Important Notes:**
* This event does **NOT** include `from`, `to`, or `subject` fields for privacy protection
* Fields like `emailId`, `campaignId`, `templateId`, `workflowAutomationId`, and `batchId` are **optional** and only present when the unsubscribe is triggered from an email context
**Use Cases:**
* Automatically remove contacts from all mailing lists
* Update contact preferences in your CRM
* Track global unsubscribe reasons for analytics
* Ensure compliance with email regulations
***
### `email.group_unsubscribed`
Triggered when a recipient unsubscribes from a specific email group or suppression group (but not all emails).
**Sample Payload:**
```json theme={null}
{
"type": "email.group_unsubscribed",
"createdAt": "2025-11-12T10:42:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"test": false,
"unsubscribeGroupId": "newsletter-marketing",
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2",
"templateId": "64f1a2b3c4d5e6f7g8h9i0j4",
"workflowAutomationId": "64f1a2b3c4d5e6f7g8h9i0j5",
"batchId": "64f1a2b3c4d5e6f7g8h9i0j6"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity (optional - only present if triggered from an email)
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `unsubscribeGroupId` - The specific group ID they unsubscribed from (optional)
* `campaignId` - Campaign ID (optional - only present if triggered from a campaign email)
* `templateId` - Template ID (optional - only present if triggered from an email)
* `workflowAutomationId` - Workflow automation ID (optional - only present if triggered from automation email)
* `batchId` - Batch ID (optional - only present if triggered from batch email)
**Important Notes:**
* This event does **NOT** include `from`, `to`, or `subject` fields for privacy protection
* Fields like `emailId`, `campaignId`, `templateId`, `workflowAutomationId`, and `batchId` are **optional** and only present when the unsubscribe is triggered from an email context
**Use Cases:**
* Remove contacts from specific mailing lists or segments
* Allow granular subscription management
* Track which email categories users opt out of
* Maintain subscriber preferences across different email types
***
### `email.group_resubscribed`
Triggered when a recipient resubscribes to a specific email group they previously unsubscribed from.
**Sample Payload:**
```json theme={null}
{
"type": "email.group_resubscribed",
"createdAt": "2025-11-12T11:00:00.000Z",
"data": {
"emailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"test": false,
"unsubscribeGroupId": "newsletter-marketing",
"campaignId": "64f1a2b3c4d5e6f7g8h9i0j2"
}
}
```
**Data Fields:**
* `emailId` - Unique identifier for the email activity (optional)
* `test` - Boolean indicating whether this is a test email (true) or production (false)
* `unsubscribeGroupId` - The specific group ID they resubscribed to (optional)
* `campaignId` - Campaign ID (optional)
**Important Notes:**
* This event does **NOT** include `from`, `to`, or `subject` fields for privacy protection
**Use Cases:**
* Re-add contacts to specific mailing lists
* Track re-engagement patterns
* Update subscription preferences in your CRM
* Resume sending specific types of emails to the contact
***
## Contact Events
### `contact.created`
Triggered when a new contact is created in your AutoSend project.
**Sample Payload:**
```json theme={null}
{
"type": "contact.created",
"createdAt": "2025-11-12T09:00:00.000Z",
"data": {
"contactId": "64f1a2b3c4d5e6f7g8h9i0j3",
"email": "newuser@example.com",
"name": "John Doe"
}
}
```
**Data Fields:**
* `contactId` - Unique identifier for the contact (optional)
* `email` - Contact's email address (optional)
* `name` - Contact's name (optional)
**Note:** Additional contact properties may be included based on your contact schema.
**Use Cases:**
* Sync new contacts to your CRM
* Trigger welcome email sequences
* Update analytics dashboards
* Initialize contact tracking in external systems
***
### `contact.updated`
Triggered when an existing contact's information is updated.
**Sample Payload:**
```json theme={null}
{
"type": "contact.updated",
"createdAt": "2025-11-12T09:30:00.000Z",
"data": {
"contactId": "64f1a2b3c4d5e6f7g8h9i0j3",
"email": "user@example.com",
"name": "Jonathan Doe",
"changedFields": ["name"]
}
}
```
**Data Fields:**
* `contactId` - Unique identifier for the contact (optional)
* `email` - Contact's email address (optional)
* `name` - Contact's name (optional)
* `changedFields` - List of field names that were changed (optional)
**Note:** Additional contact properties may be included based on your contact schema.
**Use Cases:**
* Keep contact data synchronized across systems
* Track contact lifecycle changes
* Trigger workflows based on specific field changes
* Update contact profiles in external databases
***
### `contact.deleted`
Triggered when a contact is deleted from your AutoSend project.
**Sample Payload:**
```json theme={null}
{
"type": "contact.deleted",
"createdAt": "2025-11-12T09:45:00.000Z",
"data": {
"contactId": "64f1a2b3c4d5e6f7g8h9i0j3",
"email": "deleteduser@example.com"
}
}
```
**Data Fields:**
* `contactId` - Unique identifier for the deleted contact (optional)
* `email` - Contact's email address for reference (optional)
**Use Cases:**
* Remove contacts from external systems
* Update contact counts and analytics
* Maintain data consistency across platforms
* Ensure GDPR/privacy compliance in external systems
***
## Inbound Email Events
For a conceptual overview of receiving emails on AutoSend, see the [Inbound Email API](/inbound/introduction).
### `email.received`
Triggered when an inbound email is received on one of your project's receiving domains. Use `inboundEmailId` from the payload to fetch the full structured message via the [Get Message](/api-reference/inbound-emails/get-message) endpoint.
**Sample Payload:**
```json theme={null}
{
"type": "email.received",
"createdAt": "2026-06-24T10:30:00.000Z",
"data": {
"inboundEmailId": "64f1a2b3c4d5e6f7g8h9i0j1",
"from": {
"email": "customer@example.com",
"name": "Jane Customer"
},
"to": [
{
"email": "support@inbox.autosend.email",
"name": null
}
],
"cc": [],
"subject": "Question about my order",
"attachments": [
{
"attachmentId": "64f1a2b3c4d5e6f7g8h9i0k1",
"attachmentIndex": 0,
"filename": "receipt.pdf",
"contentType": "application/pdf",
"size": 20480
}
],
"spamVerdict": "PASS",
"virusVerdict": "PASS",
"receivedAt": "2026-06-24T10:30:00.000Z",
"threadId": "64f1a2b3c4d5e6f7g8h9i0j2",
"inReplyToEmailActivityId": "64f1a2b3c4d5e6f7g8h9i0j3",
"inReplyToInboundEmailId": null
}
}
```
**Data Fields:**
* `inboundEmailId` - The database ID of the inbound message (this is the `id` field in the Get Message API response — use it as the `{id}` path parameter)
* `from` - Sender object with email and name
* `to` - Array of recipient objects with email and name
* `cc` - Array of CC recipient objects with email and name (empty array if none)
* `subject` - Email subject line
* `attachments` - Array of attachment metadata objects (empty array if none). Each entry includes `attachmentId`, `attachmentIndex`, `filename`, `contentType`, and `size`
* `spamVerdict` - SES spam check verdict (e.g. `PASS`, `FAIL`)
* `virusVerdict` - SES virus check verdict (e.g. `PASS`, `FAIL`)
* `receivedAt` - ISO 8601 timestamp when the message was received
* `threadId` - Thread identifier for grouping related messages (optional - only present when a thread was matched)
* `inReplyToEmailActivityId` - ID of the outbound email this message is a reply to, if matched (optional)
* `inReplyToInboundEmailId` - ID of a previously received inbound message this is a reply to, if matched (optional)
**Note:** This payload contains metadata only. To access the full message body, headers, attachments, and verdicts, call the [Get Message](/api-reference/inbound-emails/get-message) endpoint with the `messageId`.
**Use Cases:**
* Route incoming support emails to a ticketing system
* Auto-reply to customer questions
* Parse and store inbound replies alongside outbound conversations
* Trigger workflows when a specific address receives mail
***
## Event Handling Examples
```javascript Handle Multiple Events expandable theme={null}
app.post("/webhooks/autosend", async (req, res) => {
const { type, data } = req.body;
switch (type) {
case "email.sent":
await handleEmailSent(data);
break;
case "email.delivered":
await handleEmailDelivered(data);
break;
case "email.deferred":
await handleEmailDeferred(data);
break;
case "email.opened":
await handleEmailOpened(data);
break;
case "email.clicked":
await handleEmailClicked(data);
break;
case "email.bounced":
await handleEmailBounced(data);
break;
case "email.spam_reported":
await handleSpamReport(data);
break;
case "email.unsubscribed":
await handleGlobalUnsubscribe(data);
break;
case "email.group_unsubscribed":
await handleGroupUnsubscribe(data);
break;
case "email.group_resubscribed":
await handleGroupResubscribe(data);
break;
case "contact.created":
await handleContactCreated(data);
break;
case "contact.updated":
await handleContactUpdated(data);
break;
case "contact.deleted":
await handleContactDeleted(data);
break;
default:
console.log(`Unhandled event: ${type}`);
}
res.status(200).json({ received: true });
});
```
```javascript Track Email Engagement expandable theme={null}
async function handleEmailOpened(data) {
const { emailId, to } = data;
await db.emails.update({
where: { id: emailId },
data: {
opened: true,
openedAt: new Date(),
opens: { increment: 1 },
},
});
await db.contacts.update({
where: { email: to.email },
data: {
lastEngagement: new Date(),
engagementScore: { increment: 1 },
},
});
}
async function handleEmailClicked(data) {
const { emailId, to, clickedUrl } = data;
await db.clicks.create({
data: {
emailId,
recipientEmail: to.email,
url: clickedUrl,
clickedAt: new Date(),
},
});
await db.contacts.update({
where: { email: to.email },
data: {
lastEngagement: new Date(),
engagementScore: { increment: 5 },
},
});
}
```
```javascript Handle Bounces and Complaints expandable theme={null}
async function handleEmailBounced(data) {
const { to, bounceType, reason } = data;
if (bounceType === 'Permanent') {
// Remove from active mailing lists
await db.contacts.update({
where: { email: to.email },
data: {
status: 'bounced',
bouncedAt: new Date(),
bounceReason: reason,
},
});
// Suppress future emails
await db.suppressions.create({
data: {
email: to.email,
type: 'bounce',
reason: reason,
},
});
// Alert team
await sendAlert(`Hard bounce for ${to.email}: ${reason}`);
}
}
async function handleSpamReport(data) {
const { to } = data;
const email = to.email;
// Immediately suppress the email
await db.suppressions.create({
data: {
email,
type: 'complaint',
reportedAt: new Date(),
},
});
// Update contact status
await db.contacts.update({
where: { email },
data: {
status: 'complained',
complainedAt: new Date(),
},
});
// Alert team for reputation monitoring
await sendAlert(`Spam complaint from ${email}`);
}
```
```javascript Handle Subscription Events expandable theme={null}
async function handleGlobalUnsubscribe(data) {
const { emailId, campaignId } = data;
// Update email activity record
await db.emails.update({
where: { id: emailId },
data: {
unsubscribed: true,
unsubscribedAt: new Date(),
},
});
console.log(
`Global unsubscribe from email ${emailId}, campaign ${campaignId}`
);
}
async function handleGroupUnsubscribe(data) {
const { emailId, unsubscribeGroupId, campaignId } = data;
// Update email activity record
await db.emails.update({
where: { id: emailId },
data: {
groupUnsubscribed: true,
unsubscribeGroupId,
unsubscribedAt: new Date(),
},
});
console.log(
`Group unsubscribe from email ${emailId}, group ${unsubscribeGroupId}`
);
}
async function handleGroupResubscribe(data) {
const { emailId, unsubscribeGroupId } = data;
// Update subscription record
await db.emails.update({
where: { id: emailId },
data: {
resubscribed: true,
resubscribeGroupId: unsubscribeGroupId,
resubscribedAt: new Date(),
},
});
console.log(
`Group resubscribe for email ${emailId}, group ${unsubscribeGroupId}`
);
}
```
```javascript Sync Contacts expandable theme={null}
async function handleContactCreated(data) {
const { contactId, email, name } = data;
// Sync to CRM
await crm.contacts.create({
externalId: contactId,
email,
name,
createdAt: new Date(),
});
}
async function handleContactUpdated(data) {
const { contactId, email, name, changedFields } = data;
// Sync to CRM
await crm.contacts.update({
externalId: contactId,
email,
name,
changedFields,
updatedAt: new Date(),
});
}
async function handleContactDeleted(data) {
const { contactId, email } = data;
// Remove from CRM
await crm.contacts.delete({
externalId: contactId,
});
console.log(`Contact ${email} removed from CRM`);
}
```
***
## Get Available Events via API
You can programmatically fetch the list of available events:
```bash cURL theme={null}
curl -X GET https://api.autosend.com/v1/webhooks/events/available \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "x-project-id: YOUR_PROJECT_ID"
```
```javascript Node.js expandable theme={null}
const fetch = require('node-fetch');
const API_KEY = process.env.AUTOSEND_API_KEY;
const PROJECT_ID = process.env.AUTOSEND_PROJECT_ID;
async function getAvailableEvents() {
const response = await fetch(
'https://api.autosend.com/v1/webhooks/events/available',
{
method: 'GET',
headers: {
Authorization: `Bearer ${API_KEY}`,
'x-project-id': PROJECT_ID,
},
}
);
const data = await response.json();
console.log(data);
return data;
}
getAvailableEvents();
```
```python Python expandable theme={null}
import requests
import os
API_KEY = os.environ.get('AUTOSEND_API_KEY')
PROJECT_ID = os.environ.get('AUTOSEND_PROJECT_ID')
def get_available_events():
url = 'https://api.autosend.com/v1/webhooks/events/available'
headers = {
'Authorization': f'Bearer {API_KEY}',
'x-project-id': PROJECT_ID
}
response = requests.get(url, headers=headers)
data = response.json()
print(data)
return data
get_available_events()
```
```php PHP expandable theme={null}
```
```go Go expandable theme={null}
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("AUTOSEND_API_KEY")
projectId := os.Getenv("AUTOSEND_PROJECT_ID")
req, _ := http.NewRequest("GET", "https://api.autosend.com/v1/webhooks/events/available", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("x-project-id", projectId)
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```ruby Ruby expandable theme={null}
require 'net/http'
require 'json'
require 'uri'
api_key = ENV['AUTOSEND_API_KEY']
project_id = ENV['AUTOSEND_PROJECT_ID']
uri = URI('https://api.autosend.com/v1/webhooks/events/available')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{api_key}"
request['x-project-id'] = project_id
response = http.request(request)
data = JSON.parse(response.body)
puts data
```
**Response:**
```json expandable theme={null}
{
"success": true,
"data": {
"events": [
"email.sent",
"email.delivered",
"email.deferred",
"email.opened",
"email.clicked",
"email.bounced",
"email.spam_reported",
"email.unsubscribed",
"email.group_unsubscribed",
"email.group_resubscribed",
"contact.created",
"contact.updated",
"contact.deleted",
"email.received"
]
}
}
```
***
## Related Resources
Getting started with webhooks
Automatic retry logic and best practices
Security and signature verification
Manage your webhooks from the AutoSend sidebar
# Webhooks
Source: https://docs.autosend.com/others/webhooks/introduction
Use webhooks to notify your application about email and contact events in real-time.
## What is a webhook?
Webhooks are HTTP callbacks that send real-time notifications to your application when specific events occur. Instead of continuously polling the AutoSend API to check for updates, webhooks push data to your application the moment events happen.
All AutoSend webhooks use HTTPS and deliver a JSON payload that your application can process immediately.
### Why use webhooks? Common use cases:
* **Automatically remove bounced email addresses** from your mailing lists
* **Track email engagement** in real-time (opens, clicks, unsubscribes)
* **Sync contact changes** across multiple systems
* **Receive incoming emails in your app** with the `email.received` event and the Inbound Email API
* **Create alerts** in your messaging or incident tools based on event types
* **Store all events** in your own database for custom reporting and retention
* **Trigger workflows** when specific events occur (e.g., send a Slack notification when an email bounces)
* **Maintain compliance logs** for audit purposes
***
## How to set up webhooks
Create a new route in your application that accepts POST requests.
```javascript NodeJs expandable theme={null}
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhooks/autosend', (req, res) => {
// Verify signature (see Verify Webhook Requests section)
const signature = req.headers['x-webhook-signature'];
const isValid = verifySignature(
req.body,
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the webhook
const { event, data } = req.body;
console.log(`Received ${event} event:`, data);
// Process based on event type
switch (event) {
case 'email.opened':
// Handle email opened
break;
case 'email.clicked':
// Handle email clicked
break;
case 'contact.created':
// Handle contact created
break;
// ... handle other events
}
// Always respond quickly with 2xx status
res.status(200).json({ received: true });
});
function verifySignature(body, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return signature === expectedSignature;
}
app.listen(3000, () => {
console.log('Webhook endpoint listening on port 3000');
});
```
```javascript NextJs expandable theme={null}
// pages/api/webhooks.js
import crypto from 'crypto';
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// Verify signature
const signature = req.headers['x-webhook-signature'];
const webhookSecret = process.env.WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
const { event, data } = req.body;
console.log(`Received ${event} event:`, data);
// Respond immediately
res.status(200).json({ received: true });
}
```
```python Python expandable theme={null}
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')
def verify_signature(payload, signature):
expected = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/autosend', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not signature or not verify_signature(payload, signature):
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
event = data.get('event')
print(f'Received {event} event')
return jsonify({'received': True}), 200
```
When you receive an event, respond with an `HTTP 200 OK` status within 10
seconds to confirm successful delivery.
1. Go to Webhooks in the AutoSend sidebar
2. Click **"New Webhook"**
3. Fill in the webhook details:
* **Name**: A friendly name for identification (e.g., "Production Email Tracker")
* **Endpoint URL**: Your publicly accessible HTTPS URL. For local development, use a tunneling service like ngrok or localtunnel.
* **Events**: Select the events you want to receive notifications for
4. Click **"Create Webhook"**
5. Copy the secret token and store it securely. You'll need it to verify webhook requests.
Store your webhook secret as an environment variable
(`WEBHOOK_SECRET=your_secret_here`). Never commit it to version control. In
production, use a secret management service like AWS Secrets Manager.
Add logging to your endpoint to verify incoming requests:
```javascript theme={null}
app.post('/webhooks/autosend', (req, res) => {
console.log('Webhook received!');
console.log('Event:', req.body.event);
console.log('Data:', JSON.stringify(req.body.data, null, 2));
res.status(200).json({ received: true });
});
```
Trigger test events by performing actions in AutoSend:
* **For email events**: Send a test campaign or transactional email
* **For contact events**: Create, update, or delete a test contact
Watch your logs to confirm your endpoint receives the webhook requests.
After testing locally, deploy your webhook endpoint to your production environment (Vercel, Netlify, AWS Lambda, Railway, etc.).
**Your production endpoint must:**
* Use HTTPS (required)
* Respond within 10 seconds
* Implement proper error handling
* Verify webhook signatures
Once deployed, create a new webhook in AutoSend with your production URL. If you used a tunneling URL for development, you'll need to register a separate webhook for production.
***
## Managing webhooks
From the Webhooks page, you can:
* **View all webhooks** for your project, displayed as cards with webhook details
* **Edit webhooks**: Click the menu (⋮) on a webhook card and select "Edit"
* **Delete webhooks**: Click the menu (⋮) and select "Delete"
* **Check webhook status**: Each card shows whether the webhook is "Enabled" or "Disabled"
* **Copy webhook details**: The webhook ID and URL are copyable from each card
If you lose your webhook secret, you can reveal it by editing the webhook and
clicking "Reveal Secret".
***
## Best practices
Process webhooks asynchronously so you can respond within 10 seconds:
```javascript theme={null}
// ❌ Bad: Synchronous processing
app.post("/webhooks/autosend", async (req, res) => {
await updateDatabase(data);
await sendToAnalytics(data);
res.status(200).json({ received: true });
});
// ✅ Good: Asynchronous processing
app.post("/webhooks/autosend", async (req, res) => {
// Queue for background processing
await queue.add("process-webhook", { event, data });
// Respond immediately
res.status(200).json({ received: true });
});
```
Never trust incoming webhook requests without verification. See the Verify Webhook Requests guide for implementation details.
Maintain detailed logs for debugging:
```javascript theme={null}
app.post("/webhooks/autosend", (req, res) => {
logger.info(
{
deliveryId: req.headers["x-webhook-delivery-id"],
event: req.body.event,
timestamp: new Date().toISOString(),
},
"Webhook received"
);
// Process webhook
});
```
Track webhook delivery success and failure rates. AutoSend automatically tracks `failureCount`, `lastSuccessAt`, and `lastFailedAt` for each webhook. See Retries and Replays for monitoring examples.
***
## FAQ
AutoSend supports email engagement events (opened, clicked, bounced, delivered, etc.) and contact events (created, updated, deleted). See the Event Types guide for the complete list.
If AutoSend doesn't receive a 200 response from your webhook endpoint, it automatically retries delivery up to 3 times with exponential backoff (1 min, 5 min, 15 min). See Retries and Replays for details.
Yes, you can create up to 100 webhooks per project. Each webhook can subscribe to different events and point to different endpoints.
No, webhook URLs cannot be changed after creation for security reasons. To use a different URL, delete the existing webhook and create a new one.
AutoSend retries failed deliveries up to 3 times with exponential backoff. After all retries are exhausted, the delivery is marked as failed. See the Retries and Replays guide for details.
Yes, webhook requests have a 10-second timeout. Make sure your endpoint responds within this window.
Use tools like ngrok or localtunnel to expose your local server to the internet. Create a webhook in AutoSend with your tunnel URL, then trigger events by sending emails or managing contacts.
Yes, all webhooks are sent over HTTPS and include an HMAC-SHA256 signature for verification. See Verify Webhook Requests for implementation details.
***
## Related resources
Complete list of webhook events and payloads
Automatic retry logic and best practices
Security and signature verification
Manage your webhooks from the AutoSend sidebar
Receive incoming emails via the `email.received` event and the Inbound Email API
# Retries and Replays
Source: https://docs.autosend.com/others/webhooks/retries
Learn how AutoSend automatically handles failed webhook deliveries.
## Automatic Retries
AutoSend automatically retries webhook deliveries that fail due to network errors, timeouts, or non-2xx status codes from your endpoint.
### Retry Schedule
If AutoSend does not receive a 2xx (200-299) response from your webhook endpoint, we will retry the webhook delivery using an **exponential backoff strategy**.
| Attempt | Approximate Delay After Previous Failure |
| --------- | ---------------------------------------- |
| 1st Retry | \~5 seconds |
| 2nd Retry | \~10 seconds |
| 3rd Retry | \~20 seconds |
**Total Retry Attempts**: 3 retries (4 total delivery attempts including the initial attempt)
**Backoff Strategy**: Exponential backoff starting with a 5-second delay, doubling with each retry
**Request Timeout**: Each delivery attempt will timeout after **10 seconds** if no response is received
### Example Timeline
```
Initial Attempt: 10:00:00 - Failed (500 Internal Server Error)
1st Retry: 10:00:05 - Failed (Timeout after 10s)
2nd Retry: 10:00:15 - Failed (503 Service Unavailable)
3rd Retry: 10:00:35 - Success (200 OK)
```
***
## When Retries Occur
### Retry Triggers
AutoSend will retry webhook deliveries when:
* **Non-2xx status codes** are returned (400, 401, 403, 404, 500, 502, 503, 504, etc.)
* **Network errors** occur (connection refused, DNS resolution failure, etc.)
* **Timeouts** happen (no response within 10 seconds)
* **SSL/TLS errors** are encountered
* **Request aborted** due to timeout
### No Retry for Success
If your endpoint returns any 2xx status code (200-299), the delivery is marked as successful and no retries will occur.
```javascript theme={null} theme={null}
// ✅ These responses mark delivery as successful (no retry)
res.status(200).json({ received: true });
res.status(201).json({ queued: true });
res.status(202).json({ accepted: true });
// ❌ These responses trigger retries
res.status(400).json({ error: 'Bad request' });
res.status(401).json({ error: 'Unauthorized' });
res.status(500).json({ error: 'Internal error' });
res.status(503).json({ error: 'Service unavailable' });
```
***
## Webhook Headers
Every webhook delivery includes these headers:
| Header | Description | Example |
| ----------------------- | ------------------------------------------------- | ------------------------ |
| `Content-Type` | Always `application/json` | `application/json` |
| `X-Webhook-Signature` | HMAC-SHA256 signature for verification | `a1b2c3d4e5f6...` |
| `X-Webhook-Event` | The event type being delivered | `email.sent` |
| `X-Webhook-Delivery-Id` | Unique ID for this delivery (same across retries) | `deliver-webhook-123456` |
| `X-Webhook-Timestamp` | Unix timestamp (milliseconds) when sent | `1699876543210` |
**Important**: The `X-Webhook-Delivery-Id` remains the same across all retry attempts for the same webhook event, making it perfect for implementing idempotency.
***
## After All Retries Fail
After the conclusion of all retry attempts (initial + 3 retries = 4 total attempts), if the webhook still hasn't been delivered successfully, the delivery will be marked as **failed** in the system.
### What Happens Next
* The delivery log will show the final failure status
* The webhook's `failureCount` is incremented
* The webhook's `lastFailedAt` timestamp is updated
* Failed delivery records are kept for **48 hours** for debugging
* AutoSend will **not** automatically retry it again
### Webhook Auto-Disable (Optional)
By default, webhooks are NOT automatically disabled after consecutive failures. However, the system tracks:
* `failureCount`: Number of consecutive delivery failures
* `MAX_FAILURES` constant: Set to 5 (currently not enforced but available for future use)
You can monitor these metrics to manually disable problematic webhooks.
***
## Delivery Logs
### Log Retention
AutoSend keeps delivery attempt logs for debugging:
* **Successful deliveries**: Retained for **24 hours**
* **Failed deliveries**: Retained for **48 hours**
* **Maximum completed jobs kept**: 1,000 most recent
### Log Information
Each delivery log includes:
* Webhook ID and organization/project IDs
* Event type
* Full payload sent
* Destination URL
* HTTP status code
* Response body and headers
* Success/failure status
* Error message (if failed)
* Number of attempts made
* Duration of the request (in milliseconds)
* Timestamp of the delivery attempt
***
## Monitoring Your Webhooks
### Check Webhook Status
Monitor your webhooks from the AutoSend dashboard:
* **Active** (green) - Webhook is enabled and functional
* **Inactive** (gray) - Webhook is disabled
* **Disabled** (red) - Webhook has been disabled due to issues
### Webhook Health Metrics
Each webhook tracks important metrics:
| Metric | Description |
| ----------------- | ----------------------------------------- |
| `failureCount` | Number of consecutive delivery failures |
| `lastSuccessAt` | Timestamp of last successful delivery |
| `lastFailedAt` | Timestamp of last failed delivery |
| `lastDeliveredAt` | Timestamp of last delivery attempt (any) |
| `isActive` | Whether the webhook is enabled |
| `status` | Current status (active/inactive/disabled) |
These metrics are updated automatically:
* **On success**: `failureCount` resets to 0, `lastSuccessAt` and `lastDeliveredAt` are updated
* **On failure**: `failureCount` increments by 1, `lastFailedAt` is updated
### Implement Your Own Monitoring
Set up your own monitoring for webhook health:
```javascript Nodejs expandable theme={null} theme={null}
// Example: Check webhook health periodically
async function monitorWebhookHealth() {
// Fetch webhook delivery logs from your database
const recentDeliveries = await db.webhookDeliveryLogs.find({
createdAt: { $gte: new Date(Date.now() - 3600000) }, // Last hour
});
const failureCount = recentDeliveries.filter((d) => !d.success).length;
const successCount = recentDeliveries.filter((d) => d.success).length;
const totalDeliveries = recentDeliveries.length;
const successRate = totalDeliveries > 0 ? (successCount / totalDeliveries) * 100 : 100;
// Calculate average response time
const avgDuration = recentDeliveries.reduce((sum, d) => sum + d.duration, 0) / totalDeliveries;
console.log({
totalDeliveries,
successCount,
failureCount,
successRate: `${successRate.toFixed(2)}%`,
avgResponseTime: `${avgDuration.toFixed(0)}ms`,
});
// Alert if success rate drops
if (successRate < 90) {
await sendAlert({
title: 'Webhook Health Alert',
message: `Webhook success rate dropped to ${successRate.toFixed(2)}%`,
details: {
successCount,
failureCount,
totalDeliveries,
},
severity: 'warning',
});
}
// Alert if response time is slow
if (avgDuration > 5000) {
await sendAlert({
title: 'Webhook Performance Alert',
message: `Average webhook response time is ${avgDuration.toFixed(0)}ms`,
severity: 'warning',
});
}
}
// Run every 5 minutes
setInterval(monitorWebhookHealth, 5 * 60 * 1000);
```
***
## Best Practices
Always return a 2xx status code when your endpoint successfully receives and processes a webhook:
```javascript Nodejs expandable theme={null} theme={null}
app.post("/webhooks/autosend", async (req, res) => {
try {
// Verify signature first
const signature = req.headers["x-webhook-signature"];
const body = JSON.stringify(req.body);
if (!verifySignature(signature, body, WEBHOOK_SECRET)) {
// Return 401 for invalid signature (will retry)
// Or return 200 to prevent retries for invalid signatures
return res.status(401).json({ error: "Invalid signature" });
}
// Queue for background processing
await queue.add("process-webhook", {
deliveryId: req.headers["x-webhook-delivery-id"],
event: req.body.type,
data: req.body.data,
});
// Return 200 immediately - don't wait for processing
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook processing error:", error);
// Return 500 for temporary errors (will retry)
res.status(500).json({ error: "Internal error" });
}
});
```
Use the `X-Webhook-Delivery-Id` header to prevent duplicate processing during retries:
```javascript Nodejs expandable theme={null} theme={null}
// Using Redis for idempotency tracking
const redis = require("redis");
const client = redis.createClient();
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
const ttl = 86400; // 24 hours
// Check if already processed
const exists = await client.exists(`webhook:${deliveryId}`);
if (exists) {
console.log(`Duplicate delivery ${deliveryId}, skipping`);
return res.status(200).json({ received: true, duplicate: true });
}
try {
// Process the webhook
await processWebhook(req.body);
// Mark as processed (with TTL to auto-cleanup)
await client.setex(`webhook:${deliveryId}`, ttl, "processed");
res.status(200).json({ received: true });
} catch (error) {
// Don't mark as processed on error so it can be retried
console.error("Processing failed:", error);
res.status(500).json({ error: "Processing failed" });
}
});
```
**Alternative: Database-based idempotency**
```javascript Nodejs expandable theme={null} theme={null}
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
try {
// Try to insert the delivery ID (unique constraint)
await db.webhookDeliveries.insertOne({
deliveryId,
receivedAt: new Date(),
processed: false,
});
} catch (error) {
if (error.code === 11000) {
// Duplicate key - already processed
console.log(`Duplicate delivery ${deliveryId}`);
return res.status(200).json({ received: true, duplicate: true });
}
throw error;
}
try {
await processWebhook(req.body);
// Mark as processed
await db.webhookDeliveries.updateOne(
{ deliveryId },
{ $set: { processed: true, processedAt: new Date() } }
);
res.status(200).json({ received: true });
} catch (error) {
console.error("Processing failed:", error);
res.status(500).json({ error: "Processing failed" });
}
});
```
Return appropriate status codes based on the type of error:
```javascript Nodejs expandable theme={null} theme={null}
app.post("/webhooks/autosend", async (req, res) => {
try {
// Verify signature
const signature = req.headers["x-webhook-signature"];
const isValid = verifySignature(signature, req.body, WEBHOOK_SECRET);
if (!isValid) {
// Invalid signature is a permanent error
// Return 200 to prevent unnecessary retries
return res.status(200).json({
error: "Invalid signature",
retryable: false
});
}
// Process webhook
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
// Determine if error is temporary or permanent
if (error.code === "ECONNREFUSED" || error.code === "ETIMEDOUT") {
// Temporary database/service error - retry
return res.status(500).json({
error: "Service temporarily unavailable",
retryable: true
});
} else if (error.name === "ValidationError") {
// Permanent error - bad data, don't retry
return res.status(200).json({
error: "Invalid data format",
retryable: false
});
} else {
// Unknown error - retry to be safe
return res.status(500).json({
error: "Internal error",
retryable: true
});
}
}
});
```
Your endpoint MUST respond within 10 seconds or the request will timeout. Process webhooks asynchronously:
```javascript Nodejs expandable theme={null} theme={null}
const Queue = require("bull");
const webhookQueue = new Queue("webhooks", {
redis: { host: "localhost", port: 6379 },
});
// ❌ Bad - Synchronous processing (may timeout)
app.post("/webhooks/autosend", async (req, res) => {
await updateDatabase(req.body); // 2 seconds
await sendToAnalytics(req.body); // 3 seconds
await notifySlack(req.body); // 2 seconds
await triggerWorkflow(req.body); // 4 seconds
// Total: 11 seconds - WILL TIMEOUT!
res.status(200).json({ received: true });
});
// ✅ Good - Asynchronous processing
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
// Queue for background processing (fast!)
await webhookQueue.add("process", {
deliveryId,
event: req.body.type,
data: req.body.data,
});
// Respond immediately (< 100ms)
res.status(200).json({ received: true });
});
// Process in background worker
webhookQueue.process("process", async (job) => {
const { deliveryId, event, data } = job.data;
console.log(`Processing webhook ${deliveryId} for event ${event}`);
await updateDatabase(data);
await sendToAnalytics(data);
await notifySlack(data);
await triggerWorkflow(data);
console.log(`Completed processing webhook ${deliveryId}`);
});
```
Keep detailed logs of all webhook delivery attempts for debugging:
```javascript Nodejs expandable theme={null} theme={null}
const winston = require("winston");
const logger = winston.createLogger({
level: "info",
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: "webhooks.log" }),
new winston.transports.Console(),
],
});
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
const event = req.body.type;
const timestamp = req.headers["x-webhook-timestamp"];
logger.info("Webhook received", {
deliveryId,
event,
timestamp,
receivedAt: new Date().toISOString(),
});
try {
await processWebhook(req.body);
logger.info("Webhook processed successfully", {
deliveryId,
event,
processingTime: Date.now() - parseInt(timestamp),
});
res.status(200).json({ received: true });
} catch (error) {
logger.error("Webhook processing failed", {
deliveryId,
event,
error: error.message,
stack: error.stack,
});
res.status(500).json({ error: "Processing failed" });
}
});
```
Test how your endpoint handles retries in development:
```javascript Nodejs expandable theme={null} theme={null}
// Simulate intermittent failures for testing
const deliveryAttempts = new Map();
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
// Track attempts for this delivery
const attempts = (deliveryAttempts.get(deliveryId) || 0) + 1;
deliveryAttempts.set(deliveryId, attempts);
console.log(`Delivery ${deliveryId} - Attempt ${attempts}`);
// Simulate: Fail first 2 attempts, succeed on 3rd
if (attempts <= 2) {
console.log(`Simulating failure on attempt ${attempts}`);
return res.status(500).json({ error: "Simulated error" });
}
console.log(`Success on attempt ${attempts}`);
// Process webhook
await processWebhook(req.body);
// Clean up tracking
deliveryAttempts.delete(deliveryId);
res.status(200).json({ received: true, attempts });
});
```
AutoSend processes up to 5 webhooks concurrently. Ensure your endpoint can handle concurrent requests:
```javascript Nodejs expandable theme={null} theme={null}
const express = require("express");
const cluster = require("cluster");
const os = require("os");
if (cluster.isMaster) {
// Fork workers (one per CPU core)
const numCPUs = os.cpus().length;
console.log(`Master process starting ${numCPUs} workers`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died, starting new worker`);
cluster.fork();
});
} else {
// Worker process
const app = express();
app.use(express.json());
app.post("/webhooks/autosend", async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
res.status(500).json({ error: "Processing failed" });
}
});
app.listen(3000, () => {
console.log(`Worker ${process.pid} listening on port 3000`);
});
}
```
***
## Troubleshooting
**Symptoms**: Webhooks failing frequently
**Possible Causes**:
* Endpoint is down or unreachable
* Endpoint is timing out (>10 seconds)
* Endpoint is returning non-2xx status codes
* SSL/TLS certificate issues
* Rate limiting on your server
**Solutions**:
1. **Test your endpoint manually**:
```bash theme={null} theme={null}
curl -X POST https://your-endpoint.com/webhooks/autosend \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: test_signature" \
-H "X-Webhook-Event: email.sent" \
-H "X-Webhook-Delivery-Id: test-123" \
-H "X-Webhook-Timestamp: $(date +%s)000" \
-d '{
"type": "email.sent",
"createdAt": "2025-01-05T10:00:00.000Z",
"data": {
"emailId": "test123",
"from": "test@example.com",
"to": {"email": "user@example.com", "name": "Test User"},
"subject": "Test Email"
}
}'
```
2. **Check your server logs** for error messages
3. **Verify SSL certificate is valid**:
```bash theme={null} theme={null}
openssl s_client -connect your-endpoint.com:443 -servername your-endpoint.com
```
4. **Check response time**:
```bash theme={null} theme={null}
time curl -X POST https://your-endpoint.com/webhooks/autosend \
-H "Content-Type: application/json" \
-d '{"type":"test","data":{}}'
```
5. **Monitor your server resources** (CPU, memory, disk) to ensure it's not overloaded
6. **Check for rate limiting** on your server or firewall
**Symptoms**: All webhooks returning 401 or failing signature verification
**Possible Causes**:
* Using wrong webhook secret
* Incorrect signature verification logic
* Body parsing issues (modified body)
* Character encoding issues
**Solutions**:
See Verify Webhook Requests for detailed troubleshooting.
**Quick verification test**:
```javascript theme={null} theme={null}
const crypto = require("crypto");
function verifyWebhookSignature(signature, body, secret) {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
console.log("Received signature:", signature);
console.log("Expected signature:", expectedSignature);
console.log("Match:", signature === expectedSignature);
return signature === expectedSignature;
}
app.post("/webhooks/autosend", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-webhook-signature"];
const body = req.body.toString(); // Raw body as string
if (!verifyWebhookSignature(signature, body, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
// Parse body after verification
const data = JSON.parse(body);
res.status(200).json({ received: true });
});
```
**Symptoms**: Webhooks timing out (10 second timeout)
**Possible Causes**:
* Synchronous processing taking too long
* Database queries are slow
* External API calls are slow
* No connection pooling
* Inefficient code
**Solutions**:
1. **Use background job queues**:
```javascript theme={null} theme={null}
const Queue = require("bull");
const webhookQueue = new Queue("webhooks");
app.post("/webhooks/autosend", async (req, res) => {
// Queue immediately (fast)
await webhookQueue.add(req.body, {
attempts: 3,
backoff: { type: "exponential", delay: 2000 },
});
// Respond fast
res.status(200).json({ received: true });
});
// Process in background
webhookQueue.process(async (job) => {
const { type, data } = job.data;
// Time-consuming operations here
await updateDatabase(data);
await callExternalAPI(data);
await generateReport(data);
});
```
2. **Optimize database queries**:
```javascript theme={null} theme={null}
// ❌ Slow - Sequential queries
const user = await db.users.findOne({ email: data.to.email });
const campaign = await db.campaigns.findOne({ _id: data.campaignId });
const template = await db.templates.findOne({ _id: data.templateId });
// ✅ Fast - Parallel queries
const [user, campaign, template] = await Promise.all([
db.users.findOne({ email: data.to.email }),
db.campaigns.findOne({ _id: data.campaignId }),
db.templates.findOne({ _id: data.templateId }),
]);
```
3. **Add database indexes**:
```javascript theme={null} theme={null}
// Create indexes on frequently queried fields
db.webhookDeliveries.createIndex({ deliveryId: 1 }, { unique: true });
db.webhookDeliveries.createIndex({ createdAt: -1 });
db.webhookLogs.createIndex({ webhookId: 1, createdAt: -1 });
```
4. **Use connection pooling**:
```javascript theme={null} theme={null}
const mongoose = require("mongoose");
// Configure connection pool
mongoose.connect(MONGODB_URI, {
poolSize: 10,
socketTimeoutMS: 45000,
family: 4,
});
```
5. **Cache frequently accessed data**:
```javascript theme={null} theme={null}
const NodeCache = require("node-cache");
const cache = new NodeCache({ stdTTL: 600 }); // 10 minute TTL
async function getWebhookConfig(webhookId) {
// Check cache first
let config = cache.get(`webhook:${webhookId}`);
if (!config) {
// Fetch from database
config = await db.webhooks.findOne({ _id: webhookId });
cache.set(`webhook:${webhookId}`, config);
}
return config;
}
```
**Symptoms**: Same webhook processed multiple times
**Possible Causes**:
* Not implementing idempotency
* Retry logic processing same delivery multiple times
* Race conditions in distributed systems
**Solutions**:
Implement idempotency using `X-Webhook-Delivery-Id` (see Best Practices section above).
**Symptoms**: Not receiving expected webhooks
**Possible Causes**:
* Webhook not configured for the event type
* Webhook is inactive or disabled
* Firewall blocking incoming requests
* Incorrect URL configured
**Solutions**:
1. **Check webhook configuration** in AutoSend dashboard
2. **Verify event types** are selected for the webhook
3. **Check webhook is active** (not disabled)
4. **Test webhook URL** is accessible from external networks
5. **Check firewall rules** allow incoming traffic on your endpoint port
6. **Review AutoSend delivery logs** for delivery attempts
***
## Related Resources
Getting started with webhooks
Complete list of webhook events
Security and signature verification
Manage your webhooks from the AutoSend sidebar
# Verify Webhook Requests
Source: https://docs.autosend.com/others/webhooks/verify-requests
Learn how to verify that webhook requests are genuinely from AutoSend using HMAC-SHA256 signature verification.
## Why Verify Webhooks?
**Security is critical.** Anyone can send a POST request to your webhook endpoint. Without verification, malicious actors could:
* Send fake events to corrupt your data
* Trigger unwanted actions in your application
* Cause your system to process fraudulent information
* Launch denial-of-service attacks
**Always verify webhook signatures** to ensure requests are genuinely from AutoSend.
***
## How AutoSend Signs Webhooks
Every webhook request from AutoSend includes an `X-Webhook-Signature` header containing an HMAC-SHA256 signature.
### Signature Generation
AutoSend generates the signature using this process:
1. **Format the webhook payload** with the event type, timestamp, and event data
2. **Convert the payload to JSON string** (the raw request body)
3. **Compute HMAC-SHA256** using your webhook secret as the key
4. **Convert to hexadecimal** format
5. **Add as header**: `X-Webhook-Signature: `
```javascript theme={null}
// How AutoSend generates signatures
const payload = {
type: event,
createdAt: new Date().toISOString(),
data: eventData,
};
const payloadString = JSON.stringify(payload);
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(payloadString)
.digest('hex');
```
***
## Webhook Request Headers
Every webhook request includes these headers:
HMAC-SHA256 signature of the request body in hexadecimal format
Example: `"a1b2c3d4e5f6..."`
The event type
Example: `"email.opened"`
Unique delivery identifier (job ID from the queue system)
Example: `"delivery-123..."`
Unix timestamp in milliseconds when the webhook was sent
Example: `"1699790400000"`
Always `application/json`
Example: `"application/json"`
AutoSend user agent (if set)
Example: `"AutoSend-Webhooks/1.0"`
***
## Steps to Verify Signatures
```javascript Node.js expandable theme={null}
const express = require("express");
const crypto = require("crypto");
const app = express();
// Important: Store raw body for signature verification
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString("utf8");
},
})
);
function verifyWebhookSignature(req, webhookSecret) {
const receivedSignature = req.headers["x-webhook-signature"];
if (!receivedSignature) {
return false;
}
// Compute expected signature using raw body
const expectedSignature = crypto
.createHmac("sha256", webhookSecret)
.update(req.rawBody)
.digest("hex");
// Use constant-time comparison to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
} catch (error) {
// Buffer lengths don't match
return false;
}
}
app.post("/webhooks/autosend", (req, res) => {
const webhookSecret = process.env.WEBHOOK_SECRET;
// Verify signature
if (!verifyWebhookSignature(req, webhookSecret)) {
console.error("Invalid webhook signature");
return res.status(401).json({ error: "Invalid signature" });
}
// Process webhook
const { type, data } = req.body;
console.log(`Verified webhook: ${type}`);
res.status(200).json({ received: true });
});
```
```python Python expandable theme={null}
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')
def verify_webhook_signature(payload, signature, secret):
"""Verify webhook signature using HMAC-SHA256"""
if not signature:
return False
# Compute expected signature
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/autosend', methods=['POST'])
def webhook():
# Get signature from header
signature = request.headers.get('X-Webhook-Signature')
# Get raw payload
payload = request.get_data(as_text=True)
# Verify signature
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# Parse and process webhook
data = request.json
event_type = data.get('type')
print(f'Verified webhook: {event_type}')
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
```
```php PHP expandable theme={null}
'Invalid signature']);
exit;
}
// Parse and process webhook
$data = json_decode($payload, true);
$eventType = $data['type'];
error_log("Verified webhook: $eventType");
http_response_code(200);
echo json_encode(['received' => true]);
?>
```
```ruby Ruby expandable theme={null}
require 'sinatra'
require 'json'
require 'openssl'
WEBHOOK_SECRET = ENV['WEBHOOK_SECRET']
def verify_webhook_signature(payload, signature, secret)
return false if signature.nil? || signature.empty?
# Compute expected signature
expected = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
# Constant-time comparison
Rack::Utils.secure_compare(expected, signature)
end
post '/webhooks/autosend' do
# Get raw body and signature
payload = request.body.read
signature = request.env['HTTP_X_WEBHOOK_SIGNATURE']
# Verify signature
unless verify_webhook_signature(payload, signature, WEBHOOK_SECRET)
status 401
return { error: 'Invalid signature' }.to_json
end
# Parse and process webhook
data = JSON.parse(payload)
event_type = data['type']
puts "Verified webhook: #{event_type}"
status 200
{ received: true }.to_json
end
```
```go Go expandable theme={null}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io/ioutil"
"net/http"
"os"
)
func verifyWebhookSignature(payload []byte, signature string, secret string) bool {
if signature == "" {
return false
}
// Compute expected signature
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := hex.EncodeToString(mac.Sum(nil))
// Constant-time comparison
return hmac.Equal([]byte(expected), []byte(signature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read raw body
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
// Get signature from header
signature := r.Header.Get("X-Webhook-Signature")
// Get secret from environment
webhookSecret := os.Getenv("WEBHOOK_SECRET")
// Verify signature
if !verifyWebhookSignature(payload, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Parse webhook
var data map[string]interface{}
json.Unmarshal(payload, &data)
// Process webhook
eventType := data["type"].(string)
println("Verified webhook:", eventType)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/autosend", webhookHandler)
http.ListenAndServe(":3000", nil)
}
```
***
## Retrieving Your Webhook Secret
Your webhook secret is shown only once when you create the webhook. If you've lost it, you can retrieve it:
Store your webhook secret securely. Never commit it to version control or
expose it in client-side code.
***
## Complete Production Example
Here's a complete, production-ready webhook endpoint with signature verification, timestamp validation, and error handling:
```javascript Node.js expandable theme={null}
const express = require("express");
const crypto = require("crypto");
const app = express();
// Store raw body for signature verification
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString("utf8");
},
})
);
// Webhook secret from environment
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) {
throw new Error("WEBHOOK_SECRET environment variable is required");
}
// Verify webhook signature
function verifyWebhookSignature(req) {
const receivedSignature = req.headers["x-webhook-signature"];
if (!receivedSignature) {
return false;
}
const expectedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.rawBody)
.digest("hex");
try {
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
} catch (error) {
// Buffer lengths don't match
return false;
}
}
// Validate timestamp (optional but recommended)
function isTimestampValid(timestamp, maxAgeSeconds = 300) {
if (!timestamp) {
return false;
}
const now = Date.now();
const age = now - parseInt(timestamp);
// Reject if older than 5 minutes or more than 1 minute in the future
return age < maxAgeSeconds \* 1000 && age > -60000;
}
// Webhook endpoint
app.post("/webhooks/autosend", async (req, res) => {
const deliveryId = req.headers["x-webhook-delivery-id"];
const timestamp = req.headers["x-webhook-timestamp"];
const event = req.headers["x-webhook-event"];
// Validate timestamp
if (!isTimestampValid(timestamp)) {
console.error("Invalid or expired timestamp", { deliveryId, timestamp });
return res.status(401).json({ error: "Invalid timestamp" });
}
// Verify signature
if (!verifyWebhookSignature(req)) {
console.error("Invalid signature", { deliveryId, event });
return res.status(401).json({ error: "Invalid signature" });
}
// Process webhook
const { type, data, createdAt } = req.body;
console.log("Webhook received and verified", {
deliveryId,
type,
event,
createdAt,
});
try {
// Queue for background processing to respond quickly
await processWebhookAsync(type, data);
// Respond with 200 to acknowledge receipt
res.status(200).json({ received: true });
} catch (error) {
console.error("Error processing webhook", { deliveryId, error });
// Still return 200 to avoid retries for processing errors
// Log the error for investigation
res.status(200).json({ received: true, warning: "Processing queued" });
}
});
async function processWebhookAsync(type, data) {
// Implement your webhook processing logic here
// This should be non-blocking and ideally queued
console.log(`Processing ${type} event:`, data);
}
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
```
```python Python expandable theme={null}
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
import time
import json
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')
if not WEBHOOK_SECRET:
raise ValueError("WEBHOOK_SECRET environment variable is required")
def verify_webhook_signature(payload, signature, secret):
"""Verify webhook signature using HMAC-SHA256"""
if not signature:
return False
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
def is_timestamp_valid(timestamp, max_age_seconds=300):
"""Validate webhook timestamp"""
if not timestamp:
return False
try:
now = int(time.time() * 1000)
ts = int(timestamp)
age = now - ts
# Reject if older than 5 minutes or more than 1 minute in the future
return age < max_age_seconds * 1000 and age > -60000
except (ValueError, TypeError):
return False
@app.route('/webhooks/autosend', methods=['POST'])
def webhook():
# Get headers
delivery_id = request.headers.get('X-Webhook-Delivery-Id')
timestamp = request.headers.get('X-Webhook-Timestamp')
event = request.headers.get('X-Webhook-Event')
signature = request.headers.get('X-Webhook-Signature')
# Get raw payload
payload = request.get_data(as_text=True)
# Validate timestamp
if not is_timestamp_valid(timestamp):
logger.error(f"Invalid timestamp: {delivery_id}, {timestamp}")
return jsonify({'error': 'Invalid timestamp'}), 401
# Verify signature
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
logger.error(f"Invalid signature: {delivery_id}, {event}")
return jsonify({'error': 'Invalid signature'}), 401
# Parse and process webhook
data = request.json
event_type = data.get('type')
event_data = data.get('data')
created_at = data.get('createdAt')
logger.info(f"Webhook received and verified: {delivery_id}, {event_type}")
try:
# Process webhook asynchronously
process_webhook_async(event_type, event_data)
return jsonify({'received': True}), 200
except Exception as error:
logger.error(f"Error processing webhook: {delivery_id}, {error}")
# Still return 200 to avoid retries
return jsonify({'received': True, 'warning': 'Processing queued'}), 200
def process_webhook_async(event_type, data):
"""Process webhook in background"""
logger.info(f"Processing {event_type} event: {data}")
# Implement your webhook processing logic here
if __name__ == '__main__':
app.run(port=3000)
```
```php PHP expandable theme={null}
-60000;
}
// Get raw POST body
$payload = file_get_contents('php://input');
// Get headers
$deliveryId = $_SERVER['HTTP_X_WEBHOOK_DELIVERY_ID'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$event = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
// Validate timestamp
if (!isTimestampValid($timestamp)) {
error_log("Invalid timestamp: {$deliveryId}, {$timestamp}");
http_response_code(401);
echo json_encode(['error' => 'Invalid timestamp']);
exit;
}
// Verify signature
if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
error_log("Invalid signature: {$deliveryId}, {$event}");
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
// Parse and process webhook
$data = json_decode($payload, true);
$eventType = $data['type'] ?? '';
$eventData = $data['data'] ?? [];
$createdAt = $data['createdAt'] ?? '';
error_log("Webhook received and verified: {$deliveryId}, {$eventType}");
try {
// Process webhook
processWebhookAsync($eventType, $eventData);
http_response_code(200);
echo json_encode(['received' => true]);
} catch (Exception $error) {
error_log("Error processing webhook: {$deliveryId}, {$error->getMessage()}");
// Still return 200 to avoid retries
http_response_code(200);
echo json_encode(['received' => true, 'warning' => 'Processing queued']);
}
function processWebhookAsync($eventType, $data) {
error_log("Processing {$eventType} event");
// Implement your webhook processing logic here
}
?>
```
```ruby Ruby expandable theme={null}
require 'sinatra'
require 'json'
require 'openssl'
require 'time'
require 'logger'
WEBHOOK_SECRET = ENV['WEBHOOK_SECRET']
if WEBHOOK_SECRET.nil? || WEBHOOK_SECRET.empty?
raise "WEBHOOK_SECRET environment variable is required"
end
logger = Logger.new(STDOUT)
def verify_webhook_signature(payload, signature, secret)
return false if signature.nil? || signature.empty?
expected = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
Rack::Utils.secure_compare(expected, signature)
end
def is_timestamp_valid(timestamp, max_age_seconds = 300)
return false if timestamp.nil? || timestamp.empty?
now = (Time.now.to_f * 1000).to_i
ts = timestamp.to_i
age = now - ts
# Reject if older than 5 minutes or more than 1 minute in the future
age < (max_age_seconds * 1000) && age > -60000
end
post '/webhooks/autosend' do
# Get headers
delivery_id = request.env['HTTP_X_WEBHOOK_DELIVERY_ID']
timestamp = request.env['HTTP_X_WEBHOOK_TIMESTAMP']
event = request.env['HTTP_X_WEBHOOK_EVENT']
signature = request.env['HTTP_X_WEBHOOK_SIGNATURE']
# Get raw body
payload = request.body.read
# Validate timestamp
unless is_timestamp_valid(timestamp)
logger.error("Invalid timestamp: #{delivery_id}, #{timestamp}")
status 401
return { error: 'Invalid timestamp' }.to_json
end
# Verify signature
unless verify_webhook_signature(payload, signature, WEBHOOK_SECRET)
logger.error("Invalid signature: #{delivery_id}, #{event}")
status 401
return { error: 'Invalid signature' }.to_json
end
# Parse and process webhook
data = JSON.parse(payload)
event_type = data['type']
event_data = data['data']
created_at = data['createdAt']
logger.info("Webhook received and verified: #{delivery_id}, #{event_type}")
begin
# Process webhook asynchronously
process_webhook_async(event_type, event_data)
status 200
{ received: true }.to_json
rescue => error
logger.error("Error processing webhook: #{delivery_id}, #{error.message}")
# Still return 200 to avoid retries
status 200
{ received: true, warning: 'Processing queued' }.to_json
end
end
def process_webhook_async(event_type, data)
# Implement your webhook processing logic here
puts "Processing #{event_type} event: #{data}"
end
```
```go Go expandable theme={null}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"time"
)
var webhookSecret string
func init() {
webhookSecret = os.Getenv("WEBHOOK_SECRET")
if webhookSecret == "" {
log.Fatal("WEBHOOK_SECRET environment variable is required")
}
}
func verifyWebhookSignature(payload []byte, signature string, secret string) bool {
if signature == "" {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
func isTimestampValid(timestamp string, maxAgeSeconds int) bool {
if timestamp == "" {
return false
}
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return false
}
now := time.Now().UnixMilli()
age := now - ts
// Reject if older than 5 minutes or more than 1 minute in the future
return age < int64(maxAgeSeconds*1000) && age > -60000
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read raw body
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
// Get headers
deliveryId := r.Header.Get("X-Webhook-Delivery-Id")
timestamp := r.Header.Get("X-Webhook-Timestamp")
event := r.Header.Get("X-Webhook-Event")
signature := r.Header.Get("X-Webhook-Signature")
// Validate timestamp
if !isTimestampValid(timestamp, 300) {
log.Printf("Invalid timestamp: %s, %s", deliveryId, timestamp)
http.Error(w, "Invalid timestamp", http.StatusUnauthorized)
return
}
// Verify signature
if !verifyWebhookSignature(payload, signature, webhookSecret) {
log.Printf("Invalid signature: %s, %s", deliveryId, event)
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Parse webhook
var data map[string]interface{}
if err := json.Unmarshal(payload, &data); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
eventType, _ := data["type"].(string)
eventData, _ := data["data"].(map[string]interface{})
createdAt, _ := data["createdAt"].(string)
log.Printf("Webhook received and verified: %s, %s, %s", deliveryId, eventType, createdAt)
// Process webhook
if err := processWebhookAsync(eventType, eventData); err != nil {
log.Printf("Error processing webhook: %s, %v", deliveryId, err)
// Still return 200 to avoid retries
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func processWebhookAsync(eventType string, data map[string]interface{}) error {
// Implement your webhook processing logic here
log.Printf("Processing %s event: %v", eventType, data)
return nil
}
func main() {
http.HandleFunc("/webhooks/autosend", webhookHandler)
log.Println("Webhook server listening on port 3000")
log.Fatal(http.ListenAndServe(":3000", nil))
}
```
***
## Security Best Practices
**Never use `===` or `==` to compare signatures.** Use constant-time comparison functions to prevent timing attacks:
```javascript theme={null}
// ❌ Bad - Vulnerable to timing attacks
if (receivedSignature === expectedSignature) {
// Process webhook
}
// ✅ Good - Constant-time comparison
try {
if (
crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
)
) {
// Process webhook
}
} catch (error) {
// Buffer lengths don't match - signature is invalid
return false;
}
```
The `crypto.timingSafeEqual()` function throws an error if the buffer lengths don't match. Always wrap it in a try-catch block.
Never hardcode webhook secrets in your code:
```javascript theme={null}
// ❌ Bad - Hardcoded secret
const WEBHOOK_SECRET = 'a1b2c3d4e5f6g7h8...';
// ✅ Good - Environment variable
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
if (!WEBHOOK_SECRET) {
throw new Error('WEBHOOK_SECRET environment variable is required');
}
```
**Store secrets in:**
* Environment variables (`.env` files for local development)
* Secure secret management services (AWS Secrets Manager, HashiCorp Vault, etc.)
* Encrypted configuration files
**Never:**
* Commit secrets to version control
* Include secrets in client-side code
* Share secrets in logs or error messages
* Use the same secret across multiple environments
Compute signatures using the **raw, unparsed request body**. Do not re-stringify the parsed JSON:
```javascript theme={null}
// ✅ Good - Use raw body
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
},
})
);
const signature = crypto
.createHmac('sha256', secret)
.update(req.rawBody) // Use raw body
.digest('hex');
// ❌ Bad - Don't re-stringify parsed body
const signature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body)) // May not match original
.digest('hex');
```
JSON stringification is not deterministic. The order of object keys may differ, causing signature verification to fail.
Validate the `X-Webhook-Timestamp` header to reject old or replayed requests:
```javascript theme={null}
function isTimestampValid(timestamp, maxAgeSeconds = 300) {
if (!timestamp) {
return false;
}
const now = Date.now();
const age = now - parseInt(timestamp);
// Reject if older than 5 minutes or more than 1 minute in the future
return age < maxAgeSeconds * 1000 && age > -60000;
}
app.post('/webhooks/autosend', (req, res) => {
const timestamp = req.headers['x-webhook-timestamp'];
if (!isTimestampValid(timestamp)) {
return res.status(401).json({ error: 'Invalid or expired timestamp' });
}
if (!verifyWebhookSignature(req, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
});
```
The timestamp is in **milliseconds** (not seconds). AutoSend sends timestamps as `Date.now().toString()`.
**Always use HTTPS** for your webhook endpoints in production:
```javascript theme={null}
// ❌ Bad - HTTP in production
const webhookUrl = 'http://api.example.com/webhooks/autosend';
// ✅ Good - HTTPS
const webhookUrl = 'https://api.example.com/webhooks/autosend';
```
HTTPS ensures:
* Requests are encrypted in transit
* Man-in-the-middle attacks are prevented
* Webhook data remains confidential
* Your webhook secret is protected
AutoSend does not enforce HTTPS for webhook URLs, but it is strongly recommended for production use.
Webhook requests have a **10-second timeout**. Always respond within this time:
```javascript theme={null}
// ✅ Good - Queue processing and respond immediately
app.post('/webhooks/autosend', async (req, res) => {
// Verify signature
if (!verifyWebhookSignature(req, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Queue for background processing
await queue.add('process-webhook', req.body);
// Respond immediately
res.status(200).json({ received: true });
});
// ❌ Bad - Long processing blocks response
app.post('/webhooks/autosend', async (req, res) => {
// This might take too long
await processWebhook(req.body);
await updateDatabase(req.body);
await sendNotification(req.body);
res.status(200).json({ received: true });
});
```
If your endpoint doesn't respond within 10 seconds, AutoSend will consider the delivery failed and retry up to 3 times.
AutoSend retries failed deliveries up to **3 times**. Make your webhook handler idempotent:
```javascript theme={null}
// ✅ Good - Idempotent processing
app.post('/webhooks/autosend', async (req, res) => {
const deliveryId = req.headers['x-webhook-delivery-id'];
// Check if already processed
const exists = await db.webhookLogs.findOne({ deliveryId });
if (exists) {
return res.status(200).json({ received: true, status: 'duplicate' });
}
// Process webhook
await processWebhook(req.body);
// Store delivery ID
await db.webhookLogs.create({ deliveryId, processedAt: new Date() });
res.status(200).json({ received: true });
});
```
Use the `X-Webhook-Delivery-Id` header to track which deliveries you've already processed.
Regularly rotate your webhook secrets for enhanced security:
```javascript theme={null}
// Support multiple secrets during rotation
const WEBHOOK_SECRETS = [
process.env.WEBHOOK_SECRET,
process.env.WEBHOOK_SECRET_OLD, // Remove after rotation complete
].filter(Boolean);
function verifyWithMultipleSecrets(req) {
return WEBHOOK_SECRETS.some((secret) => {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(req.headers['x-webhook-signature']),
Buffer.from(expectedSignature)
);
} catch {
return false;
}
});
}
```
***
## Webhook Payload Structure
AutoSend sends webhook payloads in this format:
```json theme={null}
{
"type": "email.opened",
"createdAt": "2025-01-08T10:30:00.000Z",
"data": {
"emailId": "email_abc123",
"campaignId": "campaign_xyz789",
"templateId": "template_def456",
"from": "sender@example.com",
"to": {
"email": "recipient@example.com",
"name": "John Doe"
},
"subject": "Welcome to AutoSend",
"userAgent": "Mozilla/5.0...",
"ipAddress": "192.168.1.1",
"timestamp": "2025-01-08T10:30:00.000Z"
}
}
```
The event type (e.g., `"email.opened"`, `"contact.created"`)
ISO 8601 timestamp when the event occurred
Event-specific data (varies by event type)
***
## Troubleshooting
**Symptoms**: All webhook requests return 401 Unauthorized
**Common Causes:**
1. **Using the wrong secret**
```javascript theme={null}
// Debug: Check which secret you're using
console.log('Secret starts with:', webhookSecret.substring(0, 10));
console.log('Secret length:', webhookSecret.length); // Should be 64 chars
```
2. **Body parsing issues**
```javascript theme={null}
// Ensure you're using raw body
console.log('Raw body:', req.rawBody);
console.log('Raw body length:', req.rawBody.length);
```
3. **String encoding issues**
```javascript theme={null}
// Ensure consistent UTF-8 encoding
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(req.rawBody, 'utf8') // Explicit encoding
.digest('hex');
```
4. **Comparing wrong values**
```javascript theme={null}
// Debug signature comparison
console.log('Received signature:', receivedSignature);
console.log('Expected signature:', expectedSignature);
console.log(
'Lengths match:',
receivedSignature.length === expectedSignature.length
);
```
5. **Secret contains whitespace**
```javascript theme={null}
// Trim whitespace from secret
const webhookSecret = process.env.WEBHOOK_SECRET.trim();
```
**Symptoms**: Requests fail with "Invalid timestamp" error
**Common Causes:**
1. **Wrong time unit** - Timestamp is in milliseconds, not seconds
```javascript theme={null}
// ❌ Bad - Treating as seconds
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
// ✅ Good - Milliseconds
const age = Date.now() - parseInt(timestamp);
```
2. **Clock skew** - Server time is off
```javascript theme={null}
// Allow 1 minute of clock skew
const age = Date.now() - parseInt(timestamp);
return age < 300000 && age > -60000; // -1 min to +5 min
```
3. **Timezone issues**
```javascript theme={null}
// Timestamps are always UTC
const now = Date.now(); // Always use Date.now(), not local time
```
Test your signature verification without waiting for real webhooks:
```javascript theme={null}
const crypto = require('crypto');
function testSignatureVerification() {
const webhookSecret = 'test-secret-12345';
// Create a test payload (matching AutoSend's format)
const payload = {
type: 'email.opened',
createdAt: new Date().toISOString(),
data: {
emailId: 'test-123',
campaignId: 'campaign-456',
timestamp: new Date().toISOString(),
},
};
const payloadString = JSON.stringify(payload);
// Generate signature
const signature = crypto
.createHmac('sha256', webhookSecret)
.update(payloadString)
.digest('hex');
console.log('Test payload:', payloadString);
console.log('Test signature:', signature);
// Verify it works
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payloadString)
.digest('hex');
console.log('Verification passes:', signature === expectedSignature);
// Test with your actual verification function
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
console.log('timingSafeEqual passes:', isValid);
}
testSignatureVerification();
```
**Symptoms**: No webhook requests arriving at your endpoint
**Troubleshooting Steps:**
1. **Check webhook is active**
* Navigate to Webhooks in AutoSend
* Verify webhook status is "Active"
* Check if failure count is high (auto-disabled after 5 failures)
2. **Verify URL is accessible**
```bash theme={null}
# Test your endpoint is publicly accessible
curl -X POST https://your-domain.com/webhooks/autosend \
-H "Content-Type: application/json" \
-d '{"test": true}'
```
3. **Check delivery logs**
* Click on your webhook in AutoSend
* View the "Delivery Logs" tab
* Look for error messages or status codes
4. **Test with resend**
* Create a test event
* Use the "Resend" feature to manually trigger delivery
* Check your server logs
***
## Next Steps
Learn about all available webhook events and their payloads
Understand how AutoSend handles failed deliveries
Production deployment guidelines and optimization tips
Configure and monitor your webhooks in AutoSend
***
## Related Resources
Getting started with AutoSend webhooks
View webhook delivery history and debug issues
# Projects
Source: https://docs.autosend.com/projects
Organize your work into separate Projects - each with its own API keys, contacts, senders, and settings, all under one account and subscription.
A **Project** is an isolated workspace within your AutoSend account. Each project has its own sending domains, contacts, senders, templates, campaigns, API keys, and settings. All your projects share the same subscription and billing, while usage and limits are pooled across them.
## Why Use Projects?
* **Separate Environments**: Keep Production, Staging, and Development completely isolated. Use separate API keys and senders per environment without risk of cross-contamination.
* **Multiple Products**: Indie hackers and founders building several products can keep each one separate, with different contact lists and senders, all under a single subscription.
* **Multi-Tenant Platforms**: Build CRMs, marketing tools, or Shopify apps where each of your customers sends from their own custom domain. Each customer gets their own isolated project.
* **Agencies with Multiple Clients**: Manage email for multiple clients under one AutoSend account. Contacts, senders, and campaigns stay separate per client with no risk of data mixing.
## Creating a Project
Click the project name in the top-left corner of your AutoSend dashboard to open the project
switcher dropdown, then click **"New Project"**.
Enter a descriptive name for the project - for example, `Production`, `Staging`, or your
client's brand name. Add a sending domain, select Region and click **"Create Project"**.
Your new project is ready. Head to Settings > Domains to get the DNS records and verify a sending domain for this project before you can start sending emails.
## Switching Between Projects
Use the project switcher in the top-left of the dashboard to move between projects at any time. All data shown in the dashboard - contacts, campaigns, analytics, API keys - belongs to the currently selected project.
## What's Isolated Per Project
Each project has its own separate:
* **API Keys**: credentials scoped to that project only
* **Contacts and Lists**: subscriber data does not cross project boundaries
* **Senders and Domains**: verified sending addresses and domains
* **Templates**: transactional and marketing email templates
* **Campaigns**: marketing campaigns and their analytics
* **Automations**: email sequences and workflow configurations
* **Webhooks**: event notification endpoints
* **Suppressions**: unsubscribe and bounce lists
## What's Shared Across Projects
The following are shared at the account level across all projects:
* **Subscription and billing**: one plan covers all your projects
* **Monthly email send quota**: the total sends are pooled across projects
* **Team members**: all team members on your account can access all projects
## Plan Limits
Each plan includes a set number of projects:
| Plan | Projects |
| ------- | -------- |
| Hobby | 1 |
| Starter | Up to 2 |
| Growth | Up to 5 |
| Scale | Up to 20 |
Building a multi-tenant system and need more than 20 projects? Reach out to
and we'll set up a custom limit for your
use case.
***
## FAQs
Not directly - contacts are isolated per project by design. To migrate contacts, export them from the source project as a CSV and import them into the destination project.
Yes. Your plan's monthly email quota is pooled across all projects. For example, on the Starter
plan, the total sends are shared between your 2 projects - there's no per-project quota split.
Yes. Each project has its own verified senders and domains. You can verify `mail.client-a.com` in
one project and `mail.client-b.com` in another, completely independently.
Your existing projects remain active and usable - nothing is deleted. However, you won't be able
to create new projects until the number of projects is within your new plan's limit.
Yes. Team members are added at the account level and have access to all projects under the
account.
AutoSend has two types of API keys:
* **Project API key**: Scoped to a single project. Use this for sending emails, managing contacts, campaigns, and all day-to-day operations within that project. An API key from Project A cannot access Project B.
* **Account API key**: Has extended scope across your entire account. Use this when you need to programmatically create, update, or delete projects. Requests made with an Account API key must include the target `projectId` in the payload to specify which project to operate on.
For most integrations, a Project API key is the right choice. Use an Account API key only when managing projects themselves - for example, when building a multi-tenant platform that provisions new projects automatically.
Yes. You can delete a project from the project settings. Deleting a project permanently removes
all associated data - contacts, templates, campaigns, API keys, and email history. This action
cannot be undone.
The Scale plan supports up to 20 projects. If you're building a product where each of your customers needs their own isolated project, contact - we support custom project limits for multi-tenant use cases.
# Quickstart
Source: https://docs.autosend.com/quickstart
Send your first emails with AutoSend email API in less than 10 minutes.
Before sending emails, you need to verify ownership of your sending domain by adding DNS records. This ensures high deliverability and authenticates your emails.
* Add your domain or subdomain (we recommend using a subdomain like `mail.yourdomain.com`)
* Configure DNS records (DKIM, SPF, DMARC)
* Verify domain ownership
Learn how to add a domain
Create secure API keys to authenticate your requests to the AutoSend API. Each key can be named for easy management (e.g., "Production", "Staging", "Marketing").
* Generate API keys from your dashboard.
* Copy and securely store your secret key.
* Use the key in your API requests.
See authentication guide
With your domain verified and API key ready, you can start sending emails immediately using our REST API.
```bash cURL theme={null}
curl -X POST https://api.autosend.com/v1/mails/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": {
"email": "customer@example.com",
"name": "Jane Doe"
},
"from": {
"email": "hello@mail.yourdomain.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome!
Thanks for signing up.
"
}'
```
```javascript NodeJS theme={null}
fetch("https://api.autosend.com/v1/mails/send", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY", // Replace with your API key
"Content-Type": "application/json",
},
body: JSON.stringify({
from: {
email: "no-reply@example.com",
name: "AutoSend",
},
to: {
email: "user@example.com",
name: "Test User",
},
subject: "Welcome to AutoSend 🎉",
// Option 1: Send using template
templateId: "your_template_id",
// Option 2: Send using raw HTML or text
html: "Hello User
Thanks for joining AutoSend!
",
text: "Hello User, Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
unsubscribeGroupId: "12345",
}),
})
.then((response) => response.json())
.then((data) => {
console.log(" Email sent successfully: ", data);
})
.catch((error) => {
console.error("Error sending email:", error);
});
```
```rust Rust theme={null}
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde_json::json;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box> {
// API endpoint
let url = "https://api.autosend.com/v1/mails/send";
// Build request JSON payload
let payload = json!({
"from": {
"email": "no-reply@example.com",
"name": "AutoSend"
},
"to": {
"email": "user@example.com",
"name": "Test User"
},
"subject": "Welcome to AutoSend 🎉",
// Option 1: template
"templateId": "your_template_id",
// Option 2: raw HTML / text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
// Optional: unsubscribe group
"unsubscribeGroupId": "12345"
});
// Create HTTP client
let client = reqwest::Client::new();
// Send POST request
let res = client
.post(url)
.header(AUTHORIZATION, "Bearer YOUR_API_KEY") // replace with your key
.header(CONTENT_TYPE, "application/json")
.json(&payload)
.send()
.await?;
// Print response
let status = res.status();
let body = res.text().await?;
println!("Status: {}", status);
println!("Body: {}", body);
Ok(())
}
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/mails/send"
// Build request body
payload := map[string]interface{}{
"from": map[string]string{
"email": "no-reply@example.com",
"name": "AutoSend",
},
"to": map[string]string{
"email": "user@example.com",
"name": "Test User",
},
"subject": "Welcome to AutoSend 🎉",
// Option 1: Send using template
"templateId": "your_template_id",
// Option 2: Send using raw HTML or text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
"unsubscribeGroupId": "12345",
}
jsonData, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return
}
// Create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Authorization", "Bearer YOUR_API_KEY") // Replace with real API key
req.Header.Set("Content-Type", "application/json")
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
// Read response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Println("Response status:", resp.Status)
fmt.Println("Response body:", string(body))
}
```
```php PHP theme={null}
[
"email" => "no-reply@example.com",
"name" => "AutoSend"
],
"to" => [
"email" => "user@example.com",
"name" => "Test User"
],
"subject" => "Welcome to AutoSend 🎉",
// Option 1: Send using template
"templateId" => "your_template_id",
// Option 2: Send using raw HTML or text
"html" => "Hello User
Thanks for joining AutoSend!
",
"text" => "Hello User,
Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
"unsubscribeGroupId" => "12345"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_KEY", // replace with your API key
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
if(curl_errno($ch)){
echo "Curl error: " . curl_error($ch);
} else {
echo "Response: " . $response;
}
curl_close($ch);
```
```c# C# theme={null}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; // Install Newtonsoft.Json via NuGet
class Program
{
static async Task Main(string[] args)
{
var url = "https://api.autosend.com/v1/mails/send";
var payload = new
{
from = new { email = "no-reply@example.com", name = "AutoSend" },
to = new { email = "user@example.com", name = "Test User" },
subject = "Welcome to AutoSend 🎉",
// Option 1: Template
templateId = "your_template_id",
// Option 2: Raw HTML / text
html = "Hello User
Thanks for joining AutoSend!
",
text = "Hello User,
Thanks for joining AutoSend!",
// Optional unsubscribe group
unsubscribeGroupId = "12345"
};
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_KEY"); // Replace with your API key
var json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Status: {response.StatusCode}");
Console.WriteLine($"Response: {responseBody}");
}
}
}
```
```java Java theme={null}
import java.io.OutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SendEmail {
public static void main(String[] args) {
try {
String url = "https://api.autosend.com/v1/mails/send";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// Set request method and headers
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer YOUR_API_KEY"); // Replace with your API key
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
// JSON payload
String jsonPayload = """
{
"from": {"email": "no-reply@example.com", "name": "AutoSend"},
"to": {"email": "user@example.com", "name": "Test User"},
"subject": "Welcome to AutoSend 🎉",
"templateId": "your_template_id",
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,\nThanks for joining AutoSend!",
"unsubscribeGroupId": "12345"
}
""";
// Send request
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// Read response
int status = con.getResponseCode();
InputStream responseStream = (status < HttpURLConnection.HTTP_BAD_REQUEST)
? con.getInputStream()
: con.getErrorStream();
String response = new String(responseStream.readAllBytes(), StandardCharsets.UTF_8);
System.out.println("Status: " + status);
System.out.println("Response: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```python Python theme={null}
import requests
url = "https://api.autosend.com/v1/mails/send"
payload = {
"from": {
"email": "no-reply@example.com",
"name": "AutoSend"
},
"to": {
"email": "user@example.com",
"name": "Test User"
},
"subject": "Welcome to AutoSend 🎉",
# Option 1: Send using template
"templateId": "your_template_id",
# Option 2: Send using raw HTML or text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
# Optional: Unsubscribe group for compliance
"unsubscribeGroupId": "12345"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY", # Replace with your API key
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print("Status Code:", response.status_code)
print("Response:", response.text)
```
# How to send email with AutoSend API
Source: https://docs.autosend.com/quickstart/email-using-api
Learn how to send emails using the AutoSend API with code examples in multiple programming languages.
Once you’ve created a transactional email template, you can use it in your application via the AutoSend API.
### API Endpoint
```jsx theme={null}
POST https://api.autosend.com/v1/mails/send
```
### Authentication
Include your API key in the Authorization header:
```jsx theme={null}
Authorization: Bearer YOUR_API_KEY
```
### Single Email with Template
**Request:**
```bash cURL expandable theme={null}
curl -X POST https://api.autosend.com/v1/mails/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": {
"email": "customer@example.com",
"name": "Jane Doe"
},
"from": {
"email": "hello@mail.yourdomain.com",
"name": "Your Company"
},
"subject": "Welcome to Our Platform!",
"html": "Welcome!
Thanks for signing up.
"
}'
```
```javascript NodeJS expandable theme={null}
fetch("https://api.autosend.com/v1/mails/send", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY", // Replace with your API key
"Content-Type": "application/json",
},
body: JSON.stringify({
from: {
email: "no-reply@example.com",
name: "AutoSend",
},
to: {
email: "user@example.com",
name: "Test User",
},
subject: "Welcome to AutoSend 🎉",
// Option 1: Send using template
templateId: "your_template_id",
// Option 2: Send using raw HTML or text
html: "Hello User
Thanks for joining AutoSend!
",
text: "Hello User, Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
unsubscribeGroupId: "12345",
}),
})
.then((response) => response.json())
.then((data) => {
console.log(" Email sent successfully: ", data);
})
.catch((error) => {
console.error("Error sending email:", error);
});
```
```rust Rust expandable theme={null}
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde_json::json;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box> {
// API endpoint
let url = "https://api.autosend.com/v1/mails/send";
// Build request JSON payload
let payload = json!({
"from": {
"email": "no-reply@example.com",
"name": "AutoSend"
},
"to": {
"email": "user@example.com",
"name": "Test User"
},
"subject": "Welcome to AutoSend 🎉",
// Option 1: template
"templateId": "your_template_id",
// Option 2: raw HTML / text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
// Optional: unsubscribe group
"unsubscribeGroupId": "12345"
});
// Create HTTP client
let client = reqwest::Client::new();
// Send POST request
let res = client
.post(url)
.header(AUTHORIZATION, "Bearer YOUR_API_KEY") // replace with your key
.header(CONTENT_TYPE, "application/json")
.json(&payload)
.send()
.await?;
// Print response
let status = res.status();
let body = res.text().await?;
println!("Status: {}", status);
println!("Body: {}", body);
Ok(())
}
```
```go Go expandable theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
url := "https://api.autosend.com/v1/mails/send"
// Build request body
payload := map[string]interface{}{
"from": map[string]string{
"email": "no-reply@example.com",
"name": "AutoSend",
},
"to": map[string]string{
"email": "user@example.com",
"name": "Test User",
},
"subject": "Welcome to AutoSend 🎉",
// Option 1: Send using template
"templateId": "your_template_id",
// Option 2: Send using raw HTML or text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
"unsubscribeGroupId": "12345",
}
jsonData, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return
}
// Create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Authorization", "Bearer YOUR_API_KEY") // Replace with real API key
req.Header.Set("Content-Type", "application/json")
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
// Read response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Println("Response status:", resp.Status)
fmt.Println("Response body:", string(body))
}
```
```php PHP expandable theme={null}
[
"email" => "no-reply@example.com",
"name" => "AutoSend"
],
"to" => [
"email" => "user@example.com",
"name" => "Test User"
],
"subject" => "Welcome to AutoSend 🎉",
// Option 1: Send using template
"templateId" => "your_template_id",
// Option 2: Send using raw HTML or text
"html" => "Hello User
Thanks for joining AutoSend!
",
"text" => "Hello User,
Thanks for joining AutoSend!",
// Optional: Unsubscribe group for compliance
"unsubscribeGroupId" => "12345"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer YOUR_API_KEY", // replace with your API key
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
if(curl_errno($ch)){
echo "Curl error: " . curl_error($ch);
} else {
echo "Response: " . $response;
}
curl_close($ch);
```
```c# C# expandable theme={null}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; // Install Newtonsoft.Json via NuGet
class Program
{
static async Task Main(string[] args)
{
var url = "https://api.autosend.com/v1/mails/send";
var payload = new
{
from = new { email = "no-reply@example.com", name = "AutoSend" },
to = new { email = "user@example.com", name = "Test User" },
subject = "Welcome to AutoSend 🎉",
// Option 1: Template
templateId = "your_template_id",
// Option 2: Raw HTML / text
html = "Hello User
Thanks for joining AutoSend!
",
text = "Hello User,
Thanks for joining AutoSend!",
// Optional unsubscribe group
unsubscribeGroupId = "12345"
};
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_KEY"); // Replace with your API key
var json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Status: {response.StatusCode}");
Console.WriteLine($"Response: {responseBody}");
}
}
}
```
```java Java expandable theme={null}
import java.io.OutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class SendEmail {
public static void main(String[] args) {
try {
String url = "https://api.autosend.com/v1/mails/send";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// Set request method and headers
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer YOUR_API_KEY"); // Replace with your API key
con.setRequestProperty("Content-Type", "application/json");
con.setDoOutput(true);
// JSON payload
String jsonPayload = """
{
"from": {"email": "no-reply@example.com", "name": "AutoSend"},
"to": {"email": "user@example.com", "name": "Test User"},
"subject": "Welcome to AutoSend 🎉",
"templateId": "your_template_id",
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,\nThanks for joining AutoSend!",
"unsubscribeGroupId": "12345"
}
""";
// Send request
try (OutputStream os = con.getOutputStream()) {
byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
// Read response
int status = con.getResponseCode();
InputStream responseStream = (status < HttpURLConnection.HTTP_BAD_REQUEST)
? con.getInputStream()
: con.getErrorStream();
String response = new String(responseStream.readAllBytes(), StandardCharsets.UTF_8);
System.out.println("Status: " + status);
System.out.println("Response: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```python Python expandable theme={null}
import requests
url = "https://api.autosend.com/v1/mails/send"
payload = {
"from": {
"email": "no-reply@example.com",
"name": "AutoSend"
},
"to": {
"email": "user@example.com",
"name": "Test User"
},
"subject": "Welcome to AutoSend 🎉",
# Option 1: Send using template
"templateId": "your_template_id",
# Option 2: Send using raw HTML or text
"html": "Hello User
Thanks for joining AutoSend!
",
"text": "Hello User,
Thanks for joining AutoSend!",
# Optional: Unsubscribe group for compliance
"unsubscribeGroupId": "12345"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY", # Replace with your API key
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print("Status Code:", response.status_code)
print("Response:", response.text)
```
**Response:**
```json theme={null}
{
"success": true,
"data": {
"emailId": "698afb75ff4bc5466e3a797a",
"message": " Email queued successfully.",
"totalRecipients": 1
}
}
```
***
## Quickstart Guides
Explore step-by-step guides for sending emails with AutoSend across popular frameworks and platforms:
Deliver Auth0 authentication emails through the AutoSend API using a Custom Email Provider
Action.
Send authentication emails for verification, password reset, and OTP with Better Auth.
Use the official AutoSend Convex component for transactional emails with queueing, retries, and
webhook.
Use Upstash QStash to queue, schedule, retry, and reliably deliver emails through AutoSend.
Send transactional emails from Supabase Edge Functions using AutoSend.
# How to send email with AutoSend SMTP
Source: https://docs.autosend.com/quickstart/smtp
Send transactional emails using AutoSend's SMTP relay service. Integrate with any application that supports SMTP.
AutoSend provides an SMTP relay service that allows you to send transactional emails using standard SMTP protocols. This is useful when integrating with platforms that require SMTP credentials, such as [Supabase](/guides/smtp/supabase), [WordPress](/guides/smtp/wordpress), or other services that don't support REST APIs.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create a project-specific SMTP key from the SMTP tab in Project Settings.
## SMTP Credentials
Use the following credentials to configure your SMTP client:
| Setting | Value |
| -------------- | --------------------------------- |
| **Host** | `smtp.autosend.com` |
| **Port** | `465`, `587` |
| **Username** | autosend |
| **Password** | AS\_xxxx (Your AutoSend SMTP key) |
| **Encryption** | TLS/SSL (see port guide below) |
**API Key and SMTP Key are different.** Your API key (used to authenticate REST API requests)
cannot be used as your SMTP password. SMTP requires a separate, project-specific SMTP key created
from the [SMTP tab in Project Settings](https://autosend.com/settings/smtp).
## Port Configuration
AutoSend supports multiple ports to accommodate different network configurations:
| Port | Security | Description |
| ----- | ------------ | ----------------------------------------------------------------------------- |
| `465` | Implicit TLS | SMTPS - Connection is encrypted from the start. Recommended for most uses. |
| `587` | STARTTLS | Submission port - Starts unencrypted, upgrades to TLS. Most widely supported. |
**Which port should I use?** Start with port `587` (STARTTLS) as it's the most widely supported.
For implicit TLS, use port `465`.
## Integration Examples
```javascript Node.js expandable theme={null}
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtp.autosend.com',
port: 587,
secure: true,
auth: {
user: 'autosend',
pass: 'AS_xxx',
},
});
await transporter.sendMail({
from: 'sender@yourdomain.com',
to: 'recipient@example.com',
subject: 'Hello from AutoSend',
html: 'Welcome!
This email was sent via SMTP.
',
});
```
```python Python expandable theme={null}
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
smtp_host = "smtp.autosend.com"
smtp_port = 587
username = "sender@yourdomain.com"
password = "as_your_smtp_key_here"
msg = MIMEMultipart("alternative")
msg["Subject"] = "Hello from AutoSend"
msg["From"] = username
msg["To"] = "recipient@example.com"
html = "Welcome!
This email was sent via SMTP.
"
msg.attach(MIMEText(html, "html"))
with smtplib.SMTP(smtp_host, smtp_port) as server:
server.starttls()
server.login(username, password)
server.sendmail(username, ["recipient@example.com"], msg.as_string())
```
```php PHP expandable theme={null}
isSMTP();
$mail->Host = 'smtp.autosend.com';
$mail->SMTPAuth = true;
$mail->Username = 'sender@yourdomain.com';
$mail->Password = 'as_your_smtp_key_here';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->setFrom('sender@yourdomain.com', 'Your Name');
$mail->addAddress('recipient@example.com');
$mail->isHTML(true);
$mail->Subject = 'Hello from AutoSend';
$mail->Body = 'Welcome!
This email was sent via SMTP.
';
$mail->send();
?>
```
```ruby Ruby expandable theme={null}
require 'net/smtp'
message = <<~MESSAGE
From: sender@yourdomain.com
To: recipient@example.com
Subject: Hello from AutoSend
Content-Type: text/html
Welcome!
This email was sent via SMTP.
MESSAGE
Net::SMTP.start('smtp.autosend.com', 587, 'yourdomain.com',
'sender@yourdomain.com', 'as_your_smtp_key_here', :plain) do |smtp|
smtp.send_message message, 'sender@yourdomain.com', 'recipient@example.com'
end
```
```go Go expandable theme={null}
package main
import (
"net/smtp"
)
func main() {
auth := smtp.PlainAuth("", "sender@yourdomain.com", "as_your_smtp_key_here", "smtp.autosend.com")
to := []string{"recipient@example.com"}
msg := []byte("To: recipient@example.com\r\n" +
"Subject: Hello from AutoSend\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Welcome!
This email was sent via SMTP.
\r\n")
err := smtp.SendMail("smtp.autosend.com:587", auth, "sender@yourdomain.com", to, msg)
if err != nil {
panic(err)
}
}
```
## Platform Integrations
AutoSend SMTP works with any platform that supports SMTP configuration:
Send authentication emails through AutoSend SMTP.
Send emails from Customer.io through AutoSend SMTP.
Send authentication emails from Descope through AutoSend SMTP.
Send emails from Node.js apps using Nodemailer with AutoSend SMTP.
Send auth emails from Supabase through AutoSend SMTP.
Configure WordPress to send emails through AutoSend SMTP.
## Testing with Swaks
[Swaks](https://www.jetmore.org/john/code/swaks/) (Swiss Army Knife for SMTP) is a powerful command-line tool for testing SMTP configurations.
### Installation
```bash macOS theme={null}
brew install swaks
```
```bash Ubuntu/Debian theme={null}
sudo apt-get install swaks
```
```bash Windows theme={null}
# Using Chocolatey
choco install swaks
```
### Basic Test
```bash theme={null}
swaks --to recipient@example.com \
--from sender@yourdomain.com \
--server smtp.autosend.com \
--port 587 \
--tls \
--auth-user sender@yourdomain.com \
--auth-password AS_xxx
--header "Subject: Test Email via SMTP" \
--body "This email was sent via AutoSend SMTP."
```
### Test with Implicit TLS (Port 465)
```bash theme={null}
swaks --to recipient@example.com \
--from sender@yourdomain.com \
--server smtp.autosend.com \
--port 465 \
--auth-user sender@yourdomain.com \
--auth-password as_your_smtp_key_here \
--tlsc
```
### Test with HTML Body
```bash theme={null}
swaks --to recipient@example.com \
--from sender@yourdomain.com \
--server smtp.autosend.com \
--port 587 \
--auth-user sender@yourdomain.com \
--auth-password as_your_smtp_key_here \
--header "Content-Type: text/html" \
--body "Test Email
This is a test from swaks.
"
```
Add `-v` or `-vv` for detailed SMTP conversation logs.
## Troubleshooting
**Possible causes:**
* The SMTP port may be blocked by your firewall or ISP
* Incorrect server hostname
* Network connectivity issues
**Solutions:**
* Verify the hostname is `smtp.autosend.com`
* Check your firewall rules
* Test connectivity: `telnet smtp.autosend.com 587`
**Possible causes:**
* Invalid SMTP key
* Username doesn't match a verified sender
**Solutions:**
* Verify your SMTP key in the SMTP Settings
* Ensure the username is a verified sender email address
* Check that your domain is properly verified
* Check for any extra whitespace in your credentials
**Possible causes:**
* Using wrong encryption mode for the port
* TLS certificate verification issues
**Solutions:**
* For ports `465`: Use implicit TLS (connection encrypted from start)
* For ports `587`: Use STARTTLS (starts unencrypted, upgrades to TLS)
* If using swaks, add `--tlsc` flag only for ports 465
**Possible causes:**
* Domain not verified
* SPF/DKIM not configured
* Recipient server blocking
**Solutions:**
* Verify your sending domain is configured and verified in AutoSend
* Check the sender email matches your verified domain
* Add the required DNS records for SPF and DKIM
* Review email activity in the Email Activity dashboard
## FAQs
Yes! All emails sent through SMTP appear in your AutoSend Dashboard under the [**Email
Activity**](/transactional-emails/email-activity) section. You can view delivery status, opens,
clicks, and bounces just like API-sent emails.
SMTP rate limits are the same as the REST API. Your plan's sending limits apply across both SMTP
and API. Check your [Dashboard](https://autosend.com/dashboard) for your current limits.
Yes, AutoSend SMTP supports standard MIME attachments. Most SMTP libraries handle attachment
encoding automatically. The maximum message size is 25MB including attachments.
SMTP adds minimal overhead compared to the REST API. For high-volume sending or when you need
immediate delivery confirmation, the API may be slightly faster. For most use cases, SMTP
performance is excellent.
Yes! [Webhooks](/others/webhooks/introduction) are triggered for all events (delivery, bounce,
open, click, etc.) regardless of whether the email was sent via SMTP or the API.
Yes, you can send from any email address on a verified domain. Simply use the desired sender
address in the "From" field. But make sure the domain is verified in your AutoSend account.
## Next Steps
Set up SPF, DKIM, and DMARC for better deliverability
Explore our REST API for more advanced use cases
Set up webhooks to track email events in real-time
Monitor your email delivery and engagement
# AutoSend Node.js SDK
Source: https://docs.autosend.com/sdk/nodejs
Get started with the official AutoSend Node.js SDK to send emails programmatically.
The official AutoSend Node.js SDK provides a simple and intuitive way to send transactional and marketing emails from your Node.js applications. It includes full TypeScript support and handles authentication, request formatting, and error handling for you.
## Prerequisites
Make sure you have a verified domain added in AutoSend to send emails from.
Create an API key to authenticate your SDK requests.
## Installation
Install the SDK using your preferred package manager:
```bash npm theme={null}
npm install autosendjs
```
```bash yarn theme={null}
yarn add autosendjs
```
```bash pnpm theme={null}
pnpm add autosendjs
```
## Quick Start
Initialize the SDK with your API key:
```typescript theme={null}
import { Autosend } from 'autosendjs';
const autosend = new Autosend('AS_xxxxxxxxxxxx');
```
Store your API key in an environment variable (e.g., `AUTOSEND_API_KEY`) rather than hardcoding it
in your source code.
## Configuration Options
The SDK accepts optional configuration parameters:
```typescript theme={null}
const autosend = new Autosend('AS_xxxxxxxxxxxx', {
baseUrl: 'https://api.autosend.com/v1',
timeout: 30000,
maxRetries: 3,
debug: false,
});
```
| Option | Type | Default | Description |
| ------------ | ------- | ----------------------------- | -------------------------------------------- |
| `baseUrl` | string | `https://api.autosend.com/v1` | API base URL |
| `timeout` | number | `30000` | Request timeout in milliseconds |
| `maxRetries` | number | `3` | Number of retry attempts for failed requests |
| `debug` | boolean | `false` | Enable debug logging |
## Sending Emails
### Plain Text Email
```typescript theme={null}
await autosend.emails.send({
from: { email: 'hello@yourdomain.com' },
to: { email: 'user@example.com' },
subject: 'Hello World',
text: 'Welcome to AutoSend!',
});
```
### HTML Email
```typescript theme={null}
await autosend.emails.send({
from: { email: 'hello@yourdomain.com', name: 'Your Company' },
to: { email: 'user@example.com', name: 'John Doe' },
subject: 'Welcome to Our Platform',
html: 'Welcome!
Thanks for signing up.
',
});
```
### Using Templates
Send emails using a pre-built template with dynamic variables:
```typescript theme={null}
await autosend.emails.send({
from: { email: 'hello@yourdomain.com' },
to: { email: 'user@example.com' },
subject: 'Your Order Confirmation',
templateId: 'your_template_id',
dynamicData: {
name: 'Johnrao',
orderNumber: '12345',
orderTotal: '$99.00',
},
});
```
Learn more about creating and managing templates in the [Email
Templates](/transactional-emails/email-templates) documentation.
### React Email
Pass a [React Email](https://react.email) component to the `react` option and the SDK renders it to HTML for you before sending:
```tsx theme={null}
import { WelcomeEmail } from './emails/welcome';
await autosend.emails.send({
from: { email: 'hello@yourdomain.com' },
to: { email: 'user@example.com' },
subject: 'Welcome',
react: ,
});
```
Rendering React components requires the optional `@react-email/render` peer dependency. Install it
(along with `react` and your `@react-email/components`) with `npm install @react-email/render`. If
it is missing when you pass `react`, the SDK throws an error.
The `react` option also works with `autosend.emails.bulk`, and `{{...}}` dynamic-data placeholders
still work for per-recipient personalization. If you provide both `html` and `react`, the explicit
`html` takes precedence.
## Bulk Emails
Send multiple emails in a single API call:
```typescript theme={null}
await autosend.emails.bulk({
from: { email: 'you@example.com' },
subject: 'Hello',
html: 'Welcome!
',
recipients: [{ email: 'user1@gmail.com' }, { email: 'user2@gmail.com' }],
});
```
Bulk sending is more efficient than sending emails individually. Use it when you need to send the
same or similar emails to multiple recipients.
## Receiving Emails (Inbound)
The `inboundEmails` module lets you work with emails received on your inbound-enabled domain. You can list and read messages, download their attachments, and reply to them directly from your code.
### List Messages
Retrieve received messages with optional filters and pagination. The response returns matching items along with pagination metadata.
```typescript theme={null}
const result = await autosend.inboundEmails.list({
from: 'sender@example.com',
search: 'invoice',
page: 1,
limit: 25,
});
console.log(result.data.items); // matched messages
console.log(result.data.pagination); // { page, limit, total, pages }
```
Supported filters: `from`, `to`, `threadId`, `search`, `dateFrom`, `dateTo`, `page`, and `limit`.
### Get a Message
Fetch a single message by its ID. This returns the full message, including its `text` and `html` body, `attachments`, `headers`, thread details, and spam, SPF, DKIM, and DMARC verdicts.
```typescript theme={null}
const message = await autosend.inboundEmails.get('60d5ec49f1b2c72d9c8b1234');
console.log(message.data);
```
### Download an Attachment
Generate a signed, time-limited URL to download an attachment. The `attachmentRef` can be a numeric index or the stable `attachmentId` from the message.
```typescript theme={null}
const attachment = await autosend.inboundEmails.getAttachmentDownloadUrl(
'60d5ec49f1b2c72d9c8b1234',
0,
);
console.log(attachment.data.downloadUrl); // signed URL
console.log(attachment.data.expiresIn); // seconds until it expires
console.log(attachment.data.filename);
```
### Reply to a Message
Send a reply to a received message. The reply is threaded to the original message automatically.
```typescript theme={null}
const reply = await autosend.inboundEmails.reply('60d5ec49f1b2c72d9c8b1234', {
from: { email: 'support@yourdomain.com', name: 'Support' },
subject: 'Re: Hello',
html: 'Thanks for reaching out!
',
});
console.log(reply.data.emailId);
console.log(reply.data.status); // 'QUEUED'
console.log(reply.data.totalRecipients);
```
Learn more in the Inbound Email guide and the API
reference for listing,
retrieving,
downloading attachments from, and
replying to inbound messages.
## Sync user data to Contacts on AutoSend
The SDK lets you create, retrieve, update, and delete contacts directly from your code—useful for syncing users from your app, updating [contact properties](/marketing-emails/contacts/contact-properties), or managing [unsubscribe](/others/unsubscribe-groups) preferences.
### Create a Contact
Add a new contact to your AutoSend account. You can assign them to one or more lists and include contact properties for segmentation.
```typescript theme={null}
await autosend.contacts.create({
email: 'user@example.com',
firstName: 'John',
lastName: 'Doe',
listIds: ['list_abc123'],
customFields: {
company: 'Acme Inc',
plan: 'pro',
},
});
```
### Get a Contact
Retrieve a contact's details by their ID. This returns their email, name, list memberships, contact properties, and subscription status.
```typescript theme={null}
const contact = await autosend.contacts.get('contact_id');
console.log(contact);
```
### Create or Update a Contact
Use `upsert` when you're not sure if a contact already exists. If the email exists, it updates the contact; otherwise, it creates a new one. This is ideal for syncing users from your application.
```typescript theme={null}
await autosend.contacts.upsert({
email: 'user@example.com',
firstName: 'Jane',
customFields: {
plan: 'enterprise',
},
});
```
### Delete a Contact
Permanently remove a contact from your account. This removes them from all lists and deletes their data.
```typescript theme={null}
await autosend.contacts.delete('contact_id');
```
Learn more about lists, segments, and contact properties in the
[Contacts](/marketing-emails/contacts/introduction) documentation.
## Resend Adapter
If you're migrating from Resend, the SDK provides a compatibility adapter that mirrors the Resend API patterns:
```typescript theme={null}
import { Resend } from 'autosendjs/resend';
// Use your AutoSend API key
const resend = new Resend('AS_xxxxxxxxxxxx');
// Or use the RESEND_API_KEY environment variable
const resend = new Resend();
```
The Resend adapter uses `properties` instead of `customFields` for contacts, and `remove()`
instead of `delete()` to match Resend's API conventions.
## TypeScript Support
The SDK is written in TypeScript and provides full type definitions out of the box. All methods, parameters, and responses are fully typed for better developer experience and IDE support.
```typescript theme={null}
import { Autosend, SendEmailRequest } from 'autosendjs';
const autosend = new Autosend('AS_xxxxxxxxxxxx');
const emailRequest: SendEmailRequest = {
from: { email: 'hello@yourdomain.com' },
to: { email: 'user@example.com' },
subject: 'Type-safe email',
html: 'This request is fully typed!
',
};
await autosend.emails.send(emailRequest);
```
## Troubleshooting
**Possible causes:**
* Invalid or expired API key
* API key not included in the request
**Solutions:**
* Verify your API key in the Dashboard
* Ensure you're passing the API key correctly when initializing the SDK
* Check that your API key hasn't been revoked
**Possible causes:**
* Network connectivity issues
* Request payload too large
**Solutions:**
* Increase the timeout in configuration options
* Check your network connection
* For bulk emails, reduce the batch size
**Possible causes:**
* Sending from an unverified domain
* DNS records not properly configured
**Solutions:**
* Verify your sending domain in the Dashboard
* Ensure SPF and DKIM records are properly set up
* Check the [Domain Configuration](/domain) guide
**Possible causes:**
* Too many requests in a short period
**Solutions:**
* Implement exponential backoff in your application
* Use bulk sending instead of individual requests
* Check the [Rate Limits](/api-reference/rate-limit) documentation for your plan's limits
## Resources
View source code, report issues, and contribute.
View package details and version history.
## Examples
A Next.js example app in JavaScript using the AutoSend SDK.
A Next.js example app in TypeScript using the AutoSend SDK.
## Next Steps
Explore the complete API reference for advanced use cases
Create reusable email templates with dynamic variables
Set up webhooks to track email events in real-time
Learn how to manage contacts and lists
Receive incoming emails on your domain and process them programmatically
# Email Activity
Source: https://docs.autosend.com/transactional-emails/email-activity
Track delivery, performance, and troubleshoot issues for your transactional emails.
**Email Activity** gives you a complete view of every email (transactional or marketing) sent through your AutoSend account. It helps you track delivery, performance, and troubleshoot issues related to your transactional email workflows.
Every time a transactional email is sent from your AutoSend account, whether it’s a password reset, order confirmation, or notification, it appears in your **Email Activity** view.
You can monitor each message’s status, delivery details, and engagement (if applicable).
The **Email Activity** section acts as your go-to audit log for transactional messages, helping you ensure reliable email delivery and visibility across your system.
## What You Can See
Each entry in the Email Activity list includes:
* **Status:** The delivery state of the email — *Processed, Queued, Sent, Delivered, or Bounced.*
* **Recipient:** The email address the message was sent to.
* **Subject:** The subject line of the email.
* **Timestamp:** When the email was sent.
* **Opens:** How many times the email was opened by the recipient. (This is only available if tracking is turned on.)
* **Clicks:** How many times the links in the email were clicked by the recipient. (This is only available if tracking is turned on.)
You can click on an individual email record to view the Event History and Details like activity logs, including SMTP response codes, bounce reasons, or event timelines (send → delivery → open → click, etc., depending on available tracking).
***
## Email Activity Filters
You can filter your email activity by multiple criteria to quickly identify patterns, troubleshoot issues, and analyze performance.
### Filter Options
* **Status Filters**: Filter by email delivery and engagement status
* Delivery lifecycle: Sent, Delivered, Bounced
* Engagement: Opened, Clicked
* Issues: Complained, Suppressed, Failed
* **Email Source**: Filter by email template or campaign to see activity for specific email types
* **Sending Domain**: Filter emails sent from specific verified domains
* **API Key**: Filter emails sent using specific API keys for better organization and tracking
***
## Why It’s Useful
Monitoring your **Email Activity** helps you:
* **Diagnose delivery issues:** Identify bounces, failures, or deferrals quickly.
* **Track engagement:** See when recipients open or click transactional emails (if tracking is enabled).
* **Verify email behavior:** Confirm if an email was triggered and sent successfully from your application.
* **Optimize deliverability:** Spot patterns that might affect your sending reputation and take corrective steps (e.g., cleaning invalid contacts).
# Email Templates
Source: https://docs.autosend.com/transactional-emails/email-templates
Create reusable, personalized email templates for transactional emails.
Email templates in AutoSend let you create reusable, personalized designs for **transactional emails** like password resets, order confirmations, or account notifications that are sent programmatically through the **Email API**.
You can also create and manage templates from your AI assistant. Connect the AutoSend MCP server to create, update, and search templates using natural language. Install the AutoSend skill to give your AI coding agent the context it needs to generate template code in your editor.
## Creating an Email Template in AutoSend
1. Under **Transactional Emails** in the sidebar, go to **Email Templates** and click **New Template**.
2. Enter a name for your email template (max **60 characters**). This name is for internal reference only and isn’t visible to recipients.
3. In the editor’s right panel, add a **Subject** for your email. Subject lines support variables,
for example: `Welcome to {{company_name}}, {{first_name}}!`
Keep transactional email subject lines under 50 characters. Aim for 6–8 words
that clearly state the core action and purpose to improve deliverability and
user trust.
## Email Composer
Create your email using HTML with inline CSS for styling. AutoSend's email templates support responsive design, so make sure to write HTML that renders well across different email clients and devices.
* Write responsive HTML with inline CSS for maximum compatibility
* Use variables anywhere in the HTML to personalize content dynamically, for example: `{{first_name}}`, `{{company_name}}`, `{{email}}`, etc.
* Variables are wrapped in double curly braces and will be replaced with actual values when the email is sent
* Keep your HTML clean and well-structured to ensure consistent rendering across email clients
* Test your template thoroughly in both desktop and mobile views before deployment
**Example template (HTML)**
```html expandable theme={null}
Welcome to {{company_name}}, {{first_name}}!
Thank you for signing up. We're excited to have you on board.
Your account email: {{email}}
Verify Your Email
If you have any questions, just reply to this email.
Best regards,
The {{company_name}} Team
```
## Email Preview
The right side of your screen is the Email Preview. Any changes you make in the composer will be reflected in the preview.
You can easily switch between **Desktop** and **Mobile** views using the device icons at the top of the email preview. This allows you to confirm that your design adapts well to different screen sizes and remains visually consistent.
Links are disabled in the preview to prevent accidental navigation while testing.
## Test Data
To see how your personalized email will look, you can use the **Test Data** panel to add sample data in JSON format. This helps you verify that all variables are correctly replaced and the final layout looks as expected.
For example:
```json theme={null}
{
"first_name": "John",
"company_name": "AutoSend",
"email": "john@example.com",
"verification_link": "https://app.autosend.com/verify?token=abc123"
}
```
## Sending a Test Email
Once your design looks good, you can send yourself a test email. This allows you and your team to review the email across multiple inboxes and clients.
Click **Send Test** above the **Email Preview**, then enter up to 10 comma-separated addresses and click **Send Test.**
You must verify your domain
before you can send test emails. Test emails are sent from `test@yourdomain.com`.
## Using your template
Once saved, AutoSend will generate a unique **Template ID** for your email. You can use this ID in your API calls to send transactional emails.
After saving, you can continue editing your template at any time. You can also duplicate it to create variations useful for A/B testing or different use cases.
## Next Steps
Create and manage templates from your AI assistant using the AutoSend MCP server.
Install the AutoSend skill to give AI coding agents context for building templates.
# Troubleshooting
Source: https://docs.autosend.com/transactional-emails/troubleshooting
Common issues and solutions for email templates, rendering, and delivery problems.
## Template Not Rendering Correctly
**Solutions:**
- Verify you're passing `dynamicData` in your API request
- Ensure the dynamic data is a valid JSON object
- Check for typos in variable names
**Solutions:**
- Test in multiple email clients (Gmail, Outlook, etc.)
- Use inline styles instead of `