Connect your app in four steps
Open Mail sends emails through your own SMTP account. You will need a customer account, SMTP credentials and a backend that can make HTTP requests.
https://openmail.vishnudeepak.com/apiFollow steps 1–4 below. After your first email works, add attachments or scheduling if needed.
1. Connect your SMTP account
- Log in to Open Mail and open SMTP accounts.
- Click Add SMTP. Enter your provider’s hostname, username, password and authorized sender email.
- Choose STARTTLS / 587 or TLS / 465, then save.
- Click Test connection. If it fails, check your provider’s credentials.
- Click Copy ID. Save this value as
OPEN_MAIL_SMTP_IDon your server.
A successful connection test verifies SMTP access. Your provider must also authorize the sender address before it can accept email.
2. Create your API key
- Open API keys and click Create key.
- Give it a name, such as your website’s name.
- Copy the key now; it is shown only once.
- Save it as
OPEN_MAIL_API_KEYin your backend’s environment.
Authorization: Bearer YOUR_API_KEYKeep keys in server environment variables. Do not put them in browser JavaScript, URLs, repositories or mobile app bundles. Your key can access only your own emails and attachments.
SMTP credentials, connection tests and API-key management use the dashboard login session. GET /smtp does not accept API keys; copy the SMTP ID in the dashboard instead.
Regenerating a key invalidates its previous value immediately. Revoking a key blocks subsequent requests. Blocking a customer also pauses their queued sends; an already-running SMTP send may finish.
3. Send your first email
Run this command from your terminal. Replace YOUR_API_KEY, YOUR_SMTP_ID and the recipient address with your own values.
curl 'https://openmail.vishnudeepak.com/api/emails' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: welcome-user-123' \
-d '{
"smtp_id": "YOUR_SMTP_ID",
"recipients": ["customer@example.com"],
"subject": "Welcome",
"body": "Thanks for signing up.",
"from_name": "My App"
}'{
"id": "example-email-id",
"status": "queued"
}Save the returned id for step 4. queued means Open Mail accepted the request and will attempt delivery.
Request fields and HTML email
POST /emails requires JSON, a bearer token and an Idempotency-Key header.
| Field | Required | Value |
|---|---|---|
| smtp_id | Yes | ID of an active SMTP account you own. |
| recipients | Yes | Array of 1–50 email addresses. Comma-separated string also accepted. |
| subject | Yes | Non-empty string, up to 998 characters; no line breaks. |
| body | Yes | Non-empty string, up to 50,000 characters. |
| body_format | No | text (default) or html. |
| from_name | No | Sender display name; defaults to your saved sender name or account name, capped at 100 characters. |
| reply_to | No | Single reply email address, e.g. support@example.com. Defaults to the SMTP sender. |
| attachment_ids | No | Array of uploaded file IDs, up to 10 files and 10 MB combined. |
| scheduled_at | No | ISO timestamp with timezone; within one year. |
The sender email address comes from your SMTP account; there is no per-request sender-address override. CC, BCC and delivery webhooks are not currently supported.
{
"smtp_id": "YOUR_SMTP_ID",
"recipients": [
"customer@example.com"
],
"subject": "Welcome",
"body_format": "html",
"body": "<h1>Welcome</h1><p>Thanks for joining.</p>"
}4. Check your email status
Replace EMAIL_ID with the ID returned in step 3, and use the same API key.
| Method | Path | Operation | Success |
|---|---|---|---|
| POST | /emails | Queue an email | 202 |
| GET | /emails | Paginated emails, newest first | 200 |
| GET | /emails/:id | Read one email record | 200 |
| DELETE | /emails/:id | Cancel a queued email | 200 |
| POST | /attachments | Upload a private file | 201 |
| GET | /attachments/:id | Download a private file | 200 |
| DELETE | /attachments/:id | Delete a file not used by pending mail | 200 |
curl 'https://openmail.vishnudeepak.com/api/emails/EMAIL_ID' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"id": "example-email-id",
"status": "sent",
"attempts": 1,
"error": null,
"provider_id": "<example-message-id>",
"created_at": 1790000000000,
"sent_at": 1790000005000
}Email records include source: api or dashboard. Records created before source tracking show unknown. Timestamps in records are Unix milliseconds. recipients and attachment_ids are currently JSON-encoded strings in read responses; parse them if needed. Listing returns an array with 20 records per page by default. Use ?page=2&per_page=20 to request another page (maximum 20 records). Pagination metadata is in X-Total-Count, X-Page, X-Page-Size and X-Total-Pages response headers. Search with q; filter with status=pending, sent, attention, cancelled or an individual delivery status. Filters apply before pagination.
queued: waiting for the scheduler, retry or sending capacity.sending: the consumer has claimed the email.sent: SMTP server accepted it; inbox delivery is not confirmed.failed: permanent error or exhausted retries. Readerror.partial: some recipients were rejected; investigate before retrying.uncertain: delivery was interrupted and acceptance is unknown. Check your SMTP provider before resending.cancelled: stopped while queued.
curl -X DELETE 'https://openmail.vishnudeepak.com/api/emails/EMAIL_ID' \
-H 'Authorization: Bearer YOUR_API_KEY'Cancellation returns {"ok":true}. A message already sending or completed returns 409. Poll status at a reasonable interval, such as every 10–30 seconds, and stop at a final status.
Use your programming language
Choose an example for your backend. Set OPEN_MAIL_BASE_URL to https://openmail.vishnudeepak.com/api, plus the SMTP ID and API key from steps 1–2.
Node.js
// Node.js 22+. Run on your server, not in browser JavaScript.
const base = process.env.OPEN_MAIL_BASE_URL; // https://openmail.vishnudeepak.com/api
const apiKey = process.env.OPEN_MAIL_API_KEY;
const smtpId = process.env.OPEN_MAIL_SMTP_ID;
async function sendWelcome(customerEmail, eventId) {
const response = await fetch(base + '/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + apiKey,
'Content-Type': 'application/json',
'Idempotency-Key': eventId // Persist and reuse for this event.
},
body: JSON.stringify({
smtp_id: smtpId,
recipients: [customerEmail],
subject: 'Welcome',
body: 'Thanks for signing up.',
from_name: 'My App'
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(response.status + ': ' + result.error);
}
return result; // { id, status: 'queued' }
}
console.log(await sendWelcome('customer@example.com', 'welcome-user-123'));Python
# Python 3; standard library only. Run on your server.
import json, os
from urllib.request import Request, urlopen
from urllib.error import HTTPError
payload = {
'smtp_id': os.environ['OPEN_MAIL_SMTP_ID'],
'recipients': ['customer@example.com'],
'subject': 'Welcome',
'body': 'Thanks for signing up.',
'from_name': 'My App'
}
request = Request(
os.environ['OPEN_MAIL_BASE_URL'] + '/emails',
data=json.dumps(payload).encode('utf-8'),
headers={
'Authorization': 'Bearer ' + os.environ['OPEN_MAIL_API_KEY'],
'Content-Type': 'application/json',
'Idempotency-Key': 'welcome-user-123'
},
method='POST'
)
try:
with urlopen(request, timeout=30) as response:
print(json.load(response))
except HTTPError as error:
print(error.code, error.read().decode('utf-8'))Private attachments
Upload the raw file first, then include its returned ID in your email request. Do not send multipart forms or Base64 JSON.
curl 'https://openmail.vishnudeepak.com/api/attachments' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/pdf' \
-H 'X-Filename: invoice.pdf' \
--data-binary @invoice.pdfContent-Length is required; cURL supplies it for a local file. Percent-encode X-Filename when it contains non-ASCII or special characters. Maximum upload: 10,000,000 bytes.
{
"id": "example-file-id",
"filename": "invoice.pdf",
"size": 12345
}{
"smtp_id": "YOUR_SMTP_ID",
"recipients": [
"customer@example.com"
],
"subject": "Invoice",
"body": "Your invoice is attached.",
"attachment_ids": [
"example-file-id"
]
}GET /attachments/:id returns the file bytes. DELETE /attachments/:id returns {"ok":true}. Both require a bearer token. Deleting a file referenced by queued or sending mail returns 409.
Files remain private. Cleanup removes files older than 15 days when no queued or sending email references them. SMTP providers may impose lower message limits; email encoding increases attachment size by roughly one third.
Scheduling
{
"scheduled_at": "2027-01-01T09:00:00Z"
}Use an explicit timezone, such as Z for UTC or +05:30. Choose a future time within one year. Due messages are picked up approximately every minute; provider throttling and queue load can delay sending. Omit this field to queue immediately.
Errors & retries
{
"error": "Customer rate limit reached. Retry next minute."
}| HTTP | Meaning / action |
|---|---|
| 400 | Invalid fields, missing idempotency key, inactive SMTP account or invalid attachment. Correct the request. |
| 401 | Missing, invalid or revoked key; account may be blocked. Check credentials. |
| 403 | Operation requires a dashboard session, or browser origin was rejected. |
| 404 | Record is missing, expired or belongs to another customer. |
| 409 | Idempotency conflict, non-queued cancellation or file in use. |
| 413 | JSON or attachment is too large. |
| 415 | JSON endpoint received the wrong Content-Type. |
| 429 | Customer submission limit reached. Retry after the minute window resets, with backoff. |
| 500 | Server error. Retry the same request with the same idempotency key. |
Use a unique idempotency key for each logical email (maximum 128 characters). If a request times out or fails, reuse the same key and unchanged JSON payload. Repeating it returns the existing email ID with HTTP 200; changing the payload returns 409. Preserve the original serialization and property order when retrying.
Do not create a new key for a network retry. SMTP delivery itself is not exactly once. The worker attempts transient failures up to three times; partial or uncertain outcomes require review.
Limits
- 10 MB (10,000,000 bytes) total attachments per email; maximum 10 files.
- 100,000 bytes per JSON request, including UTF-8 encoding and attachments metadata.
- Per-customer submission and send-attempt limits, configured by the site owner.
- One active SMTP send per account. Sender address must be allowed by your provider.
- Only TLS port 465 and STARTTLS port 587. Port 25 is unsupported.
- Use this API from your backend. Cross-origin browser writes are rejected.