# Email time series
Source: https://docs.usesend.com/api-reference/analytics/email-time-series
get /v1/analytics/email-time-series
# Reputation metrics
Source: https://docs.usesend.com/api-reference/analytics/reputation-metrics
get /v1/analytics/reputation-metrics
# Create campaign
Source: https://docs.usesend.com/api-reference/campaigns/create-campaign
post /v1/campaigns
# Delete campaign
Source: https://docs.usesend.com/api-reference/campaigns/delete-campaign
delete /v1/campaigns/{campaignId}
# Get campaign
Source: https://docs.usesend.com/api-reference/campaigns/get-campaign
get /v1/campaigns/{campaignId}
# Get campaigns
Source: https://docs.usesend.com/api-reference/campaigns/get-campaigns
get /v1/campaigns
# Pause campaign
Source: https://docs.usesend.com/api-reference/campaigns/pause-campaign
post /v1/campaigns/{campaignId}/pause
# Resume campaign
Source: https://docs.usesend.com/api-reference/campaigns/resume-campaign
post /v1/campaigns/{campaignId}/resume
# Schedule campaign
Source: https://docs.usesend.com/api-reference/campaigns/schedule-campaign
post /v1/campaigns/{campaignId}/schedule
# Bulk create contact
Source: https://docs.usesend.com/api-reference/contacts/bulk-create-contact
post /v1/contactBooks/{contactBookId}/contacts/bulk
# Bulk delete contacts
Source: https://docs.usesend.com/api-reference/contacts/bulk-delete-contacts
delete /v1/contactBooks/{contactBookId}/contacts/bulk
# Create contact
Source: https://docs.usesend.com/api-reference/contacts/create-contact
post /v1/contactBooks/{contactBookId}/contacts
# Create contact book
Source: https://docs.usesend.com/api-reference/contacts/create-contact-book
post /v1/contactBooks
# Delete contact
Source: https://docs.usesend.com/api-reference/contacts/delete-contact
delete /v1/contactBooks/{contactBookId}/contacts/{contactId}
# Delete contact book
Source: https://docs.usesend.com/api-reference/contacts/delete-contact-book
delete /v1/contactBooks/{contactBookId}
# Get contact
Source: https://docs.usesend.com/api-reference/contacts/get-contact
get /v1/contactBooks/{contactBookId}/contacts/{contactId}
# Get contact book
Source: https://docs.usesend.com/api-reference/contacts/get-contact-book
get /v1/contactBooks/{contactBookId}
# Get contacts
Source: https://docs.usesend.com/api-reference/contacts/get-contacts
get /v1/contactBooks/{contactBookId}/contacts
# List contact books
Source: https://docs.usesend.com/api-reference/contacts/list-contact-books
get /v1/contactBooks
# Update contact
Source: https://docs.usesend.com/api-reference/contacts/update-contact
patch /v1/contactBooks/{contactBookId}/contacts/{contactId}
# Update contact book
Source: https://docs.usesend.com/api-reference/contacts/update-contact-book
patch /v1/contactBooks/{contactBookId}
# Upsert contact
Source: https://docs.usesend.com/api-reference/contacts/upsert-contact
put /v1/contactBooks/{contactBookId}/contacts/{contactId}
# Create domain
Source: https://docs.usesend.com/api-reference/domains/create-domain
post /v1/domains
# Delete domain
Source: https://docs.usesend.com/api-reference/domains/delete-domain
delete /v1/domains/{id}
# Get domain
Source: https://docs.usesend.com/api-reference/domains/get-domain
get /v1/domains/{id}
# List domains
Source: https://docs.usesend.com/api-reference/domains/list-domains
get /v1/domains
# Verify domain
Source: https://docs.usesend.com/api-reference/domains/verify-domain
put /v1/domains/{id}/verify
# Batch email
Source: https://docs.usesend.com/api-reference/emails/batch-email
post /v1/emails/batch
Send up to 100 emails in a single request.
# Cancel schedule
Source: https://docs.usesend.com/api-reference/emails/cancel-schedule
post /v1/emails/{emailId}/cancel
# Get email
Source: https://docs.usesend.com/api-reference/emails/get-email
get /v1/emails/{emailId}
# List emails
Source: https://docs.usesend.com/api-reference/emails/list-emails
get /v1/emails
# Send email
Source: https://docs.usesend.com/api-reference/emails/send-email
post /v1/emails
Send a transactional email via the public API.
# Update schedule
Source: https://docs.usesend.com/api-reference/emails/update-schedule
patch /v1/emails/{emailId}
# Introduction
Source: https://docs.usesend.com/api-reference/introduction
Fundamental concepts of Usend's API.
## Base URL
useSend's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported.
The Base URL for all API endpoints is:
```sh Terminal theme={null}
https://app.usesend.com/api/
```
## Authentication
Authentication to Usend's API is performed via the Authorization header with a Bearer token. To authenticate, you need to include the Authorization header with the word Bearer followed by your token in your API requests like so:
```sh Terminal theme={null}
Authorization: Bearer us_12345
```
You can create a new token/API key under your useSend [Developer Settings](https://app.usesend.com/dev-settings/api-keys).
# Changelog
Source: https://docs.usesend.com/changelog
Product updates and announcements from Unsend
### Campaign Personalization with Contact Books
Campaigns now support contact-book variables for personalization, so you can tailor each message with richer subscriber data.
### Double Opt-In Customization
Double opt-in now supports customizable confirmation flows and copy, making it easier to match your onboarding experience and compliance requirements.
### API and SDK Coverage
Contact book support has been expanded in the SDK, dashboard analytics is now available in both the public API and SDK, and a public endpoint to delete campaigns has been added.
### Webhook Filtering by Domain
Webhooks now support multi-domain filtering, making it easier to route, monitor, and manage events across different projects.
### Webhooks
You can now receive real-time notifications for email events via webhooks. Configure webhook endpoints to track deliveries, bounces, complaints, and more. [Learn more](https://docs.usesend.com/guides/webhooks)
### Contact Books API
Manage your contact books programmatically with the new public API. Create, update, and organize contacts at scale.
### Idempotency Support
API requests now support idempotency keys, ensuring safe retries without duplicate sends.
### Other improvements
* Export contact books to CSV for offline use.
* Security fixes for CVE-2025-55184 and CVE-2025-55183.
* Various bug fixes and performance improvements.
### Schedule Campaigns
You can now Schedule campaigns with natural language like "tomorrow at 9am" or "next Monday at 10am".
### Campaigns API
We're doubling down on APIs. Now you can create, schedule campaign emails from API. check [API documentation](/api-reference/introduction)
### Other improvements
* Camapign Emails are now sent in batches instead of a burst of emails.
* You can bulk upload large amounts of contacts without ui slowness.
* Added delete domain API.
### Suppression List
Hard bounce and complained email addresses are automatically added to the suppression list. Works for both transactional and marketing emails.
### Rebranding to useSend
From now on we [useSend](https://usesend.com). Make sure to update your docker image and sdks.
# Go
Source: https://docs.usesend.com/get-started/go
The useSend Go package lets you interact with the useSend API to send emails, manage contacts, and work with domains. This guide covers basic setup and usage.
## Prerequisites
* [useSend API key](https://app.usesend.com/dev-settings/api-keys)
* [Verified domain](https://app.usesend.com/domains)
## Installation
Install the useSend Go SDK:
```bash theme={null}
go get github.com/usesend/usesend-go
```
## Initialize
Create a new client using your API key.
```go theme={null}
package main
import (
"context"
"log"
usesend "github.com/usesend/usesend-go"
)
func main() {
client, err := usesend.NewClient("us_12345")
if err != nil {
log.Fatal(err)
}
}
```
API keys can also be supplied via the `USESEND_API_KEY` environment variable.
### Self-Hosted Setup
If you are running a self-hosted version of useSend, provide the base URL using `WithBaseURL`.
```go theme={null}
client, err := usesend.NewClient(
"us_12345",
usesend.WithBaseURL("https://app.usesend.com"),
)
if err != nil {
log.Fatal(err)
}
```
The base URL should be the root domain only. Do not include `/api/v1`, as the SDK manages API paths internally.
### Options
* `WithBaseURL(url string)`: Override the base API URL (e.g. for self-hosting)
* `WithHTTPClient(*http.Client)`: Provide a custom HTTP client
The default HTTP client uses a 30s timeout. Requests include a `User-Agent: usesend-go` header.
## Sending Emails
```go theme={null}
resp, errResp, err := client.Emails.Send(
context.Background(),
usesend.SendEmailPayload{
To: []string{"hello@acme.com"},
From: "hello@company.com",
Subject: "useSend email",
HTML: "
useSend is the best open source product to send emails
",
Text: "useSend is the best open source product to send emails",
Headers: map[string]string{
"X-Campaign": "welcome",
},
},
)
if err != nil {
log.Fatal(err)
}
if errResp != nil {
log.Fatalf("API error: %s", errResp.Message)
}
log.Printf("Email queued with ID: %s", resp.EmailID)
```
Custom headers are forwarded as-is. useSend only manages the `X-Usesend-Email-ID` and `References` headers.
## Managing Contacts
### Get Contact Book ID
Retrieve the contact book ID from the useSend dashboard.
### Create Contacts
```go theme={null}
contact, apiErr, err := client.Contacts.Create(
context.Background(),
"contactBook_123",
usesend.CreateContactPayload{
Email: "hey@koushik.dev",
FirstName: "Koushik",
LastName: "KM",
},
)
if err != nil {
log.Fatal(err) // transport error
}
if apiErr != nil {
log.Fatalf("API error: %s", apiErr.Message)
}
log.Printf("Contact ID: %s", contact.ContactID)
```
### Update Contacts
```go theme={null}
contact, apiErr, err := client.Contacts.Update(
context.Background(),
"contactBook_123",
"contact_456",
usesend.UpdateContactPayload{
FirstName: "Koushik",
LastName: "KM",
},
)
if err != nil {
log.Fatal(err)
}
if apiErr != nil {
log.Fatalf("API error: %s", apiErr.Message)
}
```
# Local Development
Source: https://docs.usesend.com/get-started/local
A guide on how to run useSend’s codebase locally
## Introduction
useSend's codebase is fully [open-source on github](https://github.com/usesend/usesend)
Here is the codebase structure
```
apps
├── docs
├── marketing
├── web
packages
├── eslint-config
├── sdk
├── tailwind-config
├── typescript-config
├── ui
```
The `apps` directory contains the code for:
* `web`: Code for our dashboard and email infra
* `marketing`: The code for the landing page of useSend
* `docs`: The documentation that you are currently reading.
The `packages` directory contains the code for:
* `eslint-config` package contains shared ESLint configuration settings
* `sdk` package contains TypeScript SDK for useSend REST API
* `tailwind-config` This package contains a shared Tailwind CSS configuration.
* `typescript-config` This package contains a shared typescript configuration
* `ui` This package is a collection of reusable UI components like buttons, badges, etc
## Running useSend locally
To run useSend locally, you will need to setup the following:
* [AWS](https://aws.amazon.com/) Free tier account will work.
* [Cloudflare](https://www.cloudflare.com/) Free tier account will work.
* [Docker](https://docs.docker.com/engine/install/) Recommended but not mandatory
## Fork and Clone the repo
Click on the fork button on [GitHub](https://github.com/usesend/usesend) to fork the repo
Once the repo is forked you can clone it on your local machine using:
```bash theme={null}
git clone https://github.com/your-username/usesend.git
```
## Install Dependencies
```bash theme={null}
corepack enable
```
```bash theme={null}
pnpm install
```
## Setup environment variables:
```bash theme={null}
cp .env.example .env
```
Use the following command to generate a key and add it under `NEXTAUTH_SECRET`
```bash theme={null}
openssl rand -base64 32
```
Create a symlink so that Next.js can access the root `.env` file:
```bash theme={null}
ln -s ../../.env apps/web/.env
```
You don't need this setup if you have `FROM_EMAIL` set in your environment
variables. for development email link will logged in the console.
Next, [create a new GitHub App](https://github.com/settings/applications/new). This will allow you to sign in to useSend with your GitHub account.
Add the homepage as:
```
http://localhost:3000/login
```
and callback URL as:
```
http://localhost:3000/api/auth/callback/github
```
Once the app is added you can add the Client ID under `GITHUB_ID`and CLIENT SECRET under `GITHUB_SECRET`
You don't need this setup if you are using the local-sen-sns image. But email
will not be sent out.
Next, we need to add in the [AWS credentials](https://docs.usesend.com/get-started/create-aws-credentials). Follow the detailed guide to get the AWS credentials with accurate permissions and add them in:
```
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
```
## Running useSend locally
We are using a local Postgresql server and a local Redis server. But if you don't have docker you can also manually set these up.
### Option 1: Using Docker Recommended
```bash theme={null}
pnpm d
```
Dashboard will be started on
```bash theme={null}
http://localhost:3000
```
Landing page will be started on
```bash theme={null}
http://localhost:3001
```
Run the following command to get the URL. Here is the more detailed guide by [cloudflare](https://developers.cloudflare.com/pages/how-to/preview-with-cloudflare-tunnel/)
```bash theme={null}
cloudflared tunnel --url http://localhost:3000
```
You can paste the URL provided by cloudflare in the Callback URL section
### Option 2: Using your own database or hosted database
```bash theme={null}
DATABASE_URL=""
REDIS_URL=""
```
```bash theme={null}
pnpm db:migrate-dev
```
```bash theme={null}
pnpm dev
```
Run the following command to get the URL. Here is the more detailed guide by [cloudflare](https://developers.cloudflare.com/pages/how-to/preview-with-cloudflare-tunnel/)
```bash theme={null}
cloudflared tunnel --url http://localhost:3000
```
You can paste the URL provided by cloudflare in the Callback URL section
## Run documentation
To run the documentation run the following command:
```bash theme={null}
pnpm dev:docs
```
# NodeJS
Source: https://docs.usesend.com/get-started/nodejs
Send your mail using useSend in NodeJS
## Prerequisites
* [useSend API key](https://app.usesend.com/dev-settings/api-keys)
* [Verified domain](https://app.usesend.com/domains)
## Using SDK
```bash npm theme={null}
npm install usesend-js
```
```bash yarn theme={null}
yarn add usesend-js
```
```bash pnpm theme={null}
pnpm add usesend-js
```
```bash bun theme={null}
bun add usesend-js
```
Get the API key from the [useSend dashboard](https://app.usesend.com/dev-settings/api-keys) and initialize the SDK
```javascript theme={null}
import { UseSend } from "usesend-js";
const usesend = new UseSend("us_12345");
```
If you are running a self-hosted version of useSend, pass the base URL as the
second argument:
```javascript theme={null}
const usesend = new UseSend("us_12345", "https://app.usesend.com");
```
```javascript theme={null}
usesend.emails.send({
to: "hello@acme.com",
from: "hello@company.com",
subject: "useSend email",
html: "
useSend is the best open source product to send emails
",
text: "useSend is the best open source product to send emails",
headers: {
"X-Campaign": "welcome",
},
});
```
> Custom headers are forwarded as-is. useSend only manages the `X-Usesend-Email-ID` and `References` headers.
## Adding contacts programatically
Get the contact book id from the [useSend dashboard](https://app.usesend.com/contacts/). Copy the contact book id
```javascript theme={null}
usesend.contacts
.create("clzeydgeygff", {
email: "hey@koushik.dev",
firstName: "Koushik",
lastName: "KM",
})
```
```javascript theme={null}
usesend.contacts.update("clzeydgeygff", contactId, {
firstName: "Koushik",
lastName: "KM",
});
```
# Python SDK
Source: https://docs.usesend.com/get-started/python
Official UseSend Python SDK for sending emails and managing contacts.
This guide shows how to install and use the official `usesend` Python SDK.
## Installation
Install from PyPI:
```bash theme={null}
pip install usesend
```
## Initialize
```python theme={null}
from usesend import UseSend, types
# Option A: pass values directly (helpful in scripts/tests)
client = UseSend("us_xxx")
# Option B: custom base URL (self-hosted)
client = UseSend("us_xxx", url="https://your-domain.example")
```
## Send an email
`EmailCreate` is a TypedDict for editor hints; at runtime you pass a regular dict. The client accepts `from` or `from_` (it normalizes `from_` to `from`).
```python theme={null}
from usesend import UseSend, types
client = UseSend("us_xxx")
payload: types.EmailCreate = {
"to": "user@example.com",
"from": "no-reply@yourdomain.com",
"subject": "Welcome",
"html": "Hello!",
"headers": {"X-Campaign": "welcome"},
}
data, err = client.emails.send(payload)
print(data or err)
```
useSend forwards your custom headers to SES. Only the `X-Usesend-Email-ID` and `References` headers are managed automatically.
Attachments and scheduling:
```python theme={null}
from datetime import datetime, timedelta
payload: types.EmailCreate = {
"to": ["user1@example.com", "user2@example.com"],
"from": "no-reply@yourdomain.com",
"subject": "Report",
"text": "See attached.",
"attachments": [
{"filename": "report.txt", "content": "SGVsbG8gd29ybGQ="}, # base64
],
"scheduledAt": datetime.utcnow() + timedelta(minutes=10),
}
data, err = client.emails.create(payload)
```
## Batch send
```python theme={null}
items: list[types.EmailBatchItem] = [
{"to": "a@example.com", "from": "no-reply@yourdomain.com", "subject": "A", "html": "
"},
]
data, err = client.emails.batch(items)
```
## Retrieve and manage emails
Get an email:
```python theme={null}
email, err = client.emails.get("email_123")
```
Update schedule time:
```python theme={null}
from datetime import datetime, timedelta
update: types.EmailUpdate = {"scheduledAt": datetime.utcnow() + timedelta(hours=1)}
data, err = client.emails.update("email_123", update)
```
Cancel a scheduled email:
```python theme={null}
data, err = client.emails.cancel("email_123")
```
## Contacts
All contact operations require a contact book ID (`book_id`).
Create a contact:
```python theme={null}
create: types.ContactCreate = {
"email": "user@example.com",
"firstName": "Jane",
"properties": {"plan": "pro"},
}
data, err = client.contacts.create("book_123", create)
```
Get a contact:
```python theme={null}
contact, err = client.contacts.get("book_123", "contact_456")
```
Update a contact:
```python theme={null}
update: types.ContactUpdate = {"subscribed": False}
data, err = client.contacts.update("book_123", "contact_456", update)
```
Upsert a contact:
```python theme={null}
upsert: types.ContactUpsert = {
"email": "user@example.com",
"firstName": "Jane",
}
data, err = client.contacts.upsert("book_123", "contact_456", upsert)
```
Delete a contact:
```python theme={null}
data, err = client.contacts.delete(book_id="book_123", contact_id="contact_456")
```
## Error handling
By default the client raises `UseSendHTTPError` for non-2xx responses. To handle errors as return values, pass `raise_on_error=False`.
```python theme={null}
from usesend import UseSend, UseSendHTTPError
# Raises exceptions on errors (default)
client = UseSend("us_xxx")
try:
data, _ = client.emails.get("email_123")
except UseSendHTTPError as e:
print("request failed:", e)
# Returns (None, error) instead of raising
client = UseSend("us_xxx", raise_on_error=False)
data, err = client.emails.get("email_123")
if err:
print("error:", err)
```
# SMTP support
Source: https://docs.usesend.com/get-started/smtp
A guide to integrate useSend with SMTP
## Prerequisites
You will need an API key and a verified domain to get the most out of this guide:
* [API Key](https://app.usesend.com/dev-settings/api-keys)
* [Verified Domain](https://app.usesend.com/domains)
## SMTP credentials
To set up your SMTP integration, you'll need to provide the following credentials:
* **Host:** `smtp.usesend.com`
* **Port:** `465`, `587`, `2465`, or `2587`
* **Username:** `usesend`
* **Password:** `YOUR-API-KEY`
## Example with Nodemailer
Following example with Nodemailer shows how you can send mails with SMTP support from useSend and Nodemailer.
```javascript theme={null}
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
host: "smtp.usesend.com",
port: 465,
secure: false,
auth: {
user: "usesend",
pass: "us_123",
},
tls: {
rejectUnauthorized: false,
},
});
const mailOptions = {
to: "sender@example.com",
from: "hello@example.com",
subject: "Testing SMTP",
html: "THIS IS USING SMTP,
useSend is the best open source sending platform
check out usesend.com",
text: "hello,\n\nuseSend is the best open source sending platform",
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error("Error sending email:", error);
} else {
console.log("Email sent successfully:", info.response);
}
});
```
# Campaign Personalization
Source: https://docs.usesend.com/guides/campaign-personalization
Use contact variables to personalize campaign content for each recipient
## Overview
Campaign personalization lets you pull values from your contacts into campaign emails. You can use built-in fields like `{{firstName}}` and `{{email}}`, plus custom variables such as `{{company}}`, `{{plan}}`, or `{{registrationCode}}`.
## How it works
Add the custom variable names you want to use, such as `company` or `plan`,
on the contact book.
Save the matching values on each contact through the dashboard, CSV import,
or API.
Select that contact book in your campaign and insert variables into the
subject or email content.
useSend replaces each variable with that contact's value when the campaign
is rendered.
## Available variables
These built-in variables are always available in campaigns:
* `{{email}}`
* `{{firstName}}`
* `{{lastName}}`
* `{{usesend_unsubscribe_url}}`
Custom variables come from the contact book's variable list.
Every campaign must include an unsubscribe link. Use the recipient-specific
`{{usesend_unsubscribe_url}}` variable as the link destination:
```html theme={null}
Unsubscribe
```
Custom variables must be added to the contact book before they can be used in
a campaign. Variable names can only contain letters, numbers, and underscores.
## Set up personalization in the dashboard
### 1. Add variables to your contact book
1. Go to [Contacts](https://app.usesend.com/contacts)
2. Create a new contact book or open an existing one
3. Add your variables as a comma-separated list, for example:
```text theme={null}
company, plan, registrationCode
```
### 2. Add values to your contacts
For each contact, save values for the variables you registered.
For example, a contact might look like this:
```json theme={null}
{
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"properties": {
"company": "Acme",
"plan": "Pro",
"registrationCode": "WELCOME-2026"
}
}
```
You can add these values:
* manually from the contact editor
* through CSV import using matching column names
* through the Contacts API using `properties`
### 3. Insert variables in your campaign
1. Open or create a campaign
2. Select the contact book that contains your variables
3. Insert variables into the subject or email body
Examples:
```text theme={null}
Subject: Welcome to {{company}}
Hi {{firstName}},
You're currently on the {{plan}} plan.
Your registration code is {{registrationCode}}.
```
If a contact book is selected, the campaign editor will suggest the available
variables automatically.
## Fallback values
If a variable might be empty, you can provide a fallback value:
```text theme={null}
Hi {{firstName,fallback=there}},
```
This renders `there` when `firstName` is empty.
## Using the API
### Create a contact book with variables
```bash theme={null}
curl -X POST https://app.usesend.com/api/v1/contactBooks \
-H "Authorization: Bearer us_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Customers",
"variables": ["company", "plan", "registrationCode"]
}'
```
### Create a contact with variable values
```bash theme={null}
curl -X POST https://app.usesend.com/api/v1/contactBooks/{contactBookId}/contacts \
-H "Authorization: Bearer us_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"firstName": "Jane",
"properties": {
"company": "Acme",
"plan": "Pro",
"registrationCode": "WELCOME-2026"
}
}'
```
### Create a campaign that uses variables
```bash theme={null}
curl -X POST https://app.usesend.com/api/v1/campaigns \
-H "Authorization: Bearer us_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome campaign",
"from": "Acme ",
"subject": "Welcome to {{company}}",
"contactBookId": "{contactBookId}",
"html": "
Hi {{firstName,fallback=there}}, your plan is {{plan}}.
"
}'
```
## Best practices
| Best practice | Why it helps |
| -------------------------------------------- | -------------------------------------------------------------------- |
| Register variables before importing contacts | Keeps CSV columns and API properties aligned with the contact book |
| Keep variable names simple | Makes templates easier to read and maintain |
| Use fallbacks for optional data | Prevents awkward empty spaces when a contact is missing a value |
| Test with a small segment first | Helps you verify rendered output before sending to a larger audience |
# Double Opt-In
Source: https://docs.usesend.com/guides/double-opt-in
Verify new subscribers with a confirmation email before adding them to your contact book
## Overview
Double opt-in requires new contacts to confirm their email address before they become subscribed. When enabled on a contact book, newly added contacts receive a confirmation email with a verification link. Only after clicking the link do they become fully subscribed.
**Why use double opt-in?**
* Ensures email addresses are valid and owned by the subscriber
* Reduces bounce rates and spam complaints
* Improves deliverability and sender reputation
* Helps comply with email marketing regulations (GDPR, CAN-SPAM)
## How it works
When a contact is added to a contact book with double opt-in enabled (via
dashboard, API, or CSV import), they are created with a **Pending** status
instead of being immediately subscribed.
A confirmation email is automatically sent to the contact with a unique
verification link. The link is signed with HMAC-SHA256 and expires after 7
days.
The contact clicks the verification link in the email and confirms their
subscription on the confirmation page.
The contact's status changes from **Pending** to **Subscribed** and they
will now receive your emails.
## Enabling double opt-in
### Via the dashboard
1. Go to [Contacts](https://app.usesend.com/contacts) and select a contact book
2. Click on the **Double Opt-In** tab
3. Toggle double opt-in on
4. Customize the confirmation email (optional)
5. Save your changes
### Via the API
Create a contact book with double opt-in enabled:
```bash theme={null}
curl -X POST https://app.usesend.com/api/v1/contactBooks \
-H "Authorization: Bearer us_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Newsletter Subscribers",
"doubleOptInEnabled": true,
"doubleOptInFrom": "Newsletter ",
"doubleOptInSubject": "Please confirm your subscription"
}'
```
Or enable it on an existing contact book:
```bash theme={null}
curl -X PATCH https://app.usesend.com/api/v1/contactBooks/{contactBookId} \
-H "Authorization: Bearer us_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"doubleOptInEnabled": true
}'
```
## Contact statuses
When double opt-in is enabled, contacts have three possible statuses:
| Status | Description |
| ---------------- | ---------------------------------------------------------------- |
| **Subscribed** | Contact has confirmed their subscription and will receive emails |
| **Pending** | Contact has been added but hasn't confirmed yet |
| **Unsubscribed** | Contact has explicitly unsubscribed |
Contacts with **Pending** status will not receive campaign emails. They will
only receive the double opt-in confirmation email.
## Customizing the confirmation email
You can customize three aspects of the confirmation email:
### From address
Set a custom sender address for confirmation emails. The address must use one of your verified domains.
```json theme={null}
{
"doubleOptInFrom": "Newsletter "
}
```
If not set, the confirmation email will be sent from the first available verified domain.
### Subject line
Customize the email subject. The default is "Please confirm your subscription".
```json theme={null}
{
"doubleOptInSubject": "Confirm your subscription to our newsletter"
}
```
### Email template
The confirmation email body can be customized using the useSend email editor (via the dashboard) or by providing the editor JSON content via the API.
#### Template variables
The following variables can be used in the confirmation email template:
| Variable | Description |
| -------------------- | -------------------------------- |
| `{{doubleOptInUrl}}` | The confirmation link (required) |
| `{{email}}` | The contact's email address |
| `{{firstName}}` | The contact's first name |
| `{{lastName}}` | The contact's last name |
The `{{ doubleOptInUrl }}` variable is **required** in the email template. The
confirmation email cannot be saved without it. This ensures every confirmation
email contains a working verification link.
## Resending confirmation emails
If a contact hasn't confirmed their subscription, you can resend the confirmation email from the dashboard:
1. Go to your contact book and find the pending contact
2. Click the **Resend** button next to the contact
Each resend generates a new confirmation link with a fresh 7-day expiration window.
## Best practices
| Best practice | Why it helps |
| ---------------------------------- | -------------------------------------------------------------------------------- |
| Set up a verified domain first | Double opt-in emails need a verified domain before they can be sent |
| Keep the confirmation email simple | Makes it easier for subscribers to understand and complete the confirmation step |
| Use a recognizable from address | Reduces the chance of the confirmation email being ignored or marked as spam |
| Monitor pending contacts | Helps you spot low confirmation rates and improve your signup flow |
# Use with React Email
Source: https://docs.usesend.com/guides/use-with-react-email
A guide on how to use useSend with React Email
## Introduction
[React Email](https://react.email/docs/introduction) is a library for building emails with React. In this guide, we will show you how to use useSend with React Email.
## Install dependencies
```sh npm theme={null}
npm install usesend-js @react-email/render
```
```sh yarn theme={null}
yarn add usesend-js @react-email/render
```
```sh pnpm theme={null}
pnpm add usesend-js @react-email/render
```
```sh bun theme={null}
bun add usesend-js @react-email/render
```
## Create an email template
```tsx theme={null}
import * as React from "react";
import { Html } from "@react-email/html";
import { Button } from "@react-email/button";
export function Email(props) {
const { url } = props;
return (
);
}
```
## Send an email using useSend
```ts theme={null}
import { UseSend } from "usesend-js";
import { render } from "@react-email/render";
import { Email } from "./email";
const usesend = new UseSend("us_your_usesend_api_key");
const html = await render();
const response = await usesend.emails.send({
to: "hello@usesend.com",
from: "hello@usesend.com",
subject: "useSend email",
html,
});
```
## Build your project
### JavaScript
If you're using nodejs, importing `email.jsx` might fail. make sure to add these to your babel config:
```js theme={null}
{
"plugins": ["@babel/plugin-proposal-class-properties"]
}
```
Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-js)
### TypeScript
Just add `jsx` to your `tsconfig.json`
```json theme={null}
{
"compilerOptions": { "jsx": "react-jsx" }
}
```
Checkout this [example](https://github.com/usesend/unsend-js-examples/tree/main/react-email-ts)
# Webhooks
Source: https://docs.usesend.com/guides/webhooks
Receive real-time notifications when events occur in your useSend account
## Overview
Webhooks allow you to receive HTTP POST requests to your server when events occur in useSend, such as when an email is delivered, bounced, or clicked. This enables you to build real-time integrations and automate workflows.
## Setting up webhooks
Create an endpoint on your server that can receive POST requests. The endpoint must:
* Accept POST requests with JSON body
* Return a 2xx status code to acknowledge receipt
* Respond within 10 seconds
Go to [Webhooks](https://app.usesend.com/webhooks) in your useSend dashboard and create a new webhook:
* Enter your endpoint URL
* Select which events you want to receive
* Copy the signing secret for verification
Always verify webhook signatures to ensure requests are from useSend. See the [Signature Verification](#signature-verification) section below.
## Event types
### Email events
| Event | Description |
| ------------------------- | ---------------------------------------------------- |
| `email.queued` | Email has been queued for sending |
| `email.sent` | Email has been sent to the recipient's mail server |
| `email.delivered` | Email was successfully delivered |
| `email.delivery_delayed` | Email delivery is being retried |
| `email.bounced` | Email bounced (permanent or temporary) |
| `email.rejected` | Email was rejected |
| `email.rendering_failure` | Email failed during template rendering |
| `email.complained` | Recipient marked email as spam |
| `email.failed` | Email failed to send |
| `email.cancelled` | Scheduled email was cancelled |
| `email.suppressed` | Email was suppressed (recipient on suppression list) |
| `email.opened` | Recipient opened the email |
| `email.clicked` | Recipient clicked a link in the email |
### Contact events
| Event | Description |
| ----------------- | ----------------------- |
| `contact.created` | New contact was created |
| `contact.updated` | Contact was updated |
| `contact.deleted` | Contact was deleted |
### Domain events
| Event | Description |
| ----------------- | ----------------------------- |
| `domain.created` | New domain was added |
| `domain.verified` | Domain verification completed |
| `domain.updated` | Domain settings were updated |
| `domain.deleted` | Domain was deleted |
## Webhook payload
Each webhook request includes a JSON payload with the following structure. See [Event data details](#event-data-details) for details on the `data` field for each event type.
```json theme={null}
{
"id": "call_abc123",
"type": "email.delivered",
"version": "2026-01-18",
"createdAt": "2024-01-15T10:30:00.000Z",
"teamId": 123,
"data": {
"id": "email_123",
"status": "DELIVERED",
"from": "sender@example.com",
"to": ["recipient@example.com"],
"subject": "Welcome!",
"occurredAt": "2024-01-15T10:30:00Z"
},
"attempt": 1
}
```
### Payload fields
| Field | Description |
| ----------- | ------------------------------------------ |
| `id` | Unique identifier for this webhook call |
| `type` | The event type (e.g., `email.delivered`) |
| `version` | API version for the payload format |
| `createdAt` | When the event was created |
| `teamId` | Your team ID |
| `data` | Event-specific data (varies by event type) |
| `attempt` | Delivery attempt number (1-6) |
## Request headers
Each webhook request includes the following headers:
| Header | Description |
| --------------------- | -------------------------------------- |
| `X-UseSend-Signature` | HMAC-SHA256 signature for verification |
| `X-UseSend-Timestamp` | Unix timestamp in milliseconds |
| `X-UseSend-Event` | Event type |
| `X-UseSend-Call` | Unique webhook call ID |
| `X-UseSend-Retry` | `true` if this is a retry attempt |
## Signature verification
Always verify webhook signatures to ensure requests are authentic. The signature is computed as:
```
HMAC-SHA256(secret, "${timestamp}.${rawBody}")
```
### Using the SDK (Recommended)
```bash npm theme={null}
npm install usesend-js
```
```bash yarn theme={null}
yarn add usesend-js
```
```bash pnpm theme={null}
pnpm add usesend-js
```
```bash bun theme={null}
bun add usesend-js
```
### Next.js App Router
```typescript theme={null}
import { UseSend } from "usesend-js";
const usesend = new UseSend("us_your_api_key");
const webhooks = usesend.webhooks(process.env.USESEND_WEBHOOK_SECRET!);
export async function POST(request: Request) {
try {
const rawBody = await request.text();
const event = webhooks.constructEvent(rawBody, {
headers: request.headers,
});
switch (event.type) {
case "email.delivered":
console.log("Email delivered to:", event.data.to);
break;
case "email.bounced":
console.log("Email bounced:", event.data.id);
break;
case "email.opened":
console.log("Email opened:", event.data.id);
break;
}
return new Response("ok");
} catch (error) {
console.error("Webhook error:", error);
return new Response((error as Error).message, { status: 400 });
}
}
```
### Express
```typescript theme={null}
import express from "express";
import { Webhooks } from "usesend-js";
const webhooks = new Webhooks(process.env.USESEND_WEBHOOK_SECRET!);
const app = express();
// Important: Use raw body parser for webhook routes
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = webhooks.constructEvent(req.body, {
headers: req.headers,
});
switch (event.type) {
case "email.delivered":
console.log("Email delivered to:", event.data.to);
break;
case "email.bounced":
console.log("Email bounced:", event.data.id);
break;
}
res.status(200).send("ok");
} catch (error) {
console.error("Webhook error:", error);
res.status(400).send((error as Error).message);
}
});
app.listen(3000);
```
### Verification only
If you only need to verify the signature without parsing:
```typescript theme={null}
const isValid = webhooks.verify(rawBody, { headers: request.headers });
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
```
### Manual verification
If you prefer to verify manually without the SDK:
```typescript theme={null}
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhook(
secret: string,
rawBody: string,
signature: string,
timestamp: string,
): boolean {
const expectedSignature = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const expected = Buffer.from(`v1=${expectedSignature}`, "utf8");
const received = Buffer.from(signature, "utf8");
if (expected.length !== received.length) {
return false;
}
return timingSafeEqual(expected, received);
}
// Usage
const signature = request.headers.get("X-UseSend-Signature");
const timestamp = request.headers.get("X-UseSend-Timestamp");
const isValid = verifyWebhook(secret, rawBody, signature, timestamp);
```
## Retry behavior
If your endpoint doesn't return a 2xx response, useSend will retry delivery with exponential backoff:
| Attempt | Delay |
| ------- | ------------ |
| 1 | Immediate |
| 2 | \~5 seconds |
| 3 | \~10 seconds |
| 4 | \~20 seconds |
| 5 | \~40 seconds |
| 6 | \~80 seconds |
After 6 failed attempts, the webhook call is marked as failed.
If your webhook endpoint fails 30 consecutive calls, the webhook will be
automatically disabled to prevent continued failures. You can re-enable it
from the dashboard.
## Best practices
Return a 2xx response as soon as possible. Process webhook data
asynchronously if needed. Requests timeout after 10 seconds.
Use the `id` field in the payload to deduplicate events. In rare cases, the
same event may be delivered more than once.
Always verify the `X-UseSend-Signature` header to ensure requests are from
useSend and haven't been tampered with.
The SDK rejects signatures older than 5 minutes by default. This prevents
replay attacks.
Always use HTTPS endpoints in production to encrypt webhook data in transit.
## Testing webhooks
You can send a test webhook from the dashboard to verify your endpoint is working correctly:
1. Go to [Webhooks](https://app.usesend.com/webhooks)
2. Click on your webhook
3. Click "Send Test" to send a test event
The test event will have type `webhook.test` with the following payload:
```json theme={null}
{
"test": true,
"webhookId": "wh_abc123",
"sentAt": "2024-01-15T10:30:00.000Z"
}
```
## Troubleshooting
* Verify your endpoint URL is correct and publicly accessible - Check that
your endpoint returns a 2xx status code - Ensure the webhook is set to
ACTIVE status in the dashboard - Check if the webhook was auto-disabled due
to consecutive failures
* Use the raw request body, not parsed JSON - Ensure you're using the
correct webhook secret - Check that the timestamp hasn't expired (5 minute
window) - Verify you're computing the HMAC correctly: `HMAC-SHA256(secret,
"${timestamp}.${rawBody}")`
After 30 consecutive failed calls, webhooks are automatically disabled. Fix
the issue with your endpoint, then re-enable the webhook from the dashboard.
The failure counter resets on the next successful delivery.
## Event data details
This section documents the `data` field structure for each event type.
### Email events
Most email events share a common base structure:
```typescript theme={null}
{
id: string; // Email ID
status: string; // Email status (e.g., "DELIVERED", "BOUNCED")
from: string; // Sender email address
to: string[]; // Recipient email addresses
occurredAt: string; // ISO 8601 timestamp
subject?: string; // Email subject
campaignId?: string; // Campaign ID (if from a campaign)
contactId?: string; // Contact ID (if sent to a contact)
domainId?: number; // Domain ID
templateId?: string; // Template ID (if using a template)
metadata?: object; // Custom metadata you attached to the email
}
```
#### email.bounced
Includes additional bounce details:
```typescript theme={null}
{
// ... base email fields
bounce: {
type: "Transient" | "Permanent" | "Undetermined";
subType: "General" | "NoEmail" | "Suppressed" | "OnAccountSuppressionList"
| "MailboxFull" | "MessageTooLarge" | "ContentRejected" | "AttachmentRejected";
message?: string; // Bounce message from the mail server
}
}
```
#### email.failed
Includes failure reason:
```typescript theme={null}
{
// ... base email fields
failed: {
reason: string; // Failure reason
}
}
```
#### email.suppressed
Includes suppression details:
```typescript theme={null}
{
// ... base email fields
suppression: {
type: "Bounce" | "Complaint" | "Manual";
reason: string; // Why the email was suppressed
source?: string; // Source of the suppression
}
}
```
#### email.opened
Includes open tracking details:
```typescript theme={null}
{
// ... base email fields
open: {
timestamp: string; // When the email was opened
userAgent?: string; // Browser/client user agent
ip?: string; // IP address
platform?: string; // Detected platform
}
}
```
#### email.clicked
Includes click tracking details:
```typescript theme={null}
{
// ... base email fields
click: {
timestamp: string; // When the link was clicked
url: string; // The clicked URL
userAgent?: string; // Browser/client user agent
ip?: string; // IP address
platform?: string; // Detected platform
}
}
```
### Contact events
All contact events (`contact.created`, `contact.updated`, `contact.deleted`) include:
```typescript theme={null}
{
id: string; // Contact ID
email: string; // Contact email address
contactBookId: string; // Contact book ID
subscribed: boolean; // Subscription status
properties: object; // Custom properties
firstName?: string; // First name
lastName?: string; // Last name
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
}
```
### Domain events
All domain events (`domain.created`, `domain.verified`, `domain.updated`, `domain.deleted`) include:
```typescript theme={null}
{
id: number; // Domain ID
name: string; // Domain name (e.g., "example.com")
status: string; // Domain status
region: string; // AWS region
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
clickTracking: boolean; // Click tracking enabled
openTracking: boolean; // Open tracking enabled
subdomain?: string; // Subdomain for tracking
dkimStatus?: string; // DKIM verification status
spfDetails?: string; // SPF record details
dmarcAdded?: boolean; // DMARC record added
}
```
# Introduction
Source: https://docs.usesend.com/introduction
useSend is open source alternative to Resend, Sendgrid, Mailgun and Postmark etc.
## Setting up
Quicklinks to set up your account and get started
Add domains to send emails
Generate API key to send emails from your app.
Learn how to use our API to send emails programmatically.
Learn how to use our SDK using NodeJS to send emails programmatically.
Learn how to use our SDK using Python to send emails programmatically.
Send emails with SMTP server instead of REST API.
# Self hosting useSend
Source: https://docs.usesend.com/self-hosting/overview
An end-to-end guide on how to self-host useSend. An open-source sending infrastructure for developers.
## Prerequisites
* A [GitHub](https://github.com) account
* An [AWS](https://aws.amazon.com) account
If you have any questions join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.
## Step 1: Environment variables
useSend depends on AWS SES to send emails and SNS to receive email status. Along with that it also depends on Postgres as a database and Redis for queue. Copy the `.env.selfhost.example` file to `.env` and fill in the values.
tl;dr: Login to your AWS console and create a new user with programmatic access. Attach the `AmazonSNSFullAccess` and `AmazonSESFullAccess` policies to the user. Then create a new access key for the user.
Add the following environment variables.
```env theme={null}
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
```
Follow this for detailed steps: [Create AWS
credentials](/get-started/create-aws-credentials)
useSend uses GitHub authentication for login.
Use this link to [create an github app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
Callback URL : `https:///api/auth/callback/github`
Under **Permissions & events**, set **Account permissions → Email addresses**
to **Read-only**. useSend needs the authenticated user's email address to match
new accounts to team invitations.
Add the following environment variables.
```env theme={null}
GITHUB_ID=""
GITHUB_SECRET=""
```
If you want email/password login, please help us out with the [code](https://github.com/usesend/usesend)
useSend uses Postgres as a database and Redis as a queue. You need to create a new database and add the following environment variables.
If you're using docker-compose, it's all automatically done for you.
```env theme={null}
DATABASE_URL="postgres://:@:/"
REDIS_URL="redis://:@:"
```
If you're sharing a Redis instance across multiple apps and using Redis ACL for isolation, set `REDIS_KEY_PREFIX` to namespace all keys (e.g. `REDIS_KEY_PREFIX="usesend"` prefixes all keys with `usesend:`).
Url is the app url you're going to use and secret is random string. You can generate a random secret using this command.
```sh theme={null}
openssl rand -base64 32
```
Add the following environment variables.
```env theme={null}
NEXTAUTH_URL="https://"
NEXTAUTH_SECRET=""
```
By default, stored email content stays in the database forever. To bound database growth, set `EMAIL_CLEANUP_DAYS` to a positive number of days. A daily job (midnight UTC) then clears the `text`, `html`, `attachments` and `headers` values for emails older than the cutoff. Delivery status and event history are kept.
```env theme={null}
EMAIL_CLEANUP_DAYS=30
```
Leave it unset to keep email content indefinitely.
## Step 2: Setting up the app
You can use any platform that supports Docker to host useSend. If you have any questions drop in the [discord channel](https://discord.gg/gbsvjb9MqV) and we'll try to help you out.
### Docker
Follow this guide to setup your docker instance: [Set up docker](/get-started/set-up-docker)
[](https://hub.docker.com/r/usesend/usesend)
## Step 3: Setting up a region
In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted. If you're confused just use `us-east-1`.
You can check available regions [here](https://docs.aws.amazon.com/general/latest/gr/ses.html)
Once you log in to useSend, it will prompt you add SES configuration.
* Add the region
* Add the callback url, which is basically the app url. Note this should be accesible from internet. This is how you get the delivery status of the emails.
* You don't need to update the send rate, it's automatically based on your account.
## Step 4: Getting out of sandbox mode
Don't forget to get the SES account out of sandbox mode.
## Step 5: SMTP Proxy Server (Optional)
The SMTP proxy server is an optional component that allows applications to send emails through useSend using standard SMTP protocol instead of the REST API. This is useful for legacy applications, email clients, or any software that needs to send emails via SMTP.
The complete source code for the SMTP proxy server is available at:
[usesend/usesend/tree/main/apps/smtp-server](https://github.com/usesend/usesend/tree/main/apps/smtp-server)
### When to use the SMTP proxy:
* **Legacy applications** that only support SMTP
* **Email clients** like Thunderbird, Outlook, Apple Mail
* **Applications** that can't easily integrate with REST APIs
* **Third-party software** that requires SMTP configuration
### Docker Compose Setup
Create a `docker-compose.yml` file for the SMTP server:
```yaml theme={null}
name: usesend-smtp-server
services:
smtp-server:
container_name: usesend-smtp-server
image: usesend/smtp-proxy:latest
environment:
SMTP_AUTH_USERNAME: "usesend" # Username for SMTP authentication
USESEND_BASE_URL: "https://your-usesend-instance.com" # Your useSend instance URL
# Optional: SSL certificate paths for secure connections
# USESEND_API_KEY_PATH: "/certs/server.key"
# USESEND_API_CERT_PATH: "/certs/server.crt"
# Optional: Mount SSL certificates
# volumes:
# - ./certs/server.key:/certs/server.key:ro
# - ./certs/server.crt:/certs/server.crt:ro
ports:
- "25:25" # Standard SMTP
- "587:587" # SMTP with STARTTLS
- "2587:2587" # Alternative SMTP port
- "465:465" # SMTP over SSL/TLS
- "2465:2465" # Alternative SMTPS port
restart: unless-stopped
```
Run the SMTP server:
```bash theme={null}
docker-compose up -d
```
### SMTP Configuration
To send emails through the proxy, configure your application with these SMTP settings:
* **Host**: Your server's IP address or domain
* **Ports**: 25, 587 (STARTTLS), 465 (SSL/TLS), 2587, or 2465
* **Username**: `usesend` (or your custom `SMTP_AUTH_USERNAME`)
* **Password**: Your useSend API key
* **Encryption**: STARTTLS (ports 25, 587, 2587) or SSL/TLS (ports 465, 2465)
The SMTP proxy forwards all emails to your useSend instance, so make sure your
main useSend application is running and accessible.
Ensure your firewall allows traffic on the SMTP ports you're using. For
production deployments, consider using non-standard ports (2587, 2465) to
avoid conflicts.
## Next steps
You're all set up now.
* Setup a domain.
* Create an API key.
* Start sending emails.
If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.
A community member shared a short write-up on hosting useSend with
[Coolify](https://mattstein.com/thoughts/coolify-unsend/). Give it a read if
you need another reference.
# Self hosting with Railway
Source: https://docs.usesend.com/self-hosting/railway
Deploy useSend on Railway with one click.
useSend is a [Railway Partner](https://railway.app/partners?utm_medium=integration\&utm_source=docs\&utm_campaign=usesend) and Railway offers the quickest way to get a useSend instance running.
[](https://railway.com/deploy/usesend?utm_medium=integration\&utm_source=docs\&utm_campaign=usesend)
## Prerequisites
* A [GitHub](https://github.com) account
* An [AWS](https://aws.amazon.com) account
If you have any questions join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.
## Step 1: Deploy on Railway
1. Click the button above and choose **Deploy now**.
2. Wait for the deployment to complete and open the service.
3. Note the assigned Railway domain (you can add a custom domain later).
4. You can redeploy from the dashboard anytime to pull the latest image.
## Step 2: Environment variables
useSend depends on AWS SES to send emails and SNS to receive email status. The Railway template provisions Postgres (database) and Redis (queue) automatically—no action needed for those. Add the following variables under your service’s Variables tab.
tl;dr: In the AWS Console, create a new IAM user with programmatic access. Attach `AmazonSNSFullAccess` and `AmazonSESFullAccess`. Then create an access key.
Add the following environment variables in Railway.
```env theme={null}
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
```
Follow this for detailed steps: [Create AWS
credentials](/get-started/create-aws-credentials)
useSend uses GitHub authentication for login.
Use this link to [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)
Callback URL : `https:///api/auth/callback/github`
Under **Permissions & events**, set **Account permissions → Email addresses**
to **Read-only**. useSend needs the authenticated user's email address to match
new accounts to team invitations.
Add the following environment variables in Railway.
```env theme={null}
GITHUB_ID=""
GITHUB_SECRET=""
```
Set your public app URL for NextAuth.
Add the following variables in Railway:
```env theme={null}
NEXTAUTH_URL="https://"
```
After adding variables, click **Redeploy** so the service picks up the new
configuration.
## Step 3: Optional — Custom domain
* Add a custom domain in Railway and configure DNS.
* Update `NEXTAUTH_URL` to your new domain (e.g., `https://mail.yourdomain.com`).
* Redeploy to apply the change.
## Step 4: Set up SES region in useSend
In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted. If you're unsure, use `us-east-1`.
You can check available regions [here](https://docs.aws.amazon.com/general/latest/gr/ses.html)
Once you log in to useSend, it will prompt you add SES configuration.
* Add the region
* Add the callback url, which is basically the app url. Note this should be accessible from the internet. This is how you get the delivery status of the emails.
* You don't need to update the send rate, it's automatically based on your account.
## Step 5: Get out of SES sandbox
Don't forget to get the SES account out of sandbox mode.
## Next steps
You're all set up.
* Set up a domain.
* Create an API key.
* Start sending emails.
If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord.