Skip to main content

API Documentation

Everything you need to send emails via our REST API

Quick Start

  1. Create an account
  2. Create an API key with your SMTP credentials in the Dashboard
  3. Copy the API key (starts with rk_) - shown only once!
  4. Make HTTP POST requests to send emails

Base URL: https://2smtp.com

How It Works

1

You send HTTP request

POST to our API with your email data

2

We relay via YOUR SMTP

Using the credentials you configured

3

Email is delivered

Sent from your own mail server

Credit Counting

Each recipient counts as one credit, regardless of message size.

Example:

  • 1 TO recipient = 1 credit
  • 1 TO + 2 CC = 3 credits
  • 5 TO + 3 CC + 2 BCC = 10 credits

Authentication

API Key (for sending emails)

Use your API key to send emails:

X-API-Key: rk_your_api_key

Session Token (for key management)

Get a session token by logging in, then use it to manage API keys:

Authorization: Bearer your_session_token

Key types: API keys (rk_) are for sending emails. Master keys (mk_) are for programmatic account management.

Send Email API

Requires your sending key, either as X-API-Key: rk_... or Authorization: Bearer rk_... β€” both work.

POST /api/v1/send

Send an email using your API key.

Request Body

json
{
  "to": ["recipient@example.com"],     // Required: array of recipient emails
  "cc": ["cc@example.com"],            // Optional: array of CC emails
  "bcc": ["bcc@example.com"],          // Optional: array of BCC emails
  "reply_to": "replyto@example.com",   // Optional: reply-to address
  "subject": "Email Subject",          // Required: email subject
  "body_text": "Plain text body",      // Optional*: plain text version
  "body_html": "<p>HTML body</p>"      // Optional*: HTML version
}

// * At least one of body_text or body_html is required

Success Response (200)

json
{
  "success": true,
  "recipients": 1,
  "credits_remaining": 9999
}

Error Response (4xx/5xx)

json
{
  "error": "invalid_api_key"
}
GET /api/v1/preflight

Grade your sender domain's SPF/DKIM/DMARC/MX before spending credits. With no params it checks your key's from-address domain β€” CI-friendly: curl -H "X-API-Key: rk_..." https://2smtp.com/api/v1/preflight. Optional ?domain= and ?selector=.

POST /api_fe/send_test_email_fe Session auth

Send a test email to verify a key's SMTP configuration. Uses your dashboard session (Authorization: Bearer), not the API key header.

json
{
  "api_key_chirho": "rk_the_key_to_test",
  "to_email_chirho": "you@example.com"
}
GET /api_fe/usage_fe Session auth

Get current credit usage for your whole account (session or master-key Authorization: Bearer β€” not the API key header).

json
{
  "total_credits_chirho": 10000,
  "used_credits_chirho": 1,
  "remaining_credits_chirho": 9999,
  "total_emails_sent_chirho": 1
}

Idempotent Retries

Networks fail mid-request. If your send times out, you cannot tell whether the email went out β€” retrying blindly risks a duplicate, and giving up risks nothing being sent. Pass an Idempotency-Key header and 2SMTP guarantees at most one successful, charged send per key: a retry with the same key and body returns the original response instead of sending again.

bash
curl -X POST https://2smtp.com/api/v1/send \
  -H "X-API-Key: rk_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-42-receipt" \
  -d '{ "to": ["customer@example.com"], "subject": "Receipt", "body_text": "Thanks!" }'

How it behaves:

  • First successful send is stored; a retry with the same key and body gets the stored response back with an Idempotent-Replayed: true header β€” no second send, no second charge.
  • Same key with a different body returns 422 idempotency_key_reused β€” pick a new key for a new message.
  • If the original request is still in flight, a concurrent retry gets 409 idempotency_in_flight with a Retry-After header.
  • Failed sends are not stored β€” a retry after an error executes fresh, so a transient relay problem never locks your key.
  • Keys are scoped to your API key, up to 255 characters (an order ID or UUID works well), and are retained for 24 hours.

Key Management API

Requires Authorization: Bearer with either a dashboard session token or a master key (mk_...) for automation.

POST /api_fe/keys_fe

Create a new API key with SMTP configuration.

bash
curl -X POST https://2smtp.com/api_fe/keys_fe \
  -H "Authorization: Bearer your_session_token" \
  -H "Content-Type: application/json" \
  -d '{
    "smtp_host_chirho": "smtp.gmail.com",
    "smtp_port_chirho": 587,
    "smtp_user_chirho": "your-email@gmail.com",
    "smtp_pass_chirho": "your-app-password",
    "smtp_tls_chirho": true,
    "from_email_chirho": "your-email@gmail.com",
    "label_chirho": "Production"
  }'
GET /api_fe/keys_fe

List all API keys with details.

bash
curl https://2smtp.com/api_fe/keys_fe \
  -H "Authorization: Bearer your_session_token"
POST /api_fe/update_key_label_fe

Update the label for an API key.

GET PATCH DELETE /api_fe/keys_fe/{api_key}

Read one key, update its SMTP configuration (PATCH accepts the same fields as create), or delete it permanently.

POST /api_fe/deactivate_key_fe

Deactivate an API key permanently.

POST /api_fe/test_smtp_fe

Test SMTP connection before creating a key.

Credits & Usage API

Requires session token authentication.

GET /api_fe/credits_fe

Get credit pool balance.

bash
curl https://2smtp.com/api_fe/credits_fe \
  -H "Authorization: Bearer your_session_token"
GET /api_fe/usage_stats_fe

Get daily and per-key usage statistics (last 30 days).

json
{
  "daily_usage_chirho": [{ "date_chirho": "2026-07-16", "emails_chirho": 120 }],
  "total_30_days_chirho": 1830,
  "key_stats_chirho": [{ "api_key_preview_chirho": "rk_9c5c...", "label_chirho": "Production", "emails_sent_chirho": 1830 }]
}
GET /api_fe/history_fe

Paginated send history β€” every attempt that reached your SMTP server, including failures. Query params: page, limit (default 50, max 100), status (sent | failed), and key; the response carries pagination_chirho metadata.

POST /api_fe/checkout_fe

Create a Stripe checkout session to purchase credits.

Master Keys API

Master keys allow programmatic management of your 2SMTP account for automation and AI integration.

Honest note: scopes are recorded for audit; enforcement is not yet active β€” treat every master key as full-account access when you share one.

POST /api_fe/master_keys_fe

Create a new master key with optional scopes and expiration.

GET /api_fe/master_keys_fe

List all master keys (shows preview only, not full key).

DELETE /api_fe/master_keys_fe

Revoke a master key by its preview.

Code Examples

cURL

bash
curl -X POST https://2smtp.com/api/v1/send \
  -H "X-API-Key: rk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["recipient@example.com"],
    "subject": "Hello from 2SMTP!",
    "body_text": "This is a plain text email.",
    "body_html": "<h1>Hello!</h1><p>This is an HTML email.</p>"
  }'

JavaScript / TypeScript

javascript
const response = await fetch('https://2smtp.com/api/v1/send', {
  method: 'POST',
  headers: {
    'X-API-Key': 'rk_your_api_key',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    to: ['recipient@example.com'],
    subject: 'Hello from 2SMTP!',
    body_text: 'This is a plain text email.',
    body_html: '<h1>Hello!</h1><p>This is an HTML email.</p>'
  })
});

const data = await response.json();
console.log(data.success); // true
console.log(data.recipients); // 1
console.log(data.credits_remaining); // 9999

Python

python
import requests

response = requests.post(
    'https://2smtp.com/api/v1/send',
    headers={
        'X-API-Key': 'rk_your_api_key',
        'Content-Type': 'application/json'
    },
    json={
        'to': ['recipient@example.com'],
        'subject': 'Hello from 2SMTP!',
        'body_text': 'This is a plain text email.',
        'body_html': '<h1>Hello!</h1><p>This is an HTML email.</p>'
    }
)

data = response.json()
print(data['success'])  # True
print(data['recipients'])  # 1
print(data['credits_remaining'])  # 9999

PHP

php
<?php
$ch = curl_init('https://2smtp.com/api/v1/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-API-Key: rk_your_api_key',
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'to' => ['recipient@example.com'],
        'subject' => 'Hello from 2SMTP!',
        'body_text' => 'Sent from PHP with one HTTP call.'
    ])
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

echo $data['success'] ? 'sent' : $data['error'];
echo $data['credits_remaining'];

Go

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"to":        []string{"recipient@example.com"},
		"subject":   "Hello from 2SMTP!",
		"body_text": "Sent from Go with one HTTP call.",
	})
	req, _ := http.NewRequest("POST", "https://2smtp.com/api/v1/send", bytes.NewReader(payload))
	req.Header.Set("X-API-Key", "rk_your_api_key")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var data struct {
		SuccessChirho          bool `json:"success"`
		RecipientsChirho       int  `json:"recipients"`
		CreditsRemainingChirho int  `json:"credits_remaining"`
	}
	json.NewDecoder(resp.Body).Decode(&data)
	fmt.Println(data.SuccessChirho, data.CreditsRemainingChirho)
}

Ruby

ruby
require 'net/http'
require 'json'

uri = URI('https://2smtp.com/api/v1/send')
request = Net::HTTP::Post.new(uri)
request['X-API-Key'] = 'rk_your_api_key'
request['Content-Type'] = 'application/json'
request.body = {
  to: ['recipient@example.com'],
  subject: 'Hello from 2SMTP!',
  body_text: 'Sent from Ruby with one HTTP call.'
}.to_json

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

data = JSON.parse(response.body)
puts data['success']
puts data['credits_remaining']

Migrating from Nodemailer

javascript
// Before: nodemailer needs an SMTP connection from your runtime -
// a problem on serverless platforms with no raw TCP (Workers, Edge).
//
// const transporter = nodemailer.createTransport({ host, port, auth });
// await transporter.sendMail({ from, to, subject, text });

// After: same SMTP server, but 2SMTP holds the connection - your code
// is one fetch that works anywhere JavaScript runs.
await fetch('https://2smtp.com/api/v1/send', {
  method: 'POST',
  headers: { 'X-API-Key': 'rk_your_api_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    to: ['recipient@example.com'],
    subject: 'Hello from 2SMTP!',
    body_text: 'No SMTP client library required.'
  })
});

Rate Limits

Important: Rate limits protect your SMTP server's reputation. Sending too fast can trigger spam filters and blacklisting.

  • 50 emails per minute per API key
  • 1,000 recipients per single request (TO + CC + BCC combined)

When you hit a limit

The response is 429 with a Retry-After header (seconds) and a JSON body naming the limit:

json
{
  "error": "rate_limit_exceeded",
  "retry_after_seconds": 30
}

Error Codes

StatusMeaning
400Bad request - invalid parameters
401Unauthorized - invalid or missing API key
402Insufficient credits (insufficient_credits) β€” nothing was sent or charged
404Resource not found
409Idempotent request still in flight (idempotency_in_flight) β€” retry after Retry-After
422Idempotency key reused with a different body, or SMTP configuration rejected
429Rate limit or per-key cap exceeded (rate_limit_exceeded, daily_limit_exceeded, total_limit_exceeded)
500Server error
502Relay error while sending (satellite_error) β€” credits released, retry safe
503No healthy relay or SMTP connection failed (smtp_connect_failed, smtp_timeout, ...)

Every error body carries a machine-readable error code (snake_case, as shown above) β€” branch on it, not on the human-readable message.

Common SMTP Providers

When creating an API key, you'll need your SMTP credentials. Here are settings for popular providers:

Gmail / Google Workspace

Host:smtp.gmail.com Port:587 (TLS) or 465 (SSL) Username:your-email@gmail.com Password:App Password (not your regular password)

Outlook / Microsoft 365

Host:smtp.office365.com Port:587

Amazon SES

Host:email-smtp.[region].amazonaws.com Port:587

SendGrid

Host:smtp.sendgrid.net Port:587 Username:apikey Password:Your SendGrid API key

OpenAPI Specification

Full machine-readable API documentation in OpenAPI 3.0.3 format.

View OpenAPI Spec (opens in a new tab)

MCP Integration (Coming Soon)

MCP server integration for AI assistants like Claude Desktop is planned.

  • remail_send_email - Send an email via API key
  • remail_list_keys - List all API keys
  • remail_get_usage - View usage statistics

Need Help?

Having trouble integrating? Contact us or use the feedback balloon on any page β€” feedback lands straight on our workbench.