# Sidemail Documentation for AI Agents
Use these docs to integrate Sidemail email sending into applications.
---
# Sidemail Agent Setup
Source: https://sidemail.io/agent-setup/index.md
# Sidemail Agent Setup
Use this file when adding Sidemail email sending to an application with an AI coding agent.
## Goal
Integrate Sidemail as the server-side email provider for transactional email.
Sidemail can send emails through:
- The Sidemail Email Sending API or SDKs, which should be used in application code.
- The Sidemail MCP server, which should be used by AI tools to manage Sidemail resources such as domains, contacts, Messenger drafts, email queries, and test sends.
Do not add the MCP server to production application code. MCP is for the agent or developer tool environment.
## Recommended Agent Prompt
```text
Fetch https://sidemail.io/agent-setup/index.md and follow it to integrate Sidemail into this project. Detect the framework, add server-side email sending with SIDEMAIL_API_KEY, update tests, and do not commit secrets or send real emails without confirmation. Use Sidemail MCP only for Sidemail account operations such as domains, contacts, Messenger drafts, querying emails, or test sends.
```
## Credentials
- Store the API key in `SIDEMAIL_API_KEY`.
- Keep the API key server-side only.
- Never expose the key in frontend bundles, mobile apps, public repositories, logs, or screenshots.
- Do not hardcode the API key in source files.
- Do not send real emails during tests unless the user explicitly asks for a live test.
## Primary Docs
- [API authentication](https://sidemail.io/docs/api/authentication/index.md)
- [Email API reference](https://sidemail.io/docs/api/email/index.md)
- [Email sending quickstart](https://sidemail.io/docs/email-sending-quickstart/index.md)
- [MCP server setup](https://sidemail.io/docs/mcp-server/index.md)
- [Node.js quickstart](https://sidemail.io/docs/send-emails-with-nodejs/index.md)
- [Next.js quickstart](https://sidemail.io/docs/send-emails-with-nextjs/index.md)
- [Python quickstart](https://sidemail.io/docs/send-emails-with-python/index.md)
## Implementation Checklist
1. Detect the application framework and runtime.
2. Add `SIDEMAIL_API_KEY` to the server environment configuration.
3. Install the official SDK when it fits the stack, or call the HTTPS API directly.
4. Create a small server-only email client or helper module.
5. Wire the requested transactional email events, for example welcome, password reset, login, trial, invoice, or notification email.
6. Use `templateName`, `templateId`, `markdown`, `html`, or `text` according to the app's needs.
7. Use a verified `fromAddress` domain before production.
8. Add tests that mock Sidemail calls rather than sending real email.
9. Document the required environment variable for deployment.
## API Rules
- Send API requests over HTTPS only.
- Authenticate with `Authorization: Bearer ${SIDEMAIL_API_KEY}`.
- Include `Content-Type: application/json`.
- Use `POST https://api.sidemail.io/v1/emails` for sending email.
- Use `fromAddress` from a verified sending domain in production.
- Keep total recipients across `toAddress`, `cc`, and `bcc` at 50 or fewer.
- Use attachments only when needed and keep the combined Base64 size under 5 MB.
## MCP Setup
Use MCP when the agent needs to inspect or manage Sidemail account resources. Prefer normal API or SDK integration for application code.
### Codex
```bash
codex mcp add sidemail --env SIDEMAIL_API_KEY=replace-with-your-api-key -- npx -y @sidemail/mcp
```
### Claude Desktop
```json
{
"mcpServers": {
"sidemail": {
"command": "npx",
"args": ["-y", "@sidemail/mcp"],
"env": { "SIDEMAIL_API_KEY": "replace-with-your-api-key" }
}
}
}
```
### Cursor
```json
{
"mcpServers": {
"sidemail": {
"command": "npx",
"args": ["-y", "@sidemail/mcp"],
"env": { "SIDEMAIL_API_KEY": "replace-with-your-api-key" }
}
}
}
```
## Acceptance Criteria
- The app sends emails only from server-side code.
- The Sidemail API key is read from environment variables.
- Tests do not send real email by default.
- Error handling does not leak secrets.
- The implementation includes enough documentation for the next developer to configure `SIDEMAIL_API_KEY`.
---
# API authentication
Source: https://sidemail.io/docs/api/authentication/index.md
# API authentication
The Sidemail API uses API keys to authenticate requests. You can view and manage your API keys in the [Sidemail Dashboard](https://client.sidemail.io). All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail.
Your API keys give full access to your account, so keep them safe. Do not share your secret API keys in public places like GitHub or client-side code.
## Using a library
If you use a library, configure it with your API key.
```javascript
const configureSidemail = require("sidemail");
const sm = configureSidemail({ apiKey: "replace-with-your-api-key" });
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
```
```ruby
require "sidemail"
sm = Sidemail.new(api_key: "replace-with-your-api-key")
```
## Making API requests manually
To authenticate, use the `Authorization` header in your HTTP request.
Include these HTTP headers when making an API request:
- `Content-Type: application/json` to tell Sidemail you're sending JSON data
- `Authorization: Bearer replace-with-your-api-key` to authenticate the API request with your API key
---
# Contact API
Source: https://sidemail.io/docs/api/contacts/index.md
# Contact profiles API methods
### Available API endpoints:
- `POST https://api.sidemail.io/v1/contacts` — Create or update a contact
- `POST https://api.sidemail.io/v1/contacts/query` — Query contacts
- `GET https://api.sidemail.io/v1/contacts` — List all contacts
- `GET https://api.sidemail.io/v1/contacts/{emailAddress}` — Find a contact
- `DELETE https://api.sidemail.io/v1/contacts/{emailAddress}` — Delete a contact
---
## Contact Object
A contact object represents a single contact profile.
- `emailAddress` (string): Email address of the contact.
- `identifier` (string, optional): Your unique identifier for the contact.
- `id` (string): Sidemail's unique identifier of the contact.
- `projectId` (string): Identifier of the project associated with the contact.
- `createdAt` (string): ISO8601 date string when the contact was created.
- `timezone` (string, optional): Timezone of the contact (e.g., "Europe/Prague").
- `timezoneUtcOffset` (number, optional): UTC offset in minutes based on the contact's timezone.
- `isSubscribed` (boolean): Indicates whether the contact is subscribed.
- `gravatarUrl` (string, optional): URL of the contact's Gravatar image, automatically generated from their email address.
- `subscribedIp` (string, optional): IP address from which the contact subscribed.
- `subscribedAt` (string, optional): ISO8601 date string when the contact subscribed.
- `unsubscribedAt` (string, optional): ISO8601 date string when the contact unsubscribed.
- `sourceType` (string): Source of the contact ("api", "bulk-import", "stripe", "hostedform").
- `updatedAt` (string): ISO8601 date string when the contact was last updated.
- `groups` (array, optional): List of group IDs the contact belongs to.
- `customProps` (array, optional): List of custom properties associated with the contact.
- `keyName` (string): Name of the custom property.
- `value` (string | number): Value of the custom property.
- `updatedAt` (string): ISO8601 date string when the property was last updated.
- `note` (string, optional): Additional notes about the contact.
### Example data:
```json
{
"id": "307f1f70bcf86cd799439011",
"projectId": "507f1f77bcf86cd799439011",
"emailAddress": "marry@lightling.com",
"identifier": "123",
"createdAt": "2019-08-15T13:20:39.160Z",
"timezone": "Europe/Prague",
"isSubscribed": true,
"subscribedAt": "2019-08-15T13:20:39.160Z",
"subscribedIp": "192.168.1.1",
"unsubscribedAt": null,
"sourceType": "api",
"updatedAt": "2019-08-15T13:20:39.160Z",
"groups": ["257f1f77bcf86cd799439011"],
"customProps": [
{
"keyName": "name",
"value": "Marry Lightning",
"updatedAt": "2019-08-15T13:20:39.160Z"
},
{
"keyName": "subscriptionPlan",
"value": "premium",
"updatedAt": "2019-08-15T13:20:39.160Z"
}
],
"note": "VIP customer"
}
```
---
## Create or update a contact
`POST https://api.sidemail.io/v1/contacts`
```javascript
const configureSidemail = require("sidemail");
const sidemail = configureSidemail({ apiKey: "xxxxx" });
const response = await sidemail.contacts.createOrUpdate({
emailAddress: "john.doe@example.com",
identifier: "123",
customProps: {
fullName: "John doe",
pricingPlan: "premium",
registeredAt: "2019-08-15T13:20:39.160Z",
lastSeenAt: "2019-08-20T17:40:39.160Z",
},
});
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
sm.contacts.create_or_update(
emailAddress="john.doe@example.com",
identifier="123",
customProps={
"fullName": "John doe",
"pricingPlan": "premium",
"registeredAt": "2019-08-15T13:20:39.160Z",
"lastSeenAt": "2019-08-20T17:40:39.160Z",
# ... more of your contact data ...
},
)
```
```ruby
require "sidemail"
sm = Sidemail.new(api_key: "replace-with-your-api-key")
sm.contacts.create_or_update(
emailAddress: "john.doe@example.com",
identifier: "123",
customProps: {
fullName: "John doe",
pricingPlan: "premium",
registeredAt: "2019-08-15T13:20:39.160Z",
lastSeenAt: "2019-08-20T17:40:39.160Z",
# ... more of your contact data ...
}
)
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
$sm->contacts->createOrUpdate([
'emailAddress' => 'john.doe@example.com',
'identifier' => '123',
'customProps' => [
'fullName' => 'John doe',
'pricingPlan' => 'premium',
'registeredAt' => '2019-08-15T13:20:39.160Z',
'lastSeenAt' => '2019-08-20T17:40:39.160Z',
// ... more of your contact data ...
],
]);
```
```bash
curl -X POST https://api.sidemail.io/v1/contacts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer replace-with-your-api-key" \
-d '{
"emailAddress": "john.doe@example.com",
"identifier": "123",
"customProps": {
"fullName": "John doe",
"pricingPlan": "premium",
"registeredAt": "2019-08-15T13:20:39.160Z",
"lastSeenAt": "2019-08-20T17:40:39.160Z"
}
}'
```
### Parameters
**identifier** `string`\
Unique string, that represent user in your system. It's recommended to use identifier and not just rely on email.
---
**emailAddress** `string`\
User's email address.
- If user changes email in your system, Sidemail will update the email value only if identifier was set. Otherwise, new contact will be created.
---
**isSubscribed** `boolean` `optional`\
Specifies if Sidemail should set contact's status to subscribed or unsubscribed.
---
**timezone** `string` `optional`\
Optionally set and update contact's timezone, this has to be [a valid timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). This is useful for timezone based delivery with Messenger.
---
**customProps** `object`\
Specify values of properties here. Use exactly the same property key name (whitespace sensitive) as you used when creating the property. Property value has to have matching data type as you specified when creating the property. Otherwise, Sidemail returns validation error. Null is allowed.
---
**groups** `array` `optional`\
An array where each item must be group ID `(string)`. To see a group ID, navigate to the contact page in your project, hover over a group and click the three-dot icon. The group ID is displayed below "Edit group" title in the edit modal window. Group ID has this format: `"5d45dd1ca546d200fe201f83"`.
### Returns
If contact was newly created:
```javascript
{
"status": "created"
}
```
or if contact was updated:
```javascript
{
"status": "updated"
}
```
---
## Find a contact
`GET https://api.sidemail.io/v1/contacts/:emailAddress`
Examples:
```js
const configureSidemail = require("sidemail");
const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" });
const response = await sidemail.contacts.find({
emailAddress: "marry@lightning.com",
});
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
resp = sm.contacts.find(emailAddress="marry@lightning.com")
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
$response = $sm->contacts->find([
'emailAddress' => 'marry@lightning.com'
]);
```
```bash
curl -X GET "https://api.sidemail.io/v1/contacts/marry@lightning.com" \
-H "Authorization: Bearer replace-with-your-api-key"
```
Retrieves the contact data. You need only supply the contact email address to the URL.
### Parameters
*No parameters.*
### Returns
Returns a contact object if contact was found or `null`.
```javascript
{
contact: {
"id": "307f1f70bcf86cd799439011",
"projectId": "507f1f77bcf86cd799439011",
"emailAddress": "marry@lightling.com",
"identifier": "123",
"createdAt": "2019-08-15T13:20:39.160Z",
"timezone": "Europe/Prague",
"isSubscribed": true,
"subscribedAt": "2019-08-15T13:20:39.160Z",
"unsubscribedAt": null,
"sourceType": "api",
"updatedAt": "2019-08-15T13:20:39.160Z",
"groups": ["257f1f77bcf86cd799439011"],
"customProps": [
{
"keyName": "name",
"value": "Marry Lightning",
"updatedAt": "2019-08-15T13:20:39.160Z"
}, {
"keyName": "subscriptionPlan",
"value": "premium",
"updatedAt": "2019-08-15T13:20:39.160Z"
}
],
"note": null
}
}
```
---
## Query contacts (unstable)
`POST https://api.sidemail.io/v1/contacts/query`
Retrieve a list of your contacts, with support for advanced filtering, searching, and pagination. The contacts are returned sorted by creation date, with the most recent contacts appearing first.
JSON payload example:
```json
{
"search": "@blueberry.com",
"isSubscribed": true,
"groupId": ["5d45dd1ca546d200fe201f83"],
"contactId": ["5d45dd1ca546d200fe201f84"],
"match": "all",
"filter": {
"match": "all",
"rules": [
{
"field": "customProps.plan",
"operator": "includes",
"value": "premium"
},
{
"field": "createdAt",
"operator": "gte",
"value": "2023-01-01T00:00:00.000Z"
}
]
},
"offset": 0,
"limit": 20
}
```
### Parameters
**contactId** `string` or `string[]`\
Filter by contact ID(s). Supports a single ID or array of IDs.
---
**groupId** `string` or `string[]`\
Filter by group ID(s). Supports a single ID or array of IDs.
---
**search** `string`\
Partial email address search, case-insensitive.
---
**isSubscribed** `boolean` `optional`\
Filter by subscription status.
---
**match** `"all"` or `"any"` `optional`\
Combine top-level filters with AND (all) or OR (any). Default is `"all"`.
---
**filter** `object` `optional`\
Advanced filter object (see below).
---
**offset** `number` `optional`\
Number of contacts to skip (pagination). Default: `0`.
---
**limit** `number` `optional`\
Number of contacts per page. Default: `20`.
### Advanced filter object (`filter`)
The `filter` object allows for complex, rule-based filtering:
- `match`: "all" or "any" (default: "all") — whether all rules must match or any rule can match.
- `rules`: Array of rule objects. Each rule:
- `field`: string (required) — the field to filter on (any contact property, e.g., `customProps.keyName`, `emailAddress`, `createdAt`)
- `operator`: string (default is `equals`) — one of: `equals`, `notEquals`, `includes`, `notIncludes`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `anyValue`, `noValue`
- `value`: string or number — the value to compare (can be empty string or null for some operators)
Example filter rule:
```json
{
"filter": {
"match": "all",
"rules": [
{ "field": "timezone", "operator": "includes", "value": "Europe" },
{
"field": "customProps.registeredAt",
"operator": "gt",
"value": "2024-01-01T00:00:00.000Z"
},
{ "field": "customProps.plan", "value": "pro" }
]
}
}
```
### Returns
Returns an object with the following structure:
```json
{
"data": [
/* Array of contact objects */
],
"pageCount": 5, // Total number of pages
"totalCount": 42 // Total number of contacts matching the query
}
```
## List all contacts
`GET https://api.sidemail.io/v1/contacts`
```js
const configureSidemail = require("sidemail");
const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" });
const response = await sidemail.contacts.list();
// to paginate:
// const response = await sidemail.contacts.list({ paginationCursorNext: "123" });
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
resp = sm.contacts.list()
# paginate: resp = sm.contacts.list(paginationCursorNext="123")
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
$response = $sm->contacts->list();
// paginate: $response = $sm->contacts->list([ 'paginationCursorNext' => '123' ]);
```
```bash
curl -X GET "https://api.sidemail.io/v1/contacts" \
-H "Authorization: Bearer replace-with-your-api-key"
```
Returns a list of your contacts. The contacts are returned sorted by creation date, with the most recent contacts appearing first.
### Parameters
**paginationCursorNext** `string`\
A cursor for use in pagination. `paginationCursorNext` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with `obj_foo`, your subsequent call can include `paginationCursorNext=obj_foo` in order to fetch the next page of the list.
### Returns
A object with a data property that contains an array of up to 100 contacts. Each entry in the array is a separate contact object. If no more contacts are available, the resulting array will be empty.
```javascript
{
hasMore: true,
paginationCursorNext: "307f1f70bcf86cd799439011",
data: [
{
"id": "307f1f70bcf86cd799439011",
"projectId": "507f1f77bcf86cd799439011",
"emailAddress": "marry@lightling.com",
"identifier": "123",
"createdAt": "2019-08-15T13:20:39.160Z",
"timezone": "Europe/Prague",
"isSubscribed": true,
"subscribedAt": "2019-08-15T13:20:39.160Z",
"unsubscribedAt": null,
"sourceType": "api",
"updatedAt": "2019-08-15T13:20:39.160Z",
"groups": ["257f1f77bcf86cd799439011"],
"customProps": [
{
"keyName": "name",
"value": "Marry Lightning",
"updatedAt": "2019-08-15T13:20:39.160Z"
}, {
"keyName": "subscriptionPlan",
"value": "premium",
"updatedAt": "2019-08-15T13:20:39.160Z"
}
],
"note": null
}
// Another 99 contacts...
]
}
```
---
## Delete a contact
`DELETE https://api.sidemail.io/v1/contacts/:emailAddress`
```js
const configureSidemail = require("sidemail");
const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" });
const response = await sidemail.contacts.delete({
emailAddress: "marry@lightning.com",
});
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
resp = sm.contacts.delete(emailAddress="marry@lightning.com")
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
$response = $sm->contacts->delete([
'emailAddress' => 'marry@lightning.com'
]);
```
```bash
curl -X DELETE "https://api.sidemail.io/v1/contacts/marry@lightning.com" \
-H "Authorization: Bearer replace-with-your-api-key"
```
Permanently deletes a contact. It cannot be undone.
### Parameters
*No parameters.*
### Returns
Returns an object with `deleted` parameter that indicates the outcome of the operation.
```javascript
{
"deleted": true
}
```
---
# Domains API
Source: https://sidemail.io/docs/api/domains/index.md
# Domains API methods (unstable)
Sidemail enables you to manage and verify custom domains for sending emails on behalf of your brand. Before you can send from your own domain, it must be added and verified (including DNS authentication such as DKIM). This API lets you list, add, and remove domains, as well as view their verification and DNS setup status.
### Available API endpoints:
- `GET https://api.sidemail.io/v1/domains` — List all sending domains
- `POST https://api.sidemail.io/v1/domains` — Create a sending domain
- `DELETE https://api.sidemail.io/v1/domains/{id}` — Delete a sending domain
---
## Domain Object
A Domain object represents a verified sending domain used for email delivery.
- `id` (string): Unique identifier of the domain (object ID).
- `domain` (string, **required**): The domain name (e.g., "example.com").
- `status` (string): Verification status. One of `pending`, `success`, `failed`.
- `dkim` (boolean): Whether DKIM is set up (true/false).
- `dkimTokens` (array of strings): DKIM selector tokens used for DNS records.
- `mailFromDomain` (string): The custom MAIL FROM domain used for email delivery.
- `mailFromStatus` (string): Status of the MAIL FROM domain (e.g., `pending`, `success`, `failed`).
- `dns` (array of objects): List of DNS records required for setup. Each object has:
- `hostname` (string): The DNS record hostname.
- `value` (string): The value to set for the DNS record.
- `dcUrl` (string): Domain Connect setup URL for automated DNS setup.
### Example Domain Object
```json
{
"id": "5d45dd1ca546d200fe201f84",
"domain": "example.com",
"status": "pending",
"dkim": true,
"dkimTokens": ["xxxxx"],
"mailFromDomain": "out.mail.example.com",
"mailFromStatus": "pending",
"dns": [
{
"hostname": "xxxxx._domainkey.out.mail.example.com",
"value": "xxxxx.dkim.sidemail.net"
},
{
"hostname": "out.mail.example.com",
"value": "xxxxx.mailfrom.sidemail.net"
}
],
"dcUrl": "https://dash.cloudflare.com/domainconnect/v2/d..."
}
```
---
## List all domains
**`GET`** `https://api.sidemail.io/v1/domains`
Returns a list of all sending domains for the authenticated organization.
**Response:**
```json
{
"data": [
{ /* Domain object */ },
...
]
}
```
---
## Create a sending domain
**`POST`** `https://api.sidemail.io/v1/domains`
Begins the sending domain set up process. Requires a domain name in the request body.
- To verify a domain, you must add the required DNS records provided in the `dns` field of the domain object.
- Use the `dcUrl` for automated DNS setup if your provider is Cloudflare.
- The domain status transitions from `pending` → `success` (when verified) or `failed` (if verification fails).
**Request Body:**
```json
{
"domain": "example.com"
}
```
**Response:**
```json
{ /* Domain object */ }
```
---
## Delete a sending domain
**`DELETE`** `https://api.sidemail.io/v1/domains/:id`
Deletes a sending domain. The domain must not be used in any active automations or scheduled emails.
**Response:**
```json
{
"deleted": true
}
```
---
---
# Sidemail API reference — email
Source: https://sidemail.io/docs/api/email/index.md
# Email API methods
### Available API endpoints:
- `POST https://api.sidemail.io/v1/email/send` — Send an email
- `POST https://api.sidemail.io/v1/email/search` — Query emails
- `POST https://api.sidemail.io/v1/email/validate` — Validate an email address
- `GET https://api.sidemail.io/v1/email/{id}` — Retrieve an email
- `DELETE https://api.sidemail.io/v1/email/{id}` — Delete an email
---
## Email Object
An email object represents a single email sent or scheduled.
- `id` (string): Unique identifier of the email.
- `projectId` (string): Identifier of the project associated with the email.
- `automationId` (string, optional): Identifier of the automation associated with the email.
- `messengerId` (string, optional): Identifier of the messenger associated with the email.
- `toAddress` (string): Recipient's email address.
- `toAddress` (string): Recipient's email address.
- `cc` (array, optional): Carbon copy recipients. Each item is an object with `email` and optional `name`.
- `bcc` (array, optional): Blind carbon copy recipients. Each item is an object with `email` and optional `name`.
- `fromName` (string, optional): Sender's display name.
- `fromAddress` (string): Sender's email address.
- `replyToName` (string, optional): Display name for the reply-to address.
- `replyToAddress` (string, optional): Reply-to email address.
- `subject` (string): Subject of the email.
- `templateId` (string, optional): Identifier of the template used.
- `templateName` (string, optional): Name of the template used.
- `templateProps` (object, optional): Key-value pairs for template variables.
- `status` (string): Status of the email ("queued", "delivered", "open", "bounce", "error", "complaint", "suppressed", "scheduled")
- `isOpenTracked` (boolean): Whether open tracking is enabled.
- `createdAt` (string): ISO8601 date string when the email was created.
- `scheduledAt` (string, optional): ISO8601 date string for scheduled delivery.
- `text` (boolean, optional): Indicates if the email has plain text content.
- `htmlReferenceUrl` (string, optional): Opaque short-lived URL to preview the HTML content of the email.
- `events` (array, optional): List of events associated with the email (e.g., open, click).
- `type` (string): Type of the event.
- `time` (string): ISO8601 date string of the event.
- `ipAddress` (string, optional): IP address associated with the event.
- `userAgent` (string, optional): User agent associated with the event.
- `attachments` (array, optional): List of attachments.
- `id` (string): Identifier of the attachment.
- `name` (string): Name of the attachment.
- `size` (number): Size of the attachment in bytes.
- `contentType` (string): MIME type of the attachment.
- `cid` (string, optional): Content-ID used for inline attachments.
- `contentDisposition` (string, optional): Attachment disposition (`attachment` or `inline`).
- `fileUrl` (string, optional): URL to download the attachment.
---
## Send email
`POST https://api.sidemail.io/v1/email/send`
Note: Inside the Sidemail dashboard, you'll find pre-generated, ready-to-copy code examples for multiple languages, including **PHP**, **Ruby**, **Python**, **cURL**, and more. These examples are tailored to your specific email templates, so you can quickly copy, customize, and start sending emails with ease.
```javascript
const configureSidemail = require("sidemail");
const sm = configureSidemail({ apiKey: "replace-with-your-api-key" });
const response = await sm.sendEmail({
toAddress: "user@email.com",
fromAddress: "you@example.com",
fromName: "Your app",
templateName: "Welcome",
});
```
```php
$sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key');
$response = $sm->sendEmail([
'toAddress' => 'user@email.com',
'fromAddress' => 'you@example.com',
'fromName' => 'Your app',
'templateName' => 'Welcome',
]);
```
```python
from sidemail import Sidemail
sm = Sidemail(api_key="replace-with-your-api-key")
resp = sm.send_email(
toAddress="user@email.com",
fromAddress="you@example.com",
fromName="Your app",
templateName="Welcome",
)
```
```ruby
require "sidemail"
sm = Sidemail.new(api_key: "replace-with-your-api-key")
response = sm.send_email(
toAddress: "user@email.com",
fromAddress: "you@example.com",
fromName: "Your app",
templateName: "Welcome"
)
```
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type EmailRequest struct {
ToAddress string `json:"toAddress"`
FromAddress string `json:"fromAddress"`
FromName string `json:"fromName"`
TemplateName string `json:"templateName"`
}
func main() {
apiKey := "replace-with-your-api-key"
url := "https://api.sidemail.io/v1/emails"
email := EmailRequest{
ToAddress: "user@email.com",
FromAddress: "you@example.com",
FromName: "Your app",
TemplateName: "Welcome",
}
jsonData, err := json.Marshal(email)
if err != nil {
fmt.Println("Error encoding data:", err)
return
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == 200 {
// Handle success
fmt.Println("Email sent successfully")
} else {
// Handle error
fmt.Printf("Error: %s\n", string(body))
}
}
```
```bash
curl -X POST https://api.sidemail.io/v1/emails \
-H "Content-Type: application/json" \
-H "Authorization: Bearer replace-with-your-api-key" \
-d '{
"toAddress": "user@email.com",
"fromAddress": "you@example.com",
"fromName": "Your app",
"templateName": "Welcome"
}'
```
## Parameters
**toAddress** `string`\
A valid email address that will receive the email.
- Only ASCII characters are supported.
---
**cc** `string | object | array` `optional`\
Add carbon copy recipients to the email.
- Accepts a single email string, a single object, or an array of strings/objects.
- Object form requires `email` and supports optional `name`.
- Only ASCII characters are supported.
- The total number of recipients across `toAddress`, `cc`, and `bcc` must be 50 or fewer.
Examples:
```javascript
// Single string
{ "cc": "jane@example.com" }
// Array of multiple CCs
{ "cc": ["alice@example.com", "bob@example.com"] }
// Single object with name
{ "cc": { "email": "jane@example.com", "name": "Jane Doe" } }
```
---
**bcc** `string | object | array` `optional`\
Add blind carbon copy recipients to the email.
- Accepts a single email string, a single object, or an array of strings/objects.
- Object form requires `email` and supports optional `name`.
- Only ASCII characters are supported.
- The total number of recipients across `toAddress`, `cc`, and `bcc` must be 50 or fewer.
- BCC recipients are not visible to other recipients in delivered headers.
Examples:
```javascript
// Single string
{ "bcc": "owner@example.com" }
// Array of multiple BCCs
{ "bcc": ["records@example.com", "legal@example.com"] }
// Single object with name
{ "bcc": { "email": "auditor@example.com", "name": "Audit" } }
```
---
**subject** `string`\
An email subject line.
- All UTF-8 characters (emojis are supported).
---
**fromName** `string` `optional`\
Display name (also known as friendly name) that appears before the `fromAddress` email address.
For example: Sidemail or Patrik from Sidemail.
- It has to have at least 1 character and less than 100 characters.
- All UTF-8 characters (emojis are supported).
---
**fromAddress** `string`\
The email address from which you want to send the email.
For example: [info@sidemail.io](mailto:info@sidemail.io).
- Only [verified sending domains](/docs/sending-identities) or pre-generated by Sidemail
- Only ASCII characters are supported.
To define a display name (a name before the actual email address), use `fromName`.
---
**replyToAddress** `string` `optional`\
If you want the recipient of an email to reply to a different email address than `fromAddress`, specify it with `replyToAddress`.
- Only ASCII characters are supported.
---
**replyToName** `string` `optional`\
Use in conjunction with `replyToAddress` to show a friendly name for the `replyToAddress`. Ignored if `replyToAddress` is not specified.
- It has to have at least 1 character and less than 100 characters.
- All UTF-8 characters (emojis are supported).
---
**templateId** `string` `optional`\
A template ID of the template you want to send.
Cannot be used together with `templateName`, `html` or `text` or `markdown`.
---
**templateName** `string` `optional`\
A template name of the template you want to send.
Cannot be used together with `templateId`, `html` or `text` or `markdown`.
---
**templateProps** `object` `optional`\
Pass data to template props here. Use exactly the same key name (whitespace sensitive) as you did in your email template. If a variable has been defined in your email template, but you didn't add it to `templateProps` when making an API request, the key name of the variable will be used as a fallback.
For example: if you defined `{firstName}` template prop inside of the email template you want to send, the `templateProps` parameter should include a key `firstName` with a value that is `string`.
```javascript
{
"firstName": "Patrik"
}
```
Learn more about [dynamic data with template props](/docs/template-props).
---
**markdown** `string` `optional`\
To send markdown content which Sidemail turns into branded transactional email, pass the markdown as a `string` into the `markdown` parameter.
- Cannot be used together with `templateId` or `templateName` or `html` or `text`.
- Must be less than 102 400 characters long (100 kB).
[Learn more about sending markdown content →](/docs/markdown-emails/)
---
**html** `string` `optional`\
To send your own HTML email, pass your HTML as a `string` into the `html` parameter.
- Cannot be used together with `templateId` or `templateName` or `markdown`.
- Must be less than 1 024 000 characters long (1 MB). Note that Gmail cuts off email preview if longer than 104 448 characters (102 kb).
- Due to potential for abuse, new accounts created after January 30, 2023 must be manually approved to send custom HTML emails. Don't hesitate to contact us at [support@sidemail.io](mailto:support@sidemail.io) to get approved with legitimate use cases.
Sidemail does not inline CSS, or process the custom HTML email in any other way. Do any processing in your application before sending if necessary.
[Learn more about sending custom HTML emails →](/docs/custom-html-emails/)
---
**text** `string` `optional`\
To send a plain-text email, pass `string` into the `text` parameter.
- Combine with `html` to create both plain-text and HTML version of your email.
- Cannot be used together with `templateId` or `templateName` or `markdown`.
- Must be less than 102 400 characters long (100 kB).
[Learn more about sending plain-text emails](/docs/plain-text-emails/).
---
**isOpenTracked** `boolean` `optional`\
Whether the email should be open tracked or not.
- Default is `true` — emails are open tracked by default.
---
**scheduledAt** `date` `optional`\
Specify a delayed email delivery by providing a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date in the future.
[Learn more about scheduled email delivery](/docs/scheduled-email-delivery/).
---
**headers** `object` `optional`\
Specify any custom headers of an email. Both key and value must be a `string` type.
Headers example:
```
"headers": { "X-Custom-Id": "12345" }
```
---
**attachments** `array` `optional`
Attach a Base64 encoded file to an email.
- The `attachments` array must contain a valid `attachment` object (described below).
- The `attachments` array can contain multiple `attachment` objects.
- The combined file size limit is 5 MB (which is equal to less than `5242880` characters long). Note that file size is calculated from the Base64 encoded string, which is on average 133% of the original file size.
- Allowed files: `.jpg` `.jpeg` `.pdf` `.csv` `.html` `.png` `.gif` `.json` `.txt` `.docx` `.xlsx` `.pptx`
**attachment** `object` `required`
- **name** `string` `required`
The name of the attached file that will show up to the recipient.
- The file ending must be one of the allowed file types (as described above).
- Must be less than 100 characters long.
- Must contain at least 1 character before a valid file ending.
- The content type is set automatically based on the file ending.
- **content** `string` `required`
The Base64 encoded file.
- Must be valid Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648))
- Must be less than `5242880` characters long (5 MB).
- **cid** `string` `optional`
A Content-ID for referencing the attachment from custom HTML, for example ``.
- Use the ID value without angle brackets.
- Must be less than 255 characters long.
- Must not contain whitespace or angle brackets.
- When `cid` is provided, the attachment is sent inline by default.
- **contentDisposition** `string` `optional`
How the attachment should be presented. Must be either `attachment` or `inline`.
Attachment example:
```javascript
{
"attachments": [
{
"name": "file.txt",
"content": "dmFsaWQgY29udGVudA=="
}
]
}
```
---
### Returns
Returns an object with an email ID and the current status on success.
```javascript
{
"id": "5e858953daf20f3aac50a3da",
"status": "queued"
}
```
When [sending a batch of emails in a single API call](/docs/batch-email-sending), it returns an ordered array of objects with email ID and the current status on success:
```javascript
[
{
id: "5e858953daf20f3aac50a3da",
status: "queued",
},
{
id: "6e858953daf20f3aac50a3da",
status: "queued",
},
];
```
---
## Search emails
`POST https://api.sidemail.io/v1/email/search`
Searches emails based on the provided query and returns found email data. This endpoint is paginated and it returns a maximum of 20 results per page. The email data are returned sorted by creation date, with the most recent emails appearing first.
To list emails without any filter, exclude the `query` from the JSON payload.
JSON payload example:
```javascript
{
"query": {
"toAddress": "john.doe@example.com",
"status": "delivered",
"templateProps": {
"color": "red"
}
}
}
```
### Parameters
- **paginationCursorNext** `string` A cursor for use in pagination. `paginationCursorNext` is an object ID that defines your place in the list. For instance, if you make a list request and receive 20 objects, ending with `obj_foo`, your subsequent call can include `paginationCursorNext=obj_foo` in order to fetch the next page of the list.
- **paginationCursorPrev** `string` A cursor for use in pagination to fetch the previous page of the list.
- **limit** `number` The number of results to return per page. Must be between 1 and 100. Default is 20.
- **query** `object`
- **search** `string`: A text search query to match against email fields.
- **toAddress** `string`: Recipient's email address.
- **fromName** `string`: Sender's display name.
- **fromAddress** `string`: Sender's email address.
- **subject** `string`: Subject of the email.
- **templateName** `string`: Name of the template used.
- **templateId** `string` or `array`: Identifier(s) of the template(s) used.
- **automationId** `string` or `array`: Identifier(s) of the automation(s) associated with the email.
- **messengerId** `string` or `array`: Identifier(s) of the messenger(s) associated with the email.
- **status** `string` or `array`: Status of the email (e.g., "queued", "delivered", "open").
- **scheduledAt** `date`: ISO8601 date string for scheduled delivery.
- **templateProps** `object`: Key-value pairs to match against template variables.
### Returns
A response object with a `data` property that contains an array of up to 20 emails. Each entry in the array is a separate email object. If no more emails are available, the resulting array will be empty.
```javascript
{
hasPrev: true,
hasMore: true,
paginationCursorPrev: "307f1f70bcf86cd799439011",
paginationCursorNext: "407f1f70bcf86cd799439011",
data: [
{
"id": "307f1f70bcf86cd799439011",
"projectId": "507f1f77bcf86cd799439011",
"toAddress": "marry@lightling.com",
"fromAddress": "hey@sidemail.io",
"fromName": "Sidemail",
"subject": "Some email subject",
"templateId": "192f1f77bcf86cd799439011",
"templateName": "Welcome",
"templateProps": [{ "keyName": "name", "value": "Marry" }],
"status": "delivered",
"isOpenTracked": true,
"createdAt": "2019-08-15T13:20:39.160Z",
"attachments": null
},
// Another 19 found emails...
]
}
```
---
## Retrieve email
`GET https://api.sidemail.io/v1/email/{id}`
Retrieves the email data. You need only supply the email ID to the URL.
### Parameters
*No parameters.*
### Returns
Returns an email object if email was found or `null`.
```javascript
{
email: {
"id": "307f1f70bcf86cd799439011",
"projectId": "507f1f77bcf86cd799439011",
"toAddress": "marry@lightling.com",
"fromAddress": "hey@sidemail.io",
"fromName": "Sidemail",
"subject": "Some email subject",
"templateId": "192f1f77bcf86cd799439011",
"templateName": "Welcome",
"templateProps": [{ "keyName": "name", "value": "Marry" }],
"status": "delivered",
"isOpenTracked": true,
"createdAt": "2019-08-15T13:20:39.160Z",
"attachments": [
{
"id": "907f1f77bcf86cd799439011",
"name": "invoice.txt",
"size": 51200
}
]
}
}
```
---
## Delete email
`DELETE https://api.sidemail.io/v1/email/{id}`
Permanently deletes an email. It cannot be undone. Only scheduled emails which are yet to be send can be deleted.
### Parameters
*No parameters.*
### Returns
Returns an object with `deleted` parameter that indicates the outcome of the operation.
```javascript
{
"deleted": true
}
```
---
## Validate an email address
`POST https://api.sidemail.io/v1/email/validate`
Checks the address format, common domain typos, disposable providers, and MX
records. Set `isDeep` to `true` to also ask the destination mail server whether
it accepts the address. No email is sent.
```bash
curl -X POST "https://api.sidemail.io/v1/email/validate" \
-H "Authorization: Bearer replace-with-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"email": "person@example.com",
"isDeep": true
}'
```
### Parameters
- **email** `string`, required: The address to validate. It is trimmed and converted to lowercase.
- **isDeep** `boolean`, optional: Adds the SMTP mailbox check. Defaults to `false`.
### Returns
The endpoint returns `200 OK` when validation ran successfully. An invalid
address is still a successful API request; inspect `valid` in the first item in
`results`. Validators run in order and stop after the first failure.
```json
{
"results": [
{
"email": "person@example.com",
"valid": true,
"validators": {
"regex": { "valid": true },
"typo": { "valid": true },
"disposable": { "valid": true },
"mx": { "valid": true },
"smtp": { "valid": true }
},
"mx": [
{
"exchange": "mx.example.com",
"priority": 10
}
]
}
],
"validationCredits": {
"remaining": 976
}
}
```
Standard validation does not use a deep-validation credit. A deep request uses
one credit only when an SMTP check returns a definitive result. Temporary mail
server failures and cached results do not use a credit.
When no credits remain, the endpoint returns `402` with `limit-exhausted`. A
`409` response with `resource-busy` means another deep check for the same
organization is reserving a credit; retry the request.
---
# API errors
Source: https://sidemail.io/docs/api/errors/index.md
# API errors
Sidemail uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate that there was an issue with the request that was sent. Among other things, this could mean that you did not authenticate correctly, that you are requesting an action that you do not have authorization for, or that your request is malformed. Codes in the `5xx` range indicate a server error on the Sidemail's side.
## Error structure
```javascript
{
"developerMessage": "Not authorized.",
"errorCode": "authentication-failed",
"moreInfo": "https://sidemail.io/docs/authentication"
}
```
---
**developerMessage** `string`\
A brief description of the error. Usually contains instruction on how to resolve the issue.
---
**errorCode** `string`\
An error code. Use this to [gracefully handle errors](https://en.wikipedia.org/wiki/Graceful_exit).
---
**moreInfo** `string`\
A link to an appropriate documentation page.
---
## Complete list of error codes
- `authentication-required`\
You need to authenticate this API request with your API key that you can find in API section in Sidemail's dashboard.
- `authentication-invalid`\
Check the authentication section on how to properly authenticate API calls.
- `authentication-failed`\
Check the authentication section on how to properly authenticate API calls.
- `account-subscription-expired`\
Your Sidemail subscription has expired or is past due.
- `to-address-invalid`\
A valid email email address that will receive the email. Only ASCII characters are supported.
- `from-address-unverified`\
Make sure the `fromAddress` is verified. To manage verified addresses and domains go to your project settings.
- `parameters-invalid`\
Some JSON parameter failed validation.
- `limit-exhausted`: A quota or credit limit for the requested operation has been exhausted. The error message identifies the relevant limit.
- `resource-busy`: Another request is operating on the same limited resource. Retry the request.
- `template-not-found`\
Provided template ID or template name doesn't exist. Both fields are case sensitive and whitespace sensitive.
- `resource-duplicate`
A resource with the provided unique value already exists.
- `account-unverified`\
Your Sidemail account is not verified because you haven't verify your email address.
- `email-sending-paused`\
Email sending was paused because your project has high bounce or complaint rate.
- `account-suspended`\
Your Sidemail account was suspended.
- `json-invalid`\
Invalid JSON body.
- `request-too-large`\
The body of the request is too large for processing.
- `resource-missing`\
The ID provided is not valid. Either the resource does not exist, or an ID for a different resource has been provided.
- `access-denied`\
Returned when the requested feature is not allowed or a limit is exhausted.
- `error-unknown`\
An unknown error occurred.
---
# Inbound API
Source: https://sidemail.io/docs/api/inbound/index.md
# Inbound API methods
Sidemail enables you to receive emails on your verified domains via inbound routes. An inbound route defines which email addresses on your domain should accept incoming mail and how they should respond. When a matching email arrives, Sidemail stores it and optionally fires an `email.received` [webhook](/docs/webhooks/).
### Available API endpoints:
- `GET https://api.sidemail.io/v1/inbound/routes` — List all inbound routes
- `POST https://api.sidemail.io/v1/inbound/routes` — Create an inbound route
- `PATCH https://api.sidemail.io/v1/inbound/routes/{routeId}` — Update an inbound route
- `DELETE https://api.sidemail.io/v1/inbound/routes/{routeId}` — Delete an inbound route
- `GET https://api.sidemail.io/v1/inbound/emails` — List received emails
- `GET https://api.sidemail.io/v1/inbound/emails/{receivedEmailId}` — Retrieve a received email
---
## Inbound Route Object
An inbound route object represents a rule for accepting inbound email on a specific domain and address.
- `id` (string): Unique identifier of the inbound route.
- `domain` (string): The domain to receive email on (e.g., "example.com"). Must be a verified domain or its subdomain.
- `localPart` (string): The local part before the `@` sign. Use `"*"` for a catch-all route that accepts all addresses on the domain.
- `isCatchAll` (boolean): Whether the route is a catch-all (i.e. `localPart` is `"*"`).
- `responseMode` (string): How the mail server responds during the SMTP transaction. One of `"accept"`, `"reject-temp"`, `"reject-perm"`.
- `isEnabled` (boolean): Whether the route is active.
- `createdAt` (string): ISO8601 date string when the route was created.
- `updatedAt` (string): ISO8601 date string when the route was last updated.
### Example Inbound Route Object
```json
{
"id": "67e4f2b6d4b7a63a0f6f2e11",
"domain": "example.com",
"localPart": "*",
"isCatchAll": true,
"responseMode": "accept",
"isEnabled": true,
"createdAt": "2026-03-27T12:00:00.000Z",
"updatedAt": "2026-03-27T12:00:00.000Z"
}
```
---
## Received Email Object
A received email object represents a single inbound email received by Sidemail.
- `id` (string): Unique identifier of the received email.
- `destination` (string): The SMTP envelope recipient address that matched the inbound route.
- `from` (object): Sender information with `email` (string) and `name` (string or null).
- `to` (array): Header "To" recipients. Each item has `email` and `name`.
- `cc` (array): Header "Cc" recipients. Each item has `email` and `name`.
- `replyTo` (array): Header "Reply-To" addresses. Each item has `email` and `name`.
- `subject` (string): Email subject line.
- `text` (string): Plain text body of the email.
- `htmlAvailable` (boolean): Whether the email contains an HTML part.
- `attachments` (array): Attachment metadata. Each item has `name` (string), `contentType` (string), and `size` (number, bytes).
- `headers` (object): Raw email headers.
- `auth` (object): Authentication results (SPF, DKIM, etc.).
- `envelope` (object): SMTP envelope data.
- `inboundRouteId` (string): Identifier of the inbound route that matched.
- `spam` (object): Spam analysis results with `score` (number), `threshold` (number), `isSpam` (boolean), `action` (string), and `symbols` (array of strings).
- `receivedAt` (string): ISO8601 date string when the email was received.
- `previewHtmlUrl` (string or null): Opaque short-lived URL to preview the HTML version. Present only when `htmlAvailable` is true.
- `rawEmailUrl` (string or null): Opaque short-lived signed URL to download the raw email (RFC 822).
### Example Received Email Object
```json
{
"id": "67e4f2b6d4b7a63a0f6f2e11",
"destination": "hi@example.com",
"from": { "email": "john@acme.com", "name": "John" },
"to": [{ "email": "hi@example.com", "name": "Support" }],
"cc": [{ "email": "ops@example.com", "name": null }],
"replyTo": [{ "email": "billing@acme.com", "name": "Billing" }],
"subject": "Invoice Q1",
"text": "Hello, please find invoice attached...",
"htmlAvailable": true,
"attachments": [
{ "name": "invoice-q1.pdf", "contentType": "application/pdf", "size": 84320 }
],
"headers": {},
"auth": {},
"envelope": {},
"inboundRouteId": "67e4f2b6d4b7a63a0f6f2e10",
"spam": {
"score": 2.4,
"threshold": 15,
"isSpam": false,
"action": "accept",
"symbols": ["R_SPF_ALLOW", "R_DKIM_ALLOW", "MIME_GOOD"]
},
"receivedAt": "2026-03-27T12:01:18.921Z",
"previewHtmlUrl": "https://signed-url.example/...",
"rawEmailUrl": "https://signed-url.example/..."
}
```
---
## List all inbound routes
**`GET`** `https://api.sidemail.io/v1/inbound/routes`
Returns a list of all inbound routes in the project.
```bash
curl https://api.sidemail.io/v1/inbound/routes \
-H "Authorization: Bearer replace-with-your-api-key"
```
**Response:**
```json
{
"data": [
{
"id": "67e4f2b6d4b7a63a0f6f2e11",
"domain": "example.com",
"localPart": "*",
"isCatchAll": true,
"responseMode": "accept",
"isEnabled": true,
"createdAt": "2026-03-27T12:00:00.000Z",
"updatedAt": "2026-03-27T12:00:00.000Z"
}
]
}
```
---
## Create an inbound route
**`POST`** `https://api.sidemail.io/v1/inbound/routes`
Creates a new inbound route. The domain must be a verified domain in your project, or a subdomain of one.
```bash
curl -X POST https://api.sidemail.io/v1/inbound/routes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer replace-with-your-api-key" \
-d '{
"domain": "example.com",
"localPart": "*",
"responseMode": "accept",
"isEnabled": true
}'
```
**Parameters:**
- `domain` (string, **required**): The domain to receive email on. Must be a verified domain or its subdomain.
- `localPart` (string, optional, default: `"*"`): The local part of the email address (before `@`). Use `"*"` to create a catch-all route. Maximum 128 characters. Cannot contain `@`.
- `responseMode` (string, optional, default: `"accept"`): How the mail server should respond. One of:
- `"accept"` — Accept and store the email.
- `"reject-temp"` — Temporarily reject (4xx SMTP response). The sending server may retry.
- `"reject-perm"` — Permanently reject (5xx SMTP response).
- `isEnabled` (boolean, optional, default: `true`): Whether the route should be active.
**Response:**
```json
{
"created": {
"id": "67e4f2b6d4b7a63a0f6f2e11",
"domain": "example.com",
"localPart": "*",
"isCatchAll": true,
"responseMode": "accept",
"isEnabled": true,
"createdAt": "2026-03-27T12:00:00.000Z",
"updatedAt": "2026-03-27T12:00:00.000Z"
}
}
```
---
## Update an inbound route
**`PATCH`** `https://api.sidemail.io/v1/inbound/routes/:routeId`
Updates an existing inbound route. You can change `responseMode` and `isEnabled`. At least one field must be provided.
```bash
curl -X PATCH https://api.sidemail.io/v1/inbound/routes/67e4f2b6d4b7a63a0f6f2e11 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer replace-with-your-api-key" \
-d '{
"responseMode": "reject-perm",
"isEnabled": false
}'
```
**Parameters:**
- `responseMode` (string, optional): One of `"accept"`, `"reject-temp"`, `"reject-perm"`.
- `isEnabled` (boolean, optional): Whether the route is active.
**Response:**
```json
{
"updated": {
"id": "67e4f2b6d4b7a63a0f6f2e11",
"domain": "example.com",
"localPart": "*",
"isCatchAll": true,
"responseMode": "reject-perm",
"isEnabled": false,
"createdAt": "2026-03-27T12:00:00.000Z",
"updatedAt": "2026-03-27T12:05:00.000Z"
}
}
```
---
## Delete an inbound route
**`DELETE`** `https://api.sidemail.io/v1/inbound/routes/:routeId`
Deletes an inbound route. Existing received emails are not affected.
```bash
curl -X DELETE https://api.sidemail.io/v1/inbound/routes/67e4f2b6d4b7a63a0f6f2e11 \
-H "Authorization: Bearer replace-with-your-api-key"
```
**Response:**
```json
{
"deleted": true
}
```
---
## List received emails
**`GET`** `https://api.sidemail.io/v1/inbound/emails`
Returns a paginated list of received inbound emails, sorted by most recent first.
```bash
curl "https://api.sidemail.io/v1/inbound/emails?limit=20" \
-H "Authorization: Bearer replace-with-your-api-key"
```
**Parameters:**
- `limit` (number, optional, default: `20`): Number of results to return. Min 1, max 100.
- `paginationCursorNext` (string, optional): Cursor for fetching the next page of results.
- `paginationCursorPrev` (string, optional): Cursor for fetching the previous page of results.
**Response:**
```json
{
"data": [
{
"id": "67e4f2b6d4b7a63a0f6f2e11",
"destination": "hi@example.com",
"from": { "email": "john@acme.com", "name": "John" },
"to": [{ "email": "hi@example.com", "name": "Support" }],
"cc": [],
"replyTo": [],
"subject": "Invoice Q1",
"receivedAt": "2026-03-27T12:01:18.921Z",
"spam": { "score": 2.4 },
"inboundRouteId": "67e4f2b6d4b7a63a0f6f2e10"
}
],
"hasMore": false,
"paginationCursorNext": null,
"paginationCursorPrev": null,
"limit": 20
}
```
The list endpoint returns a summary of each email. To get the full email detail including body, attachments, and spam analysis, use the [retrieve endpoint](#retrieve-a-received-email).
---
## Retrieve a received email
**`GET`** `https://api.sidemail.io/v1/inbound/emails/:receivedEmailId`
Returns the full detail of a single received email, including text body, attachment metadata, spam analysis, and short-lived preview/download URLs.
```bash
curl https://api.sidemail.io/v1/inbound/emails/67e4f2b6d4b7a63a0f6f2e11 \
-H "Authorization: Bearer replace-with-your-api-key"
```
**Response:**
```json
{
"id": "67e4f2b6d4b7a63a0f6f2e11",
"destination": "hi@example.com",
"from": { "email": "john@acme.com", "name": "John" },
"to": [{ "email": "hi@example.com", "name": "Support" }],
"cc": [{ "email": "ops@example.com", "name": null }],
"replyTo": [{ "email": "billing@acme.com", "name": "Billing" }],
"subject": "Invoice Q1",
"text": "Hello, please find invoice attached...",
"htmlAvailable": true,
"attachments": [
{ "name": "invoice-q1.pdf", "contentType": "application/pdf", "size": 84320 }
],
"headers": {},
"auth": {},
"envelope": {},
"inboundRouteId": "67e4f2b6d4b7a63a0f6f2e10",
"spam": {
"score": 2.4,
"threshold": 15,
"isSpam": false,
"action": "accept",
"symbols": ["R_SPF_ALLOW", "R_DKIM_ALLOW", "MIME_GOOD"]
},
"receivedAt": "2026-03-27T12:01:18.921Z",
"previewHtmlUrl": "https://signed-url.example/...",
"rawEmailUrl": "https://signed-url.example/..."
}
```
`previewHtmlUrl` and `rawEmailUrl` are short-lived. Fetch fresh URLs by calling this endpoint again when needed.
To access attachment content, download the raw email via `rawEmailUrl` and parse it with a MIME parser (e.g., `mailparser` for Node.js). The `attachments` array in the response contains metadata only (name, content type, size in bytes).
---
## Webhooks
When an inbound route receives an email, Sidemail fires an `email.received` webhook event. See the [webhooks documentation](/docs/webhooks/) for setup instructions and the full event payload.
---
# Messenger API
Source: https://sidemail.io/docs/api/messenger/index.md
# Messenger API methods (unstable)
Sidemail Messenger API lets you easily send newsletters, campaigns, and product updates to your users — fully automated, personalized, and managed via API. Create and schedule one-time broadcasts, target any segment of contacts.
### Available API endpoints:
- `GET https://api.sidemail.io/v1/messenger` — List all Messenger drafts
- `GET https://api.sidemail.io/v1/messenger/{id}` — Get a Messenger draft
- `POST https://api.sidemail.io/v1/messenger` — Create a Messenger draft
- `PATCH https://api.sidemail.io/v1/messenger/{id}` — Update a Messenger draft
- `DELETE https://api.sidemail.io/v1/messenger/{id}` — Delete a Messenger draft
---
## Messenger Object
A Messenger draft object represents a single message draft.
- `id` (string): Unique identifier of the Messenger draft.
- `subject` (string): Subject line of the Messenger. Defaults to `Untitled draft`.
- `fromName` (string, optional): Name of the sender.
- `fromAddress` (string): Email address of the sender.
- `html` (string, optional): HTML content of the message (used if not using template).
- `richTextValue` (object or null, optional): Rich text content, or null if not set.
- `templateId` (string or null, optional): Template ID to use.
- `templateProps` (object, optional): Template variables. Values accept the same forms as the Email API: strings, number arrays, or arrays of objects used by dynamic template components.
- `recipients` (object): Recipients query object. See [Contacts API](/docs/api/contacts/#query-contacts-unstable).
- `contactId` (string or array of strings, optional): List of contact IDs.
- `groupId` (string or array of strings, optional): List of group IDs.
- `filter` (object, optional): Advanced filter object. Its nested `filter.match` controls how rules inside the filter are combined and defaults to `"all"` ([see Contacts API](/docs/api/contacts/#query-contacts-unstable)).
- `match` ("all" or "any", optional): Controls how the top-level `contactId`, `groupId`, and `filter` selectors are combined. Defaults to `"any"` when creating a draft.
- `isSubscribed` (boolean, optional): Only accepts `true`, defaults to `true`.
- `scheduledAt` (string or null, optional): ISO8601 date string for scheduled delivery, or null.
- `isTimezoneScheduled` (boolean): Whether to schedule by recipient timezone. Defaults to `false`.
- `contentType` (string): One of `"with-layout"`, `"no-layout"`, or `"template"`. Defaults to `"with-layout"`.
- `status` (string): One of `"draft"`, `"queued"`, `"scheduled"`, `"processing"`, `"DONE"`. Only `"draft"` and `"queued"` statuses are editable. Defaults to `"draft"`.
- `createdAt` (string): ISO8601 date string when the Messenger was created.
- `stats` (object, not editable): Stats object (see below). Only defined after a draft is queued.
- `totalRecipients` (number): Total recipients.
- `unsubscribedTotal` (number): Unsubscribed recipients.
- `deliveriesTotal` (number): Delivered messages.
- `complaintsTotal` (number): Complaints received.
- `bouncesTotal` (number): Bounced emails.
- `uniqueOpensTotal` (number): Unique opens.
### Recipient matching
- `recipients.match`: Combines `contactId`, `groupId`, and `filter`. Default is `"any"` when creating a draft.
- `recipients.filter.match`: Combines rules inside `filter.rules`. Default is `"all"`.
- `isSubscribed`: Always applied as an additional AND condition.
Messenger draft updates are partial. Recipient fields omitted from an update retain their existing values. Set unused `contactId`, `groupId`, or `filter` fields to `null` to remove them.
Multiple IDs within `groupId` match contacts belonging to any of the supplied groups. A draft without `contactId`, `groupId`, or `filter` targets all subscribed contacts.
### Check the recipient count before queueing
Use the [Contacts query endpoint](/docs/api/contacts/#query-contacts-unstable) with the same recipient query before queueing a Messenger draft. The response includes the number of matching contacts in `totalCount`.
Example request:
```json
{
"isSubscribed": true,
"groupId": ["5d45dd1ca546d200fe201f83"],
"match": "all",
"filter": {
"match": "all",
"rules": [
{
"field": "customProps.plan",
"operator": "includes",
"value": "premium"
}
]
},
"limit": 1
}
```
Example response:
```json
{
"data": [
/* Array of contact objects */
],
"pageCount": 42,
"totalCount": 42
}
```
Provide `match` explicitly when checking the recipient count. The Contacts query endpoint defaults `match` to `"all"`, while Messenger draft creation defaults it to `"any"`.
Recipient criteria are evaluated again during delivery. Contact properties and group membership changes made after queueing may affect the final recipients.
### Example data:
```json
{
"id": "5d45dd1ca546d200fe201f84",
"subject": "Welcome to Sidemail!",
"fromName": "Sidemail Team",
"fromAddress": "team@yourdomain.com",
"html": "
Hello {{contact.name}}, welcome!
", "richTextValue": null, "templateId": null, "templateProps": null, "recipients": { "contactId": null, "groupId": ["5d45dd1ca546d200fe201f83"], "filter": { "match": "all", "rules": [ { "field": "customProps.plan", "operator": "includes", "value": "premium" }, { "field": "createdAt", "operator": "gte", "value": "2025-01-01T00:00:00.000Z" } ] }, "match": "all", "isSubscribed": true }, "scheduledAt": "2025-05-10T09:00:00.000Z", "isTimezoneScheduled": false, "contentType": "with-layout", "status": "DONE", "createdAt": "2025-05-04T12:00:00.000Z", "stats": { "totalRecipients": 1500, "unsubscribedTotal": 5, "deliveriesTotal": 1490, "complaintsTotal": 1, "bouncesTotal": 4, "uniqueOpensTotal": 900 } } ``` --- ## Get a Messenger draft `GET https://api.sidemail.io/v1/messenger/:id` ### Returns Example response: ```json { "data": {/* Messenger object */ } } ``` --- ## List Messenger drafts `GET https://api.sidemail.io/v1/messenger` ### Query string parameters - `offset` (number, optional): Number of drafts to skip (pagination). Default: `0`. - `limit` (number, optional): Number of drafts per page. Default: `20`. ### Returns Example response: ```json { "data": [/* ... */], "pageCount": 5, "totalCount": 42 } ``` --- ## Create a Messenger draft `POST https://api.sidemail.io/v1/messenger` The request body must be a valid Messenger draft object ([see above](#messenger-object)) Example request: ```json { "subject": "Welcome!", "fromAddress": "team@yourdomain.com", "templateId": "5d45dd1ca546d200fe201f83", "templateProps": { "firstName": "Patrik", "metrics": [12, 19, 7], "items": [ { "name": "Pro plan", "links": [{ "label": "Open dashboard", "url": "https://example.com" }] } ] }, "recipients": { "groupId": ["5d45dd1ca546d200fe201f83"], "match": "all", "filter": { "match": "all", "rules": [ { "field": "customProps.plan", "operator": "includes", "value": "premium" } ] } }, "scheduledAt": "2025-05-04T12:00:00.000Z" } ``` ### Returns Returns the created Messenger draft object ([see above](#messenger-object)). --- ## Update a Messenger draft `PATCH https://api.sidemail.io/v1/messenger/:id` The request body must be a valid Messenger draft object ([see above](#messenger-object)). Partial updates are supported. Recipient fields omitted from an update retain their existing saved values. When changing recipients, explicitly provide top-level `recipients.match` and set unused selectors to `null`. Example request: ```json { "subject": "Updated subject", "scheduledAt": "2025-05-05T12:00:00.000Z" } ``` Example to queue a draft for delivery: ```json { "status": "queued" } ``` Setting the status to `"queued"` begins the delivery process without an additional API confirmation prompt. Verify the saved recipient settings and expected recipient count before queueing. ### Returns Returns status code 200 on success and an empty JSON object: ```json {} ``` --- ## Delete a Messenger draft `DELETE https://api.sidemail.io/v1/messenger/:id` Permanently deletes a Messenger draft. It cannot be undone. ### Returns Example response: ```json { "deleted": true } ``` --- # Linked projects Source: https://sidemail.io/docs/api/projects/index.md # Linked projects API methods Linked projects enable white-label sending through Sidemail. They are useful when you need a separate sending identity for each customer, tenant, or user in your application. Each linked project has access to the templates of the parent project (via template ID, not template names). We recommend creating one linked project per user or tenant for optimal white-label functionality. Each linked project can have its own bounce and complaint reputation limits. Organization-level health limits still apply globally, so one linked project can be paused without bypassing the parent organization's overall sending health. ### Available API endpoints: - `POST https://api.sidemail.io/v1/project` - `GET https://api.sidemail.io/v1/project` - `PATCH https://api.sidemail.io/v1/project` - `DELETE https://api.sidemail.io/v1/project` ## Using linked projects (example) This outlines how a linked project is designed for white-label email delivery: 1. Create a linked project for each user of your application. 2. Customize the email template design by updating the linked project. 3. Send emails through linked projects using their respective API keys. ```js import { configureSidemail } from "@sidemail/sidemail"; // 1) Configure with your main project API key const sidemail = configureSidemail({ apiKey: "replace-with-main-project-api-key" }); // 2) Create a linked project and store its apiKey const createRes = await sidemail.project.create({ name: "Customer X linked project" }); const linkedApiKey = createRes.apiKey; // save for future requests // 3) Update design within the linked project context const sidemailLinked = configureSidemail({ apiKey: linkedApiKey }); await sidemailLinked.project.update({ name: "New name", emailTemplateDesign: { logo: { sizeWidth: 50, href: "https://example.com", file: "PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==", }, font: { name: "Acme" }, colors: { highlight: "#0000FF", isDarkModeEnabled: true }, unsubscribeText: "Darse de baja", footerTextTransactional: "You're receiving these emails because you registered for Acme Inc.", }, }); // 4) Get the linked project const getRes = await sidemailLinked.project.get(); // 5) Send an email using a parent templateId await sidemailLinked.sendEmail({ toAddress: "user@email.com", fromAddress: "you@example.com", fromName: "Your app", templateId: "65dc4595af2e7a530209b414", }); ``` ```ruby require "sidemail" # 1) Configure with your main project API key sm = Sidemail.new(api_key: "replace-with-main-project-api-key") # 2) Create a linked project and store its apiKey create_res = sm.project.create(name: "Customer X linked project") linked_api_key = create_res.api_key # save for future requests # 3) Update design within the linked project context sm_linked = Sidemail.new(api_key: linked_api_key) sm_linked.project.update( name: "New name", emailTemplateDesign: { logo: { sizeWidth: 50, href: "https://example.com", file: "PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==", }, font: { name: "Acme" }, colors: { highlight: "#0000FF", isDarkModeEnabled: true }, unsubscribeText: "Darse de baja", footerTextTransactional: "You're receiving these emails because you registered for Acme Inc.", } ) # 4) Get the linked project get_res = sm_linked.project.get # 5) Send an email using a parent templateId sm_linked.send_email( toAddress: "user@email.com", fromAddress: "you@example.com", fromName: "Your app", templateId: "65dc4595af2e7a530209b414" ) ``` ```php project->create([ 'name' => 'Customer X linked project', ]); $linkedApiKey = $createRes->apiKey; // save for future requests // 3) Update design within the linked project context $sidemailLinked = new Sidemail(apiKey: $linkedApiKey); $sidemailLinked->project->update([ 'name' => 'New name', 'emailTemplateDesign' => [ 'logo' => [ 'sizeWidth' => 50, 'href' => 'https://example.com', 'file' => 'PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==', ], 'font' => ['name' => 'Acme'], 'colors' => ['highlight' => '#0000FF', 'isDarkModeEnabled' => true], 'unsubscribeText' => 'Darse de baja', 'footerTextTransactional' => "You're receiving these emails because you registered for Acme Inc.", ], ]); // 4) Get the linked project $getRes = $sidemailLinked->project->get(); // 5) Send an email using a parent templateId $sidemailLinked->sendEmail([ 'toAddress' => 'user@email.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateId' => '65dc4595af2e7a530209b414', ]); ``` ```python from sidemail import Sidemail # 1) Configure with your main project API key sm = Sidemail(api_key="replace-with-main-project-api-key") # 2) Create a linked project and store its apiKey project = sm.project.create(name="Customer X linked project") linked_api_key = project.apiKey # save for future requests # 3) Update design within the linked project context sm_linked = Sidemail(api_key=linked_api_key) sm_linked.project.update( name="New name", emailTemplateDesign={ "logo": { "sizeWidth": 50, "href": "https://example.com", "file": "PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==", }, "font": {"name": "Acme"}, "colors": {"highlight": "#0000FF", "isDarkModeEnabled": True}, "unsubscribeText": "Darse de baja", "footerTextTransactional": "You're receiving these emails because you registered for Acme Inc.", }, ) # 4) Get the linked project get_res = sm_linked.project.get() # 5) Send an email using a parent templateId sm_linked.send_email( toAddress="user@email.com", fromAddress="you@example.com", fromName="Your app", templateId="65dc4595af2e7a530209b414", ) ``` ## Create a linked project A linked project is automatically associated with a regular project using the provided API key in the request. To personalize the email template design, make a subsequent update API request. Linked projects will be visible within the parent project on the API page in your Sidemail dashboard. It's crucial to save the `apiKey` provided in the response, as it is required for subsequent API requests related to this linked project. `POST https://api.sidemail.io/v1/project` Example: ```javascript { "name": "Lorem ipsum", "bounceRateLimit": 7, "complaintRateLimit": 0.1 } ``` ```js import { configureSidemail } from "@sidemail/sidemail"; const sidemail = configureSidemail({ apiKey: "replace-with-main-project-api-key" }); const res = await sidemail.project.create({ name: "Lorem ipsum", bounceRateLimit: 7, complaintRateLimit: 0.1, }); // Store res.apiKey for future calls scoped to this linked project ``` ```php project->create([ 'name' => 'Lorem ipsum', 'bounceRateLimit' => 7, 'complaintRateLimit' => 0.1, ]); // Important! Save $res->apiKey for later use ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-main-project-api-key") project = sm.project.create( name="Lorem ipsum", bounceRateLimit=7, complaintRateLimit=0.1, ) # Important! Save project.apiKey for later use ``` ### Parameters **name** `string`\ Name of the linked project as displayed in the Sidemail dashboard. --- **bounceRateLimit** `number` Optional maximum adjusted bounce rate allowed for this linked project, expressed as a percentage. For example, `7` means 7%. Minimum `0.1`, maximum `10`. If omitted, Sidemail uses the account default. --- **complaintRateLimit** `number` Optional maximum adjusted complaint rate allowed for this linked project, expressed as a percentage. For example, `0.1` means 0.1%. Minimum `0.001`, maximum is your account's complaint-rate limit. If omitted, Sidemail uses the account default. --- ### Returns ```javascript { "created": { "id": "65d75fea9cca1727dc110e1b", "apiKey": "TcL6gLeiok8KiQf1ggqkfiA6n888w4r4i0cSqeEM", "bounceRateLimit": 7, "complaintRateLimit": 0.1 } } ``` **id** `string`\ Unique identifier of the linked project. --- **apiKey** `string`\ Store this value for subsequent API requests related to this linked project (provided only once). --- **bounceRateLimit** `number` The configured bounce rate limit for this linked project. --- **complaintRateLimit** `number` The configured complaint rate limit for this linked project. ## Update a linked project Updates a linked project based on the provided API key in the `Authorization` header. `PATCH https://api.sidemail.io/v1/project` Example: ```javascript { "name": "New name", "bounceRateLimit": 7, "complaintRateLimit": 0.1, "emailTemplateDesign": { "logo": { "sizeWidth": 50, "href": "https://example.com", "file": "PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==" }, "font": { "name": "Acme" }, "colors": { "highlight": "#0000FF", "isDarkModeEnabled": true }, "unsubscribeText": "Darse de baja", "footerTextTransactional": "You're receiving these emails because you registered for Acme Inc." } } ``` ```js import { configureSidemail } from "@sidemail/sidemail"; // Use the linked project's apiKey for updates const sidemail = configureSidemail({ apiKey: "replace-with-linked-project-api-key" }); await sidemail.project.update({ name: "New name", bounceRateLimit: 7, complaintRateLimit: 0.1, emailTemplateDesign: { logo: { sizeWidth: 50, href: "https://example.com", file: "...base64..." }, font: { name: "Acme" }, colors: { highlight: "#0000FF", isDarkModeEnabled: true }, unsubscribeText: "Darse de baja", footerTextTransactional: "You're receiving these emails because you registered for Acme Inc.", }, }); ``` ```php project->update([ 'name' => 'New name', 'bounceRateLimit' => 7, 'complaintRateLimit' => 0.1, 'emailTemplateDesign' => [ 'logo' => [ 'sizeWidth' => 50, 'href' => 'https://example.com', 'file' => '...base64...', ], 'font' => ['name' => 'Acme'], 'colors' => ['highlight' => '#0000FF', 'isDarkModeEnabled' => true], 'unsubscribeText' => 'Darse de baja', 'footerTextTransactional' => "You're receiving these emails because you registered for Acme Inc.", ], ]); ``` ```python from sidemail import Sidemail # Use the linked project's apiKey for updates sm = Sidemail(api_key="replace-with-linked-project-api-key") sm.project.update( name="New name", bounceRateLimit=7, complaintRateLimit=0.1, emailTemplateDesign={ "logo": {"sizeWidth": 50, "href": "https://example.com", "file": "...base64..."}, "font": {"name": "Acme"}, "colors": {"highlight": "#0000FF", "isDarkModeEnabled": True}, "unsubscribeText": "Darse de baja", "footerTextTransactional": "You're receiving these emails because you registered for Acme Inc.", }, ) ``` ### Parameters **name** `string`\ The name of the linked project for your reference in the Sidemail dashboard. --- **bounceRateLimit** `number` Maximum adjusted bounce rate allowed for this linked project, expressed as a percentage. For example, `7` means 7%. Minimum `0.1`, maximum `10`. --- **complaintRateLimit** `number` Maximum adjusted complaint rate allowed for this linked project, expressed as a percentage. For example, `0.1` means 0.1%. Minimum `0.001`, maximum is your account's complaint-rate limit. --- **isSendingPaused** `boolean` Set to `true` to pause sending for this linked project. Set to `false` to re-enable linked-project sending. Only linked projects can be paused or re-enabled with this field. --- **emailTemplateDesign** `object`\ The design of the email template, with its properties described below. --- ### Email template design object **font** `object`\ The primary font for the email template. Font object properties are described below. --- **fontFallback** `object`\ Fallback font for email templates on clients that don't support custom fonts. Font fallback object properties are described below. --- **colors** `object`\ Color scheme for the email template. Colors object properties are described below. --- **logo** `object`\ The logo of the email design. Logo object properties are described below. --- **unsubscribeText** `string`\ The text of the unsubscribe link displayed at the bottom of the email template. --- **unsubscribePageTitle** `string`\ The title shown on the unsubscribe page. --- **unsubscribePageDescription** `string`\ The description shown on the unsubscribe page. --- **footerTextPromotional** `string`\ The footer text for promotional emails shown at the bottom of the email template. Some markdown formatting is supported: hyperlink, bold, italics, strikethrough. Variables are supported via `{variable}` syntax. --- **footerTextTransactional** `string`\ The footer text for transactional emails shown at the bottom of the email template. Some markdown formatting is supported: hyperlink, bold, italics, strikethrough. Variables are supported via `{variable}` syntax. --- ### Font object **name** `string`\ The name of a specific font. Supported fonts (the name must match exactly): ``` sans-serif, serif, monospace, ABeeZee, Abel, Abhaya Libre, Abril Fatface, Aclonica, Acme, Actor, Adamina, Advent Pro, Aguafina Script, Akronim, Aladin, Alata, Alatsi, Aldrich, Alef, Alegreya, Alegreya SC, Alegreya Sans, Alegreya Sans SC, Aleo, Alex Brush, Alfa Slab One, Alice, Alike, Alike Angular, Allan, Allerta, Allerta Stencil, Allura, Almarai, Almendra, Almendra Display, Almendra SC, Amarante, Amaranth, Amatic SC, Amethysta, Amiko, Amiri, Amita, Anaheim, Andada, Andika, Angkor, Annie Use Your Telescope, Anonymous Pro, Antic, Antic Didone, Antic Slab, Anton, Arapey, Arbutus, Arbutus Slab, Architects Daughter, Archivo, Archivo Black, Archivo Narrow, Aref Ruqaa, Arima Madurai, Arimo, Arizonia, Armata, Arsenal, Artifika, Arvo, Arya, Asap, Asap Condensed, Asar, Asset, Assistant, Astloch, Asul, Athiti, Atma, Atomic Age, Aubrey, Audiowide, Autour One, Average, Average Sans, Averia Gruesa Libre, Averia Libre, Averia Sans Libre, Averia Serif Libre, B612, B612 Mono, Bad Script, Bahiana, Bahianita, Bai Jamjuree, Baloo 2, Baloo Bhai 2, Baloo Bhaina 2, Baloo Chettan 2, Baloo Da 2, Baloo Paaji 2, Baloo Tamma 2, Baloo Tammudu 2, Baloo Thambi 2, Balthazar, Bangers, Barlow, Barlow Condensed, Barlow Semi Condensed, Barriecito, Barrio, Basic, Baskervville, Battambang, Baumans, Bayon, Be Vietnam, Bebas Neue, Belgrano, Bellefair, Belleza, Bellota, Bellota Text, BenchNine, Bentham, Berkshire Swash, Beth Ellen, Bevan, Big Shoulders Display, Big Shoulders Text, Bigelow Rules, Bigshot One, Bilbo, Bilbo Swash Caps, BioRhyme, BioRhyme Expanded, Biryani, Bitter, Black And White Picture, Black Han Sans, Black Ops One, Blinker, Bokor, Bonbon, Boogaloo, Bowlby One, Bowlby One SC, Brawler, Bree Serif, Bubblegum Sans, Bubbler One, Buda, Buenard, Bungee, Bungee Hairline, Bungee Inline, Bungee Outline, Bungee Shade, Butcherman, Butterfly Kids, Cabin, Cabin Condensed, Cabin Sketch, Caesar Dressing, Cagliostro, Cairo, Caladea, Calistoga, Calligraffitti, Cambay, Cambo, Candal, Cantarell, Cantata One, Cantora One, Capriola, Cardo, Carme, Carrois Gothic, Carrois Gothic SC, Carter One, Catamaran, Caudex, Caveat, Caveat Brush, Cedarville Cursive, Ceviche One, Chakra Petch, Changa, Changa One, Chango, Charm, Charmonman, Chathura, Chau Philomene One, Chela One, Chelsea Market, Chenla, Cherry Cream Soda, Cherry Swash, Chewy, Chicle, Chilanka, Chivo, Chonburi, Cinzel, Cinzel Decorative, Clicker Script, Coda, Coda Caption, Codystar, Coiny, Combo, Comfortaa, Comic Neue, Coming Soon, Concert One, Condiment, Content, Contrail One, Convergence, Cookie, Copse, Corben, Cormorant, Cormorant Garamond, Cormorant Infant, Cormorant SC, Cormorant Unicase, Cormorant Upright, Courgette, Courier Prime, Cousine, Coustard, Covered By Your Grace, Crafty Girls, Creepster, Crete Round, Crimson Pro, Crimson Text, Croissant One, Crushed, Cuprum, Cute Font, Cutive, Cutive Mono, DM Sans, DM Serif Display, DM Serif Text, Damion, Dancing Script, Dangrek, Darker Grotesque, David Libre, Dawning of a New Day, Days One, Dekko, Delius, Delius Swash Caps, Delius Unicase, Della Respira, Denk One, Devonshire, Dhurjati, Didact Gothic, Diplomata, Diplomata SC, Do Hyeon, Dokdo, Domine, Donegal One, Doppio One, Dorsa, Dosis, Dr Sugiyama, Duru Sans, Dynalight, EB Garamond, Eagle Lake, East Sea Dokdo, Eater, Economica, Eczar, El Messiri, Electrolize, Elsie, Elsie Swash Caps, Emblema One, Emilys Candy, Encode Sans, Encode Sans Condensed, Encode Sans Expanded, Encode Sans Semi Condensed, Encode Sans Semi Expanded, Engagement, Englebert, Enriqueta, Erica One, Esteban, Euphoria Script, Ewert, Exo, Exo 2, Expletus Sans, Fahkwang, Fanwood Text, Farro, Farsan, Fascinate, Fascinate Inline, Faster One, Fasthand, Fauna One, Faustina, Federant, Federo, Felipa, Fenix, Finger Paint, Fira Code, Fira Mono, Fira Sans, Fira Sans Condensed, Fira Sans Extra Condensed, Fjalla One, Fjord One, Flamenco, Flavors, Fondamento, Fontdiner Swanky, Forum, Francois One, Frank Ruhl Libre, Freckle Face, Fredericka the Great, Fredoka One, Freehand, Fresca, Frijole, Fruktur, Fugaz One, GFS Didot, GFS Neohellenic, Gabriela, Gaegu, Gafata, Galada, Galdeano, Galindo, Gamja Flower, Gayathri, Gelasio, Gentium Basic, Gentium Book Basic, Geo, Geostar, Geostar Fill, Germania One, Gidugu, Gilda Display, Girassol, Give You Glory, Glass Antiqua, Glegoo, Gloria Hallelujah, Goblin One, Gochi Hand, Gorditas, Gothic A1, Gotu, Goudy Bookletter 1911, Graduate, Grand Hotel, Gravitas One, Great Vibes, Grenze, Griffy, Gruppo, Gudea, Gugi, Gupter, Gurajada, Habibi, Halant, Hammersmith One, Hanalei, Hanalei Fill, Handlee, Hanuman, Happy Monkey, Harmattan, Headland One, Heebo, Henny Penny, Hepta Slab, Herr Von Muellerhoff, Hi Melody, Hind, Hind Guntur, Hind Madurai, Hind Siliguri, Hind Vadodara, Holtwood One SC, Homemade Apple, Homenaje, IBM Plex Mono, IBM Plex Sans, IBM Plex Sans Condensed, IBM Plex Serif, IM Fell DW Pica, IM Fell DW Pica SC, IM Fell Double Pica, IM Fell Double Pica SC, IM Fell English, IM Fell English SC, IM Fell French Canon, IM Fell French Canon SC, IM Fell Great Primer, IM Fell Great Primer SC, Ibarra Real Nova, Iceberg, Iceland, Imprima, Inconsolata, Inder, Indie Flower, Inika, Inknut Antiqua, Inria Sans, Inria Serif, Inter, Irish Grover, Istok Web, Italiana, Italianno, Itim, Jacques Francois, Jacques Francois Shadow, Jaldi, Jim Nightshade, Jockey One, Jolly Lodger, Jomhuria, Jomolhari, Josefin Sans, Josefin Slab, Jost, Joti One, Jua, Judson, Julee, Julius Sans One, Junge, Jura, Just Another Hand, Just Me Again Down Here, K2D, Kadwa, Kalam, Kameron, Kanit, Kantumruy, Karla, Karma, Katibeh, Kaushan Script, Kavivanar, Kavoon, Kdam Thmor, Keania One, Kelly Slab, Kenia, Khand, Khmer, Khula, Kirang Haerang, Kite One, Knewave, KoHo, Kodchasan, Kosugi, Kosugi Maru, Kotta One, Koulen, Kranky, Kreon, Kristi, Krona One, Krub, Kulim Park, Kumar One, Kumar One Outline, Kurale, La Belle Aurore, Lacquer, Laila, Lakki Reddy, Lalezar, Lancelot, Lateef, Lato, League Script, Leckerli One, Ledger, Lekton, Lemon, Lemonada, Lexend Deca, Lexend Exa, Lexend Giga, Lexend Mega, Lexend Peta, Lexend Tera, Lexend Zetta, Libre Barcode 128, Libre Barcode 128 Text, Libre Barcode 39, Libre Barcode 39 Extended, Libre Barcode 39 Extended Text, Libre Barcode 39 Text, Libre Baskerville, Libre Caslon Display, Libre Caslon Text, Libre Franklin, Life Savers, Lilita One, Lily Script One, Limelight, Linden Hill, Literata, Liu Jian Mao Cao, Livvic, Lobster, Lobster Two, Londrina Outline, Londrina Shadow, Londrina Sketch, Londrina Solid, Long Cang, Lora, Love Ya Like A Sister, Loved by the King, Lovers Quarrel, Luckiest Guy, Lusitana, Lustria, M PLUS 1p, M PLUS Rounded 1c, Ma Shan Zheng, Macondo, Macondo Swash Caps, Mada, Magra, Maiden Orange, Maitree, Major Mono Display, Mako, Mali, Mallanna, Mandali, Manjari, Manrope, Mansalva, Manuale, Marcellus, Marcellus SC, Marck Script, Margarine, Markazi Text, Marko One, Marmelad, Martel, Martel Sans, Marvel, Mate, Mate SC, Maven Pro, McLaren, Meddon, MedievalSharp, Medula One, Meera Inimai, Megrim, Meie Script, Merienda, Merienda One, Merriweather, Merriweather Sans, Metal, Metal Mania, Metamorphous, Metrophobic, Michroma, Milonga, Miltonian, Miltonian Tattoo, Mina, Miniver, Miriam Libre, Mirza, Miss Fajardose, Mitr, Modak, Modern Antiqua, Mogra, Molengo, Molle, Monda, Monofett, Monoton, Monsieur La Doulaise, Montaga, Montez, Montserrat, Montserrat Alternates, Montserrat Subrayada, Moul, Moulpali, Mountains of Christmas, Mouse Memoirs, Mr Bedfort, Mr Dafoe, Mr De Haviland, Mrs Saint Delafield, Mrs Sheppards, Mukta, Mukta Mahee, Mukta Malar, Mukta Vaani, Muli, Mystery Quest, NTR, Nanum Brush Script, Nanum Gothic, Nanum Gothic Coding, Nanum Myeongjo, Nanum Pen Script, Neucha, Neuton, New Rocker, News Cycle, Niconne, Niramit, Nixie One, Nobile, Nokora, Norican, Nosifer, Notable, Nothing You Could Do, Noticia Text, Noto Sans, Noto Sans HK, Noto Sans JP, Noto Sans KR, Noto Sans SC, Noto Sans TC, Noto Serif, Noto Serif JP, Noto Serif KR, Noto Serif SC, Noto Serif TC, Nova Cut, Nova Flat, Nova Mono, Nova Oval, Nova Round, Nova Script, Nova Slim, Nova Square, Numans, Nunito, Nunito Sans, Odibee Sans, Odor Mean Chey, Offside, Old Standard TT, Oldenburg, Oleo Script, Oleo Script Swash Caps, Open Sans, Open Sans Condensed, Oranienbaum, Orbitron, Oregano, Orienta, Original Surfer, Oswald, Over the Rainbow, Overlock, Overlock SC, Overpass, Overpass Mono, Ovo, Oxanium, Oxygen, Oxygen Mono, PT Mono, PT Sans, PT Sans Caption, PT Sans Narrow, PT Serif, PT Serif Caption, Pacifico, Padauk, Palanquin, Palanquin Dark, Pangolin, Paprika, Parisienne, Passero One, Passion One, Pathway Gothic One, Patrick Hand, Patrick Hand SC, Pattaya, Patua One, Pavanam, Paytone One, Peddana, Peralta, Permanent Marker, Petit Formal Script, Petrona, Philosopher, Piedra, Pinyon Script, Pirata One, Plaster, Play, Playball, Playfair Display, Playfair Display SC, Podkova, Poiret One, Poller One, Poly, Pompiere, Pontano Sans, Poor Story, Poppins, Port Lligat Sans, Port Lligat Slab, Pragati Narrow, Prata, Preahvihear, Press Start 2P, Pridi, Princess Sofia, Prociono, Prompt, Prosto One, Proza Libre, Public Sans, Puritan, Purple Purse, Quando, Quantico, Quattrocento, Quattrocento Sans, Questrial, Quicksand, Quintessential, Qwigley, Racing Sans One, Radley, Rajdhani, Rakkas, Raleway, Raleway Dots, Ramabhadra, Ramaraja, Rambla, Rammetto One, Ranchers, Rancho, Ranga, Rasa, Rationale, Ravi Prakash, Red Hat Display, Red Hat Text, Redressed, Reem Kufi, Reenie Beanie, Revalia, Rhodium Libre, Ribeye, Ribeye Marrow, Righteous, Risque, Roboto, Roboto Condensed, Roboto Mono, Roboto Slab, Rochester, Rock Salt, Rokkitt, Romanesco, Ropa Sans, Rosario, Rosarivo, Rouge Script, Rozha One, Rubik, Rubik Mono One, Ruda, Rufina, Ruge Boogie, Ruluko, Rum Raisin, Ruslan Display, Russo One, Ruthie, Rye, Sacramento, Sahitya, Sail, Saira, Saira Condensed, Saira Extra Condensed, Saira Semi Condensed, Saira Stencil One, Salsa, Sanchez, Sancreek, Sansita, Sarabun, Sarala, Sarina, Sarpanch, Satisfy, Sawarabi Gothic, Sawarabi Mincho, Scada, Scheherazade, Schoolbell, Scope One, Seaweed Script, Secular One, Sedgwick Ave, Sedgwick Ave Display, Sen, Sevillana, Seymour One, Shadows Into Light, Shadows Into Light Two, Shanti, Share, Share Tech, Share Tech Mono, Shojumaru, Short Stack, Shrikhand, Siemreap, Sigmar One, Signika, Signika Negative, Simonetta, Single Day, Sintony, Sirin Stencil, Six Caps, Skranji, Slabo 13px, Slabo 27px, Slackey, Smokum, Smythe, Sniglet, Snippet, Snowburst One, Sofadi One, Sofia, Solway, Song Myung, Sonsie One, Sorts Mill Goudy, Source Code Pro, Source Sans Pro, Source Serif Pro, Space Mono, Spartan, Special Elite, Spectral, Spectral SC, Spicy Rice, Spinnaker, Spirax, Squada One, Sree Krushnadevaraya, Sriracha, Srisakdi, Staatliches, Stalemate, Stalinist One, Stardos Stencil, Stint Ultra Condensed, Stint Ultra Expanded, Stoke, Strait, Stylish, Sue Ellen Francisco, Suez One, Sulphur Point, Sumana, Sunflower, Sunshiney, Supermercado One, Sura, Suranna, Suravaram, Suwannaphum, Swanky and Moo Moo, Syncopate, Tajawal, Tangerine, Taprom, Tauri, Taviraj, Teko, Telex, Tenali Ramakrishna, Tenor Sans, Text Me One, Thasadith, The Girl Next Door, Tienne, Tillana, Timmana, Tinos, Titan One, Titillium Web, Tomorrow, Trade Winds, Trirong, Trocchi, Trochut, Trykker, Tulpen One, Turret Road, Ubuntu, Ubuntu Condensed, Ubuntu Mono, Ultra, Uncial Antiqua, Underdog, Unica One, UnifrakturCook, UnifrakturMaguntia, Unkempt, Unlock, Unna, VT323, Vampiro One, Varela, Varela Round, Vast Shadow, Vesper Libre, Viaoda Libre, Vibes, Vibur, Vidaloka, Viga, Voces, Volkhov, Vollkorn, Vollkorn SC, Voltaire, Waiting for the Sunrise, Wallpoet, Walter Turncoat, Warnes, Wellfleet, Wendy One, Wire One, Work Sans, Yanone Kaffeesatz, Yantramanav, Yatra One, Yellowtail, Yeon Sung, Yeseva One, Yesteryear, Yrsa, ZCOOL KuaiLe, ZCOOL QingKe HuangYou, ZCOOL XiaoWei, Zeyada, Zhi Mang Xing, Zilla Slab, Zilla Slab Highlight ``` --- **fontSize** `number`\ The default font size in pixels. Minimum 10 pixels. Maximum 30 pixels. --- **lineHeight** `number`\ The default line height in a unitless format. Minimum 1. Maximum 3. --- ### Font fallback object **name** `string`\ The name of a specific fallback font when the primary font is not supported by the email client. Supported fonts (the name must match exactly): ``` sans-serif, serif, monospace ``` --- **fontSize** `number`\ The default font size in pixels. Minimum 10 pixels. Maximum 30 pixels. --- **lineHeight** `number`\ The default line height in a unitless format. Minimum 1. Maximum 3. --- ### Colors object **highlight** `string` `hex`\ The primary highlight color used for button backgrounds and hyperlink text color. --- **bodyBg** `string` `hex`\ The body background color. --- **contentBg** `string` `hex`\ The content background color. --- **contentBorder** `string` `hex`\ The content border color. --- **buttonTextColor** `string` `hex`\ The button text color. --- **defaultTextColor** `string` `hex`\ The default text color. --- **titleTextColor** `string` `hex`\ The title color. --- **mutedTextColor** `string` `hex`\ The muted text color. --- **footerTextColor** `string` `hex`\ The footer text color. --- **isDarkModeEnabled** `boolean`\ Indicates whether the dark mode of the email template is enabled or disabled. The default is false (dark mode turned off). --- ### Logo object **file** `string` `base64`\ The logo image file in Base64 format. Use `null` to remove a previously uploaded file. --- **darkModeFile** `string` `base64`\ A logo image file in Base64 format. This logo will only be displayed in supported email clients when dark mode is enabled. Use `null` to remove a previously uploaded file. --- **placeholder** `string`\ A text logo placeholder shown when images are blocked by the email client or the logo file fails to load. --- **sizeWidth** `number`\ The width of the logo image in pixels. Minimum 0. Maximum 999. --- **href** `string`\ The URL used as a hyperlink for the logo image. Set to `null` to remove the hyperlink. Default is `null`. --- ### Returns ```javascript { "updated": { "id": "65d9cc698b6fe02d04f7f82b", "orgId": "61e6b9e048f51028dc83489f", "parentId": "65d75fea9cca1727dc110e1b", "name": "New name", "createdAt": "2024-02-22T14:53:30.004Z", "sentTotal": 0, "deliveriesTotal": 0, "bouncesTotal": 0, "complaintsTotal": 0, "suppressedTotal": 0, "bounceRateLimit": 7, "complaintRateLimit": 0.1, "sendingRate": 1, "isSendingPaused": false, "emailTemplateDesign": { "unsubscribeText": "Darse de baja", "unsubscribePageTitle": "Unsubscribed!", "unsubscribePageDescription": "You won’t receive more emails.", "font": { "name": "Acme", "type": "google", "fontSize": 15, "lineHeight": 1.6 }, "fontFallback": { "name": "sans-serif", "type": "system", "fontSize": 14, "lineHeight": 1.65 }, "colors": { "highlight": "#0000FF", "bodyBg": "#f7f7f7", "contentBg": "#ffffff", "contentBorder": "#e6e6e6", "buttonTextColor": "#ffffff", "defaultTextColor": "#575757", "titleTextColor": "#222222", "mutedTextColor": "#b3b3b3", "footerTextColor": "#b3b3b3", "isDarkModeEnabled": true }, "logo": { "sizeWidth": 50, "placeholder": "Lorem ipsum", "file": "https://sidemail.s3.amazonaws.com/user-uploaded/logos/7e06c4edd18af569e666a0c1dfef27b2", "darkModeFile": null, "href": "https://example.com" }, "footerTextPromotional": null, "footerTextTransactional": "You're receiving these emails because you registered for Acme Inc." } } } ``` **id** `string`\ Unique identifier of the linked project. --- **apiKey** `string`\ This value must be stored on your side for subsequent API requests related to this linked project (provided only once). ## Get a project Retrieves project data based on the API key in the Authorization header. This method works for both normal projects created via Sidemail dashboard and linked projects created via the API. `GET https://api.sidemail.io/v1/project` ### Parameters *No parameters.* ### Returns Returns a project object. ```javascript { "project": { "id": "65d9cc698b6fe02d04f7f82b", "orgId": "61e6b9e048f51028dc83489f", "parentId": "65d75fea9cca1727dc110e1b", "name": "Lorem ipsum", "createdAt": "2024-02-24T11:00:57.453Z", "sentTotal": 0, "deliveriesTotal": 0, "bouncesTotal": 0, "complaintsTotal": 0, "suppressedTotal": 0, "bounceRateLimit": 7, "complaintRateLimit": 0.1, "sendingRate": 1, "isSendingPaused": false, "emailTemplateDesign": { "unsubscribeText": "Unsubscribe", "unsubscribePageTitle": "Unsubscribed!", "unsubscribePageDescription": "You won’t receive more emails.", "font": { "name": "Lato", "type": "google", "fontSize": 15, "lineHeight": 1.6 }, "fontFallback": { "name": "sans-serif", "type": "system", "fontSize": 14, "lineHeight": 1.65 }, "colors": { "highlight": "#0090f0", "bodyBg": "#f7f7f7", "contentBg": "#ffffff", "contentBorder": "#e6e6e6", "buttonTextColor": "#ffffff", "defaultTextColor": "#575757", "titleTextColor": "#222222", "mutedTextColor": "#b3b3b3", "footerTextColor": "#b3b3b3" }, "logo": { "sizeWidth": 130, "placeholder": "Lorem ipsum", "url": null, "darkModeUrl": null, "href": null }, "footerTextPromotional": null, "footerTextTransactional": null } } } ``` ```js import { configureSidemail } from "@sidemail/sidemail"; // Works with either a normal project or a linked project's apiKey const sidemail = configureSidemail({ apiKey: "replace-with-project-or-linked-api-key" }); const project = await sidemail.project.get(); ``` ```php project->get(); ``` ```python from sidemail import Sidemail # Works with either a normal project or a linked project's apiKey sm = Sidemail(api_key="replace-with-project-or-linked-api-key") project = sm.project.get() ``` ## Reputation limits and sending controls Linked project bounce and complaint rates are evaluated independently from other linked projects in the same organization. The limits are configured with `bounceRateLimit` and `complaintRateLimit`, and the current configuration is returned when you create, update, or get a linked project. The dashboard shows Sidemail's adjusted reputation rate as `current / limit`. At low send volume, displayed rates may be adjusted to avoid misleading percentages. If a linked project exceeds its configured reputation limits, Sidemail can pause sending for that linked project. You can also manually pause and re-enable linked project sending from the parent project's API page in the Sidemail dashboard. You can also pause or re-enable linked-project sending via the API by updating `isSendingPaused` with the linked project's API key. ```javascript { "isSendingPaused": true } ``` ```javascript { "isSendingPaused": false } ``` Re-enabling a linked project restores linked-project sending, but organization-level health limits still apply globally and can continue to block sending. Pause and re-enable controls are available for linked projects only. They are available from the Sidemail dashboard and via `PATCH https://api.sidemail.io/v1/project`. ## Delete a linked project `DELETE https://api.sidemail.io/v1/project` Permanently deletes a linked project based on the API key in the `Authorization` header. It cannot be undone. ### Parameters *No parameters.* ### Returns Returns an object with `deleted` parameter that indicates the outcome of the operation. ```javascript { "deleted": true } ``` ```js import { configureSidemail } from "@sidemail/sidemail"; const sidemail = configureSidemail({ apiKey: "replace-with-linked-project-api-key" }); await sidemail.project.delete(); ``` ```php project->delete(); ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-linked-project-api-key") resp = sm.project.delete() print(resp) ``` --- # Templates API Source: https://sidemail.io/docs/api/templates/index.md # Templates API methods (unstable) Sidemail Templates API lets you create and manage reusable email templates via API. Use templates to keep email subjects and content consistent, then send emails using a template name or ID. ### Available API endpoints: - `GET https://api.sidemail.io/v1/templates` - List templates - `GET https://api.sidemail.io/v1/templates/{id}` - Retrieve a template - `POST https://api.sidemail.io/v1/templates` - Create a template - `PATCH https://api.sidemail.io/v1/templates/{id}` - Update a template - `GET https://api.sidemail.io/v1/templates/gallery` - List gallery templates - `GET https://api.sidemail.io/v1/templates/fonts` - List available template fonts --- ## Template Object A template object represents a reusable email template. - `id` (string): Unique identifier of the template. - `name` (string): Unique template name within the project. - `subject` (string): Email subject. Template variables are supported. - `content` (array): Template content nodes. Omitted from list responses unless `includeContent` is `true`. - `emailText` (string or null, optional): Custom plain-text email content. - `preheader` (string or null, optional): Preview text displayed by supported email clients. - `sampleProps` (object or null, optional): Example template variable values shown when previewing the template in the Sidemail editor. These values are not used when sending emails. Values can be strings, arrays of non-negative numbers, or arrays of objects. - `layoutId` (string or null, optional): Identifier of the project template layout used to wrap the template. A null or unknown layout ID uses the default auto-generated layout. - `version` (number, read-only): Template content format version. Templates returned by this API use version `2`. - `createdAt` (string): ISO8601 date string when the template was created. - `updatedAt` (string, optional): ISO8601 date string when the template was last updated. ### Content nodes The `content` array contains the nodes rendered in the email. Supported node types are `text`, `button`, `list`, `table`, `divider`, `image`, `chart`, `code`, and `box`. Every content node uses these common fields: - `id` (string, optional): Unique identifier of the node. Sidemail generates an ID when it is missing or duplicated in a create or update request. - `type` (string): Node type. - `styles` (object, optional): Node style overrides. - `iterationKey` (string, optional): Name of an array in `templateProps`. The node is repeated for every item in the array. Text values, links, labels, image properties, and table cells can contain template variables using `{variable_name}` syntax. ### Node styles The fields supported inside `styles` depend on the node type. Common fields include: - `marginBottom` (number): Space below the node in pixels. - `align` (string): One of `left`, `center`, or `right`. - `width` (number or string): Width in pixels or as a percentage such as `"100%"`. - `height` (number or string): Node height. - `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `letterSpacing`, `color`, `textTransform`, and `textDecoration`: Text style overrides. - `linkColor` and `linkTextDecoration`: Link style overrides. - `backgroundColor`, `borderWidth`, `borderColor`, and `borderRadius`: Color and border overrides. - `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`: Padding in pixels. - `dark` (object): Style overrides applied in dark mode when dark mode is enabled in the project template design. - `mobile` (object): Style overrides applied on small screens. ### Text element - `text` (string): Text content. Markdown and template variables are supported. - `preset` (string, optional): Text style preset ID, such as `default`, `title`, or `muted`. ```json { "id": "text-title", "type": "text", "preset": "title", "text": "Welcome to {project_name}", "styles": { "marginBottom": 20, "align": "left" } } ``` ### Button element - `label` (string): Button label. Template variables are supported. - `url` (string): Button destination URL. Template variables are supported. - `preset` (string, optional): Button style preset ID. ```json { "id": "button-account", "type": "button", "label": "Open your account", "url": "{account_url}", "styles": { "marginBottom": 20, "align": "left" } } ``` ### List element - `items` (array of strings): List item content. Markdown and template variables are supported. - `symbol` (string, optional): List marker. Use `number` for a numbered list or an empty string to hide markers. - `preset` (string, optional): List style preset ID. ```json { "id": "list-links", "type": "list", "items": [ "Read our [documentation]({docs_url})", "Contact {support_email}" ], "symbol": "›", "styles": { "marginBottom": 20 } } ``` ### Table element - `columns` (array): Table column definitions. - `label` (string): Column heading. - `align` (string, optional): One of `left`, `center`, or `right`. - `rows` (array): Array of rows. Each row is an array of cells containing a `text` string. - `iterationTarget` (string, optional): Set to `children` together with `iterationKey` to repeat the first row for every item in the array. The default repeats the complete table. ```json { "id": "table-invoice", "type": "table", "columns": [ { "label": "Item", "align": "left" }, { "label": "Price", "align": "right" } ], "rows": [ [ { "text": "{name}" }, { "text": "{price}" } ] ], "iterationKey": "items", "iterationTarget": "children", "styles": { "marginBottom": 20 } } ``` ### Divider element The divider has no content-specific fields. Use `styles.borderWidth`, `styles.borderColor`, and `styles.borderRadius` to customize it. ```json { "id": "divider-footer", "type": "divider", "styles": { "marginBottom": 20, "borderWidth": 1, "borderColor": "#e6e6e6" } } ``` ### Image element - `src` (string): Image URL. Template variables are supported. - `darkModeSrc` (string, optional): Image URL used in dark mode. - `alt` (string): Alternative text. Template variables are supported. - `href` (string or null, optional): Link opened when the image is clicked. ```json { "id": "image-banner", "type": "image", "src": "https://example.com/banner.png", "darkModeSrc": "https://example.com/banner-dark.png", "alt": "Welcome to {project_name}", "href": "{account_url}", "styles": { "marginBottom": 20, "align": "center", "width": "100%", "borderRadius": 8 } } ``` ### Chart element - `dataKey` (string): Name of an array of non-negative numbers in `templateProps`. - `chartType` (string): One of `line` or `bar`. - `curve` (string, optional): For line charts, one of `monotone` or `linear`. - `fill` (boolean, optional): Whether to fill the chart background. - `maxValue` (number, optional): Maximum value displayed on the Y axis. - `xAxisStartLabel` and `xAxisEndLabel` (string, optional): Labels displayed at the start and end of the X axis. ```json { "id": "chart-usage", "type": "chart", "dataKey": "usage", "chartType": "line", "curve": "monotone", "fill": true, "maxValue": 100, "xAxisStartLabel": "30 days ago", "xAxisEndLabel": "Now", "styles": { "marginBottom": 20 } } ``` The matching template props would contain: ```json { "usage": [20, 35, 42, 70, 85] } ``` ### Code element - `code` (string): Code content. - `language` (string): One of `text`, `javascript`, `php`, `ruby`, `python`, or `bash`. ```json { "id": "code-install", "type": "code", "code": "npm install @sidemail/sidemail", "language": "bash", "styles": { "marginBottom": 20 } } ``` ### Box element - `columns` (array): Box columns. - `width` (string or number, optional): Column width, such as `"50%"` or `"200px"`. - `elements` (array): Content elements rendered inside the column. Elements can be nested recursively. - `styles.gap` (number, optional): Space between columns in pixels. - `styles.verticalAlign` (string, optional): One of `top`, `middle`, or `bottom`. - `styles.mobile.stackColumns` (boolean, optional): Whether columns stack on small screens. ```json { "id": "box-summary", "type": "box", "columns": [ { "width": "50%", "elements": [ { "id": "box-summary-title", "type": "text", "text": "**Current plan**" } ] }, { "width": "50%", "elements": [ { "id": "box-summary-value", "type": "text", "text": "{plan_name}" } ] } ], "styles": { "marginBottom": 20, "gap": 20, "verticalAlign": "top", "mobile": { "stackColumns": true } } } ``` ### Example Template Object ```json { "id": "65dc4595af2e7a530209b414", "name": "Welcome", "subject": "Welcome to {project_name}", "preheader": "Your account is ready.", "sampleProps": { "project_name": "Acme" }, "content": [ { "id": "i3f5n6a8q", "type": "text", "preset": "title", "text": "Welcome to {project_name}", "styles": { "marginBottom": 20 } }, { "id": "r2n9c4m7x", "type": "button", "label": "Open your account", "url": "{account_url}" } ], "emailText": null, "layoutId": "custom-layout", "version": 2, "createdAt": "2026-06-12T09:00:00.000Z", "updatedAt": "2026-06-12T09:30:00.000Z" } ``` --- ## List templates `GET https://api.sidemail.io/v1/templates` Returns templates sorted by creation date, with the most recent templates appearing first. ```bash curl -X GET "https://api.sidemail.io/v1/templates?limit=100" \ -H "Authorization: Bearer replace-with-your-api-key" ``` ### Parameters **paginationCursorNext** `string` `optional` Cursor for fetching the next page of templates. Use the `paginationCursorNext` value returned by the previous request. --- **limit** `number` `optional` Number of templates returned per page. The default and maximum value is `100`. --- **includeContent** `boolean` `optional` Whether to include the `content` array for each template. Defaults to `false` to reduce response size. ### Returns Returns a `data` property containing an array of template objects, `hasMore` indicating whether another page is available, and `paginationCursorNext` for fetching that page. Template content is omitted unless `includeContent` is `true`. ```json { "hasMore": true, "paginationCursorNext": "65dc4595af2e7a530209b413", "data": [ { "id": "65dc4595af2e7a530209b414", "name": "Welcome", "subject": "Welcome to {project_name}", "layoutId": null, "version": 2 } ] } ``` --- ## Retrieve a template `GET https://api.sidemail.io/v1/templates/{id}` Returns a template by ID. ```bash curl -X GET "https://api.sidemail.io/v1/templates/65dc4595af2e7a530209b414" \ -H "Authorization: Bearer replace-with-your-api-key" ``` ### Parameters **id** `string` Unique identifier of the template. ### Returns Returns the template under the `data` property. ```json { "data": { "id": "65dc4595af2e7a530209b414", "name": "Welcome", "subject": "Welcome to {project_name}", "content": [], "layoutId": null, "version": 2 } } ``` --- ## Create a template `POST https://api.sidemail.io/v1/templates` Creates a template. Template names must be unique. ```bash curl -X POST "https://api.sidemail.io/v1/templates" \ -H "Authorization: Bearer replace-with-your-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "Welcome", "subject": "Welcome to {project_name}", "preheader": "Your account is ready.", "layoutId": "custom-layout", "sampleProps": { "project_name": "Acme" }, "content": [ { "type": "text", "preset": "title", "text": "Welcome to {project_name}" }, { "type": "button", "label": "Open your account", "url": "{account_url}" } ] }' ``` ### Parameters **name** `string` Required. Unique template name within the project. Maximum 200 characters. --- **subject** `string` Required. Email subject. Maximum 1,000 characters. --- **content** `array` `optional` Template content nodes. Sidemail generates IDs for nodes that do not have a unique ID. --- **emailText** `string` or `null` `optional` Custom plain-text email content. Maximum 1 MB. --- **preheader** `string` or `null` `optional` Email preview text. Maximum 200 characters. --- **sampleProps** `object` or `null` `optional` Example values for template variables shown when previewing the template in the Sidemail editor. These values are not used when sending emails. Values can be strings, arrays of non-negative numbers, or arrays of objects. --- **layoutId** `string` or `null` `optional` Identifier of the project template layout used to wrap the template. Maximum 20 characters. Use `null` or omit this field to use the default auto-generated layout. ### Returns Returns the created template under the `data` property. ```json { "data": { "id": "65dc4595af2e7a530209b414", "name": "Welcome", "subject": "Welcome to {project_name}", "content": [ { "id": "i3f5n6a8q", "type": "text", "preset": "title", "text": "Welcome to {project_name}" } ], "layoutId": "custom-layout", "version": 2, "createdAt": "2026-06-12T09:00:00.000Z" } } ``` Creating a template with an existing name returns `403 Forbidden` with the `resource-duplicate` error code. --- ## Update a template `PATCH https://api.sidemail.io/v1/templates/{id}` Updates the provided fields of a template. ```bash curl -X PATCH "https://api.sidemail.io/v1/templates/65dc4595af2e7a530209b414" \ -H "Authorization: Bearer replace-with-your-api-key" \ -H "Content-Type: application/json" \ -d '{ "subject": "Welcome, {first_name}", "preheader": "Your account is ready." }' ``` ### Parameters **id** `string` Unique identifier of the template. The request body accepts the same template fields as the create endpoint. All fields are optional and only the provided fields are updated. ### Returns Returns the updated template under the `data` property. ```json { "data": { "id": "65dc4595af2e7a530209b414", "name": "Welcome", "subject": "Welcome, {first_name}", "preheader": "Your account is ready.", "content": [], "layoutId": "custom-layout", "version": 2, "updatedAt": "2026-06-12T09:30:00.000Z" } } ``` Updating a template to an existing name returns `403 Forbidden` with the `resource-duplicate` error code. --- ## List gallery templates `GET https://api.sidemail.io/v1/templates/gallery` Returns the templates available in the Sidemail template gallery. Gallery templates can be submitted to the create template endpoint. ```bash curl -X GET "https://api.sidemail.io/v1/templates/gallery" \ -H "Authorization: Bearer replace-with-your-api-key" ``` ### Parameters *No parameters.* ### Returns Returns gallery templates under the `data` property. Gallery templates include `slug` and `tags` metadata and do not include a saved template ID. ```json { "data": [ { "name": "Welcome", "slug": "welcome", "tags": ["transactional", "marketing"], "subject": "Welcome to {project_name}", "version": 2, "sampleProps": { "project_name": "Company name" }, "content": [ { "id": "i3f5n6a8q", "type": "text", "preset": "title", "text": "Hi there!" } ] } ] } ``` --- ## List available template fonts `GET https://api.sidemail.io/v1/templates/fonts` Returns fonts that can be used by the Sidemail template editor. ```bash curl -X GET "https://api.sidemail.io/v1/templates/fonts" \ -H "Authorization: Bearer replace-with-your-api-key" ``` ### Parameters *No parameters.* ### Returns ```json { "data": [ { "name": "Roboto", "category": "sans-serif", "variants": ["regular", "700"], "subsets": ["latin"] } ] } ``` --- # Batch email sending Source: https://sidemail.io/docs/batch-email-sending/index.md # Batch email sending Send up to 50 emails in a single API request by sending an `array` of parameters accepted by [send email method](/docs/api/email/) in a `object`. This is a recommended way to efficiently send emails to a large list of recipients. For example, if you're sending email report every week to all your users, consider batching. ## Limitations - The total size of the request must be less than 10 MB. ## Example ```javascript const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" }); const response = await sidemail.sendEmail([ { toAddress: "user-1@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, { toAddress: "user-2@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, { toAddress: "user-3@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, ]); ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $response = $sm->sendEmail([ [ 'toAddress' => 'user-1@example.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'Weekly report', ], [ 'toAddress' => 'user-2@example.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'Weekly report', ], [ 'toAddress' => 'user-3@example.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'Weekly report', ], ]); ``` ```ruby require "sidemail" sm = Sidemail.new(api_key: "replace-with-your-api-key") response = sm.send_email([ { toAddress: "user-1@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, { toAddress: "user-2@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, { toAddress: "user-3@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", }, ]) ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") resp = sm.send_email([ { "toAddress": "user-1@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report", }, { "toAddress": "user-2@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report", }, { "toAddress": "user-3@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report", }, ]) ``` ```bash curl -X POST https://api.sidemail.io/v1/emails \ -H "Content-Type: application/json" \ -H "Authorization: Bearer replace-with-your-api-key" \ -d '[ { "toAddress": "user-1@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report" }, { "toAddress": "user-2@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report" }, { "toAddress": "user-3@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report" } ]' ``` ## Returns The response contains an `array` of `objects` with an email ID and a status of email on success. ```javascript [ { id: "5e858953daf20f3aac50a3da", status: "queued", }, { id: "5e858953daf20f3aac50a4da", status: "queued", }, { id: "5e858953daf20f3aac50a5da", status: "queued", }, ]; ``` --- # Quickstart to contact profiles Source: https://sidemail.io/docs/contact-profiles-quickstart/index.md # Introduction Tracking user (contact) data is useful to learn more about your users, and makes possible sending targeted emails. For example, you can track when user was last seen, how many todos completed (if todos app) or the favourite color. You can track anything, really. The more you know about your user, the better you can target them with specific emails. ## What data you should track? To get your some ideas for what data you could track about your users: - **Generic** – name, company, website, registration date - **Payment** – plan type, trial expiration date, billing interval, next billing date, customer lifetime value - **Activity** – last seen date; your application specifics, if todos app: todos created, todos archived, last todo created at date, onboarding completed date ## Setting up contact properties ### Create contact (user) properties Before you can track any user data, you need to tell Sidemail what data should expect. Head over to your project settings and find the **Contact properties** section. **There, you can create and edit properties that you track about your users.** Supported data types: - String - Number - Date [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) ### Naming convention You can name contact properties in whatever naming convention you prefer. For example, this how you could name full name property: - `fullName` - `FullName` - `full_name` - `Full name` ## Example Push contact data to Sidemail via `create or update` contact method. ```js const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "xxxxx" }); const response = await sidemail.contacts.createOrUpdate({ emailAddress: "john.doe@example.com", identifier: "123", // ID representing the user in your database customProps: { fullName: "John doe", pricingPlan: "premium", registeredAt: "2019-08-15T13:20:39.160Z", lastSeenAt: "2019-08-20T17:40:39.160Z", // ... more of your contact data ... }, }); ``` ```ruby require "sidemail" sm = Sidemail.new(api_key: "replace-with-your-api-key") sm.contacts.create_or_update( emailAddress: "john.doe@example.com", identifier: "123", customProps: { fullName: "John doe", pricingPlan: "premium", registeredAt: "2019-08-15T13:20:39.160Z", lastSeenAt: "2019-08-20T17:40:39.160Z", } ) ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") sm.contacts.create_or_update( emailAddress="john.doe@example.com", identifier="123", customProps={ "fullName": "John doe", "pricingPlan": "premium", "registeredAt": "2019-08-15T13:20:39.160Z", "lastSeenAt": "2019-08-20T17:40:39.160Z", }, ) ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $sm->contacts->createOrUpdate([ 'emailAddress' => 'john.doe@example.com', 'identifier' => '123', 'customProps' => [ 'fullName' => 'John doe', 'pricingPlan' => 'premium', 'registeredAt' => '2019-08-15T13:20:39.160Z', 'lastSeenAt' => '2019-08-20T17:40:39.160Z', ], ]); ``` ```bash curl -X POST https://api.sidemail.io/v1/contacts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer replace-with-your-api-key" \ -d '{ "emailAddress": "john.doe@example.com", "identifier": "123", "customProps": { "fullName": "John doe", "pricingPlan": "premium", "registeredAt": "2019-08-15T13:20:39.160Z", "lastSeenAt": "2019-08-20T17:40:39.160Z" } }' ``` ## Force-triggering automation If you need to force trigger an automation so that all contacts currently in a matching state enter it, follow these steps: - Create a temporary custom contact property (e.g., name `trigger` and type `number`). Ensure this new property is not used in any other automation to avoid unintended re-triggers. - Go to the automation you want to force trigger, click update, and add this property as one of the triggers (e.g., `trigger` equals `1`). Save the changes. - Use the Sidemail.io API to get all contacts and update each contact with the property `trigger` set to `1`. Since your automation will have other triggers, it’s safe to update all contacts; only the correct contacts based on the automation’s triggers will enter the automation. - As a cleanup step, after you remove `trigger` as the automation trigger, make a second API update request to set the `trigger` property to `null`. This process ensures that only the relevant contacts will enter the automation while maintaining the integrity of other automations. Here's a code example: ```js const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" }); let totalUpdated = 0; const result = await sidemail.contacts.list(); for await (const contact of result) { console.log(`Updating: ${contact.emailAddress} (${++totalUpdated})`); await sidemail.contacts.createOrUpdate({ emailAddress: contact.emailAddress, customProps: { trigger: 1 }, }); } console.log("All contacts processed."); ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") result = sm.contacts.list() totalUpdated = 0 for contact in result.auto_paginate(): totalUpdated += 1 print(f"Updating: {contact['emailAddress']} ({totalUpdated})") sm.contacts.create_or_update( emailAddress=contact["emailAddress"], customProps={ "trigger": 1 }, ) print("All contacts processed.") ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $result = $sm->contacts->list(); $totalUpdated = 0; foreach ($result->autoPaginate() as $contact) { $totalUpdated += 1; echo "Updating: {$contact['emailAddress']} ({$totalUpdated})\n"; $sm->contacts->createOrUpdate([ 'emailAddress' => $contact['emailAddress'], 'customProps' => [ 'trigger' => 1 ], ]); } echo "All contacts processed."; ``` --- # Sending custom HTML emails Source: https://sidemail.io/docs/custom-html-emails/index.md # Sending custom HTML emails You can send custom HTML emails via the Sidemail API. To send HTML emails you'll need to specify the `html` parameter. - You can use all UTF-8 characters inside inside the `html` parameter. - An email open tracking pixel is automatically inserted at the end of the email ``. You can disable open tracking by setting parameter `isOpenTracked` to `false`. ## Example ```js const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" }); const response = await sidemail.sendEmail({ toAddress: "user@example.com", fromAddress: "you@example.com", fromName: "Your app", subject: "Testing HTML only custom emails :)", html: "Hello!
Amount due: $${amount}
Amount due: $99
Amount due: $${amount}
Amount due: ${amount}
Amount due: $99
This is a test email.
" }); ``` ```php isSMTP(); $mail->Host = 'mx.sidemail.net'; $mail->Port = 587; $mail->SMTPAuth = true; $mail->Username = 'api'; $mail->Password = 'your-api-key'; $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS; $mail->setFrom('you@yourdomain.com'); $mail->addAddress('customer@example.com'); $mail->Subject = 'Hello!'; $mail->Body = 'This is a test email.
'; $mail->AltBody = 'This is a test email.'; $mail->send(); ``` ```python import smtplib from email.mime.text import MIMEText msg = MIMEText("This is a test email.") msg["Subject"] = "Hello!" msg["From"] = "you@yourdomain.com" msg["To"] = "customer@example.com" with smtplib.SMTP("mx.sidemail.net", 587) as server: server.starttls() server.login("api", "your-api-key") server.send_message(msg) ``` ## Limits - **5MB** maximum message size (including attachments) - Sender address must be [verified](/docs/sending-identities/) in your project - Inline images referenced with `cid:` URLs are supported when the MIME part includes a matching `Content-ID` ## Troubleshooting ### Authentication failed - Double-check your API key is correct - Make sure the API key belongs to the project you're sending from - Verify your account is active and not suspended ### Sender not verified The "From" address must be verified in your Sidemail project. Add and verify it in your dashboard under Sending Identities. ### Connection timeout - Ensure port 587 is not blocked by your firewall - Try using a different network to rule out ISP blocking --- # Template iteration group Source: https://sidemail.io/docs/template-iteration-group/index.md # Template iteration group You can define dynamic lists in the no-code email editor, and then send an `array` of `objects` inside of `templateProps` parameter from which the dynamic list will be generated. Use-cases: - A website monitoring tool that sends an weekly email report. User can have multiple websites monitored. We'll use this as a example below. - A receipt where there are many options or products a customer can purchase. - A blogging platform that notifies an article owner about new comments. ## Set up email template First, you'll need to create an template in the no-code email editor and fill in a `iteration group name`. All elements support the `iteration group name` and you can find it in element's options. You can use all template props by name `{item}` or `{price}` in any element which has the `iteration group name` specified as `list`. This means template props inside iteration group `array` are made local to each element that is in iteration group. But this also means template props inside of iteration group overwrite global template props (eg., `price` inside `list` would overwrite `price` that is directly inside of `templateProps` parameter), ## Code example ```js const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" }); const response = await sidemail.sendEmail({ toAddress: "user@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "After purchase email", templateProps: { list: [ { item: "🧢 The Shirt Shop Script A Hat", price: "25 USD" }, { item: "👕 Peter Millar Jubilee Game Day Polo", price: "113 USD" }, { item: "📦 2-day shipping", price: "FREE" }, { item: "**Total**", price: "**138 USD**" }, ], }, }); ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $response = $sm->sendEmail([ 'toAddress' => 'user@example.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'After purchase email', 'templateProps' => [ 'list' => [ [ 'item' => '🧢 The Shirt Shop Script A Hat', 'price' => '25 USD' ], [ 'item' => '👕 Peter Millar Jubilee Game Day Polo', 'price' => '113 USD' ], [ 'item' => '📦 2-day shipping', 'price' => 'FREE' ], [ 'item' => '**Total**', 'price' => '**138 USD**' ], ], ], ]); ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") resp = sm.send_email( toAddress="user@example.com", fromAddress="you@example.com", fromName="Your app", templateName="After purchase email", templateProps={ "list": [ { "item": "🧢 The Shirt Shop Script A Hat", "price": "25 USD" }, { "item": "👕 Peter Millar Jubilee Game Day Polo", "price": "113 USD" }, { "item": "📦 2-day shipping", "price": "FREE" }, { "item": "**Total**", "price": "**138 USD**" }, ] } ) ``` ```bash curl -X POST https://api.sidemail.io/v1/emails \ -H "Content-Type: application/json" \ -H "Authorization: Bearer replace-with-your-api-key" \ -d '{ "toAddress": "user@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "After purchase email", "templateProps": { "list": [ { "item": "🧢 The Shirt Shop Script A Hat", "price": "25 USD" }, { "item": "👕 Peter Millar Jubilee Game Day Polo", "price": "113 USD" }, { "item": "📦 2-day shipping", "price": "FREE" }, { "item": "**Total**", "price": "**138 USD**" } ] } }' ``` ## Nested iteration group It's often useful to include a dynamic list inside of dynamic list. You can define nested iteration group by using `.` dot in the element's `iteration group name`. Only one iteration group can be nested inside of another iteration group. For the following code example to work, the element's `iteration group name` should be set to `list.downtimes`. All template props from the `downtimes` array will be made local in the element that is in the iteration group `list.downtimes` (overwriting any global template props). ```js const configureSidemail = require("sidemail"); const sidemail = configureSidemail({ apiKey: "replace-with-your-api-key" }); const response = await sidemail.sendEmail({ toAddress: "user@example.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Weekly report", templateProps: { list: [ { website: "Example app 1", uptime: "99.252", downtimes: [ { date: "2020-05-05", duration: "10" }, { date: "2020-05-07", duration: "23" }, { date: "2020-05-09", duration: "2" }, ], }, { website: "Example website 2", uptime: "99.252", downtimes: [ { date: "2020-05-05", duration: "1000" }, { date: "2020-05-02", duration: "100" }, { date: "2020-05-07", duration: "2300" }, { date: "2020-05-09", duration: "200" }, ], }, // etc... ], }, }); ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $response = $sm->sendEmail([ 'toAddress' => 'user@example.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'Weekly report', 'templateProps' => [ 'list' => [ [ 'website' => 'Example app 1', 'uptime' => '99.252', 'downtimes' => [ [ 'date' => '2020-05-05', 'duration' => '10' ], [ 'date' => '2020-05-07', 'duration' => '23' ], [ 'date' => '2020-05-09', 'duration' => '2' ], ], ], [ 'website' => 'Example website 2', 'uptime' => '99.252', 'downtimes' => [ [ 'date' => '2020-05-05', 'duration' => '1000' ], [ 'date' => '2020-05-02', 'duration' => '100' ], [ 'date' => '2020-05-07', 'duration' => '2300' ], [ 'date' => '2020-05-09', 'duration' => '200' ], ], ], ], ], ]); ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") resp = sm.send_email( toAddress="user@example.com", fromAddress="you@example.com", fromName="Your app", templateName="Weekly report", templateProps={ "list": [ { "website": "Example app 1", "uptime": "99.252", "downtimes": [ { "date": "2020-05-05", "duration": "10" }, { "date": "2020-05-07", "duration": "23" }, { "date": "2020-05-09", "duration": "2" }, ], }, { "website": "Example website 2", "uptime": "99.252", "downtimes": [ { "date": "2020-05-05", "duration": "1000" }, { "date": "2020-05-02", "duration": "100" }, { "date": "2020-05-07", "duration": "2300" }, { "date": "2020-05-09", "duration": "200" }, ], }, ] } ) ``` ```bash curl -X POST https://api.sidemail.io/v1/emails \ -H "Content-Type: application/json" \ -H "Authorization: Bearer replace-with-your-api-key" \ -d '{ "toAddress": "user@example.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Weekly report", "templateProps": { "list": [ { "website": "Example app 1", "uptime": "99.252", "downtimes": [ { "date": "2020-05-05", "duration": "10" }, { "date": "2020-05-07", "duration": "23" }, { "date": "2020-05-09", "duration": "2" } ] }, { "website": "Example website 2", "uptime": "99.252", "downtimes": [ { "date": "2020-05-05", "duration": "1000" }, { "date": "2020-05-02", "duration": "100" }, { "date": "2020-05-07", "duration": "2300" }, { "date": "2020-05-09", "duration": "200" } ] } ] } }' ``` --- # Dynamic data with template props Source: https://sidemail.io/docs/template-props/index.md # Dynamic data with template props In the real world, emails are not just static and that's when template props come in handy. Template props are basically variables that allow you to put dynamic data in your email templates. - Template props can be used in any no-code editor's element. - To define a template prop, use `{}` curly brackets. For example, in a password reset email template, you specify the URL of a button to `{url}` or `{passwordResetUrl}` or `{password_reset_url}` depending on your preference. Now when sending the password reset email template, the template expects you to supply `url` (or whatever name you used) inside of `templateProps` object parameter. This is how you send a template with `templateProps`: ```javascript const configureSidemail = require("sidemail"); const sm = configureSidemail({ apiKey: "replace-with-your-api-key" }); const response = await sm.sendEmail({ toAddress: "user@email.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Password reset", templateProps: { "url": "https://reset.me/123" } }); ``` ```ruby require "sidemail" sm = Sidemail.new(api_key: "replace-with-your-api-key") response = sm.send_email( toAddress: "user@email.com", fromAddress: "you@example.com", fromName: "Your app", templateName: "Password reset", templateProps: { url: "https://reset.me/123" } ) ``` ```php $sm = new Sidemail\Sidemail(apiKey: 'replace-with-your-api-key'); $response = $sm->sendEmail([ 'toAddress' => 'user@email.com', 'fromAddress' => 'you@example.com', 'fromName' => 'Your app', 'templateName' => 'Password reset', 'templateProps' => [ "url" => "https://reset.me/123" ] ]); ``` ```python from sidemail import Sidemail sm = Sidemail(api_key="replace-with-your-api-key") resp = sm.send_email( toAddress="user@email.com", fromAddress="you@example.com", fromName="Your app", templateName="Password reset", templateProps={ "url": "https://reset.me/123" } ) ``` ```bash curl -X POST https://api.sidemail.io/v1/emails \ -H "Content-Type: application/json" \ -H "Authorization: Bearer replace-with-your-api-key" \ -d '{ "toAddress": "user@email.com", "fromAddress": "you@example.com", "fromName": "Your app", "templateName": "Password reset", "templateProps": { "url": "https://reset.me/123" } }' ``` When no corresponding template prop name is found inside of `templateProps`, the template prop is left as is, just the curly brackets `{}` are removed. ## Use-cases for template props Here are a few ideas of how you can utilize template props: - to personalize an email with user's first name, eg., `{firstName}` becomes `John` if `templateProps` look like this: ```javascript { "firstName": "John" } ``` - to include unique typically auto-generated URLs (password reset, login link), eg., `{sso_url}` becomes `https://login.me/in?token=123` if `templateProps` look like this: ```javascript { "sso_url": "https://login.me/in?token=123" } ``` - show the charged amount for your service on a receipt, eg., `{charged-price}` becomes `$249` if `templateProps` look like this: ```javascript { "charged-price": "$249" } ``` ## Gotchas `templateProps` object parameter expects the values of template props to be `string`. Make sure you convert numbers to strings, otherwise, Sidemail will return `parameters-invalid` [error](/docs/api/errors/). Data for [chart element](/docs/data-charts/) and [iteration group](/docs/template-iteration-group/) are exceptions. --- # Receive email events via webhooks Source: https://sidemail.io/docs/webhooks/index.md # Receive email events via webhooks Sidemail uses webhooks to notify your application when an event happens in your project. Webhooks are particularly useful for asynchronous events like when an email is delivered, bounces, is opened, when the recipient reports a spam complaint, when your inbound route receives a new email, or when a sending domain is verified or fails verification. ## How to set up - In your project's API section, add a webhook URL, choose which events it should receive, and get your unique webhook secret. - In your webhook handler, compare the value of `sidemail-secret` header with your webhook secret to verify the request. - Respond to the webhook request with 200 status code (response with any other status is recognized as a webhook failure). ## Node.js webhook handler example: ```js function handleSidemailWebhook(req, res, next) { // Get your webhook secret in your project's API section const WEBHOOK_SECRET = "replace-with-your-webhook-secret"; // Verify that the request is coming from Sidemail if (req.headers["sidemail-secret"] !== WEBHOOK_SECRET) { return res.status(401).send(); } // Your custom logic ... console.log(req.body.type, req.body.data); // Acknowledge the webhook by responding with 200 status code return res.status(200).send(); } ``` ## Events Domain events are organization-wide because sending domains are shared across projects. If multiple projects in the organization have webhook URLs configured, each project webhook with that domain event enabled receives it. ### Event: `email.delivered` ```js { "type": "email.delivered", "data": { "email": { "id": "123", "templateId": "123", "templateName": "Welcome", "templateProps": { "name": "John" }, "fromName": "Example App", "fromAddress": "hello@example.com", "toAddress": "user@example.com", "createdAt": "2020-11-07T09:50:09.951Z" }, "time": "2020-11-07T09:50:09.951Z", "smtpResponse": "250 2.6.0 Message received" } } ``` ### Event: `email.open` Triggered on the first open of an email by the recipient. Subsequent opens are not reported. ```js { "type": "email.open", "data": { "email": { "id": "123", "templateId": "123", "templateName": "Welcome", "templateProps": { "name": "John" }, "fromName": "Example App", "fromAddress": "hello@example.com", "toAddress": "user@example.com", "createdAt": "2020-11-07T09:50:09.951Z" }, "time": "2020-11-07T09:50:09.951Z", "ipAddress": "203.0.113.10", "userAgent": "Mozilla/5.0 ..." } } ``` ### Event: `email.bounce` ```js { "type": "email.bounce", "data": { "email": { "id": "123", "templateId": "123", "templateName": "Welcome", "templateProps": { "name": "John" }, "fromName": "Example App", "fromAddress": "hello@example.com", "toAddress": "user@example.com", "createdAt": "2020-11-07T09:50:09.951Z" }, "time": "2020-11-07T09:50:09.951Z", "bounceType": "permanent" } } ``` ### Event: `email.complaint` ```js { "type": "email.complaint", "data": { "email": { "id": "123", "templateId": "123", "templateName": "Welcome", "templateProps": { "name": "John" }, "fromName": "Example App", "fromAddress": "hello@example.com", "toAddress": "user@example.com", "createdAt": "2020-11-07T09:50:09.951Z" }, "time": "2020-11-07T09:50:09.951Z", "complaintFeedbackType": "abuse" } } ``` ### Event: `email.received` Triggered when an inbound route receives an email. ```js { "type": "email.received", "data": { "email": { "id": "email_123456", "self": "https://api.sidemail.io/v1/inbound/emails/email_123456", "destination": "hi@example.com", "from": { "email": "sender@example.com", "name": "Sender" }, "to": [{ "email": "support@example.com", "name": "Support Team" }], "cc": [{ "email": "ops@example.com", "name": null }], "replyTo": [{ "email": "billing@example.com", "name": "Billing" }], "subject": "Example inbound message", "text": "Hello, please find invoice attached...", "htmlAvailable": true, "attachments": [ { "name": "attachment.pdf", "contentType": "application/pdf", "size": 84320 } ], "previewHtmlUrl": "https://api.sidemail.io/v1/inbound/emails/preview-html?token=...", "rawEmailUrl": "https://storage.example.com/..." }, "spam": { "score": 2.4, "threshold": 15, "isSpam": false, "action": "accept", "symbols": ["R_SPF_ALLOW", "R_DKIM_ALLOW", "MIME_GOOD"] }, "time": "2026-03-27T12:01:18.921Z" } } ``` Notes: - `spam.symbols` contains the rule names that contributed to spam scoring. - `email.self` is a stable API URL for fetching full inbound email detail. - `destination` is the SMTP recipient routed in Sidemail. Header recipients are in `to` and `cc`. - `previewHtmlUrl` is present only when an HTML part is available and is short-lived (currently 15 minutes). - `rawEmailUrl` is a short-lived signed URL (currently 5 minutes). - `text` is truncated to 10 000 characters in the webhook. Fetch the full body via `email.self`. - `attachments` contains metadata only (name, content type, size in bytes). Download the raw email via `rawEmailUrl` to access attachment content. ### Event: `domain.sending.success` Triggered when a sending domain changes to verified and is ready for sending. ```js { "type": "domain.sending.success", "data": { "domain": { "id": "123", "domain": "example.com", "status": "success", "previousStatus": "pending", "mailFromDomain": "out.mail.example.com", "mailFromStatus": "success", "createdAt": "2026-05-10T09:50:09.951Z" }, "time": "2026-05-10T10:05:09.951Z" } } ``` ### Event: `domain.sending.failed` Triggered when a sending domain changes to failed. ```js { "type": "domain.sending.failed", "data": { "domain": { "id": "123", "domain": "example.com", "status": "failed", "previousStatus": "pending", "mailFromDomain": "out.mail.example.com", "mailFromStatus": "failed", "createdAt": "2026-05-10T09:50:09.951Z" }, "time": "2026-05-13T09:50:09.951Z" } } ```