Open Mail

API documentation

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.

API base URLhttps://openmail.vishnudeepak.com/api

Follow steps 1–4 below. After your first email works, add attachments or scheduling if needed.

1. Connect your SMTP account

  1. Log in to Open Mail and open SMTP accounts.
  2. Click Add SMTP. Enter your provider’s hostname, username, password and authorized sender email.
  3. Choose STARTTLS / 587 or TLS / 465, then save.
  4. Click Test connection. If it fails, check your provider’s credentials.
  5. Click Copy ID. Save this value as OPEN_MAIL_SMTP_ID on 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

  1. Open API keys and click Create key.
  2. Give it a name, such as your website’s name.
  3. Copy the key now; it is shown only once.
  4. Save it as OPEN_MAIL_API_KEY in your backend’s environment.
Required header
Authorization: Bearer YOUR_API_KEY

Keep 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.

Terminal · cURL
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"
  }'
Expected response · HTTP 202
{
  "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.

FieldRequiredValue
smtp_idYesID of an active SMTP account you own.
recipientsYesArray of 1–50 email addresses. Comma-separated string also accepted.
subjectYesNon-empty string, up to 998 characters; no line breaks.
bodyYesNon-empty string, up to 50,000 characters.
body_formatNotext (default) or html.
from_nameNoSender display name; defaults to your saved sender name or account name, capped at 100 characters.
reply_toNoSingle reply email address, e.g. support@example.com. Defaults to the SMTP sender.
attachment_idsNoArray of uploaded file IDs, up to 10 files and 10 MB combined.
scheduled_atNoISO 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.

HTML message · JSON payload
{
  "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.

MethodPathOperationSuccess
POST/emailsQueue an email202
GET/emailsPaginated emails, newest first200
GET/emails/:idRead one email record200
DELETE/emails/:idCancel a queued email200
POST/attachmentsUpload a private file201
GET/attachments/:idDownload a private file200
DELETE/attachments/:idDelete a file not used by pending mail200
cURL · check a message
curl 'https://openmail.vishnudeepak.com/api/emails/EMAIL_ID' \
  -H 'Authorization: Bearer YOUR_API_KEY'
200 OK · abbreviated example
{
  "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. Read error.
  • 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 · cancel queued mail
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 · server-side
// 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 · standard library
# 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 · upload a file
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.pdf

Content-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.

201 Created · example response
{
  "id": "example-file-id",
  "filename": "invoice.pdf",
  "size": 12345
}
Send with an attachment · JSON payload
{
  "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

Optional fields in POST /emails
{
  "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 response format
{
  "error": "Customer rate limit reached. Retry next minute."
}
HTTPMeaning / action
400Invalid fields, missing idempotency key, inactive SMTP account or invalid attachment. Correct the request.
401Missing, invalid or revoked key; account may be blocked. Check credentials.
403Operation requires a dashboard session, or browser origin was rejected.
404Record is missing, expired or belongs to another customer.
409Idempotency conflict, non-queued cancellation or file in use.
413JSON or attachment is too large.
415JSON endpoint received the wrong Content-Type.
429Customer submission limit reached. Retry after the minute window resets, with backoff.
500Server 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.