Authentication
Create a key in Developers → API keys. Send it as a bearer token. Keys can be limited to scopes: send, verify, contacts, campaigns, domains, webhooks, automations, ai, reports. The full key is shown once; rotate it any time.
curl https://inboxroads.com/api/v1/me \
-H "Authorization: Bearer YOUR_KEY"
Errors
Errors return JSON with a message; validation errors add an errors object. Every response carries an X-Request-Id header — include it when you contact support.
401missing or invalid key ·402not enough credits ·403scope, plan or feature not allowed ·404not found (or not yours) ·413message too large ·422validation failed ·429rate limit or daily sending limit reached ·5xxretry with back-off
Rate limits
Requests per minute depend on your plan (see pricing). When you exceed it you receive 429 with a Retry-After header. Sending also respects a per-account daily limit and infrastructure limits; temporarily rejected messages return 429.
Send email
POST /send sends one message (transactional or personalised) through the same pipeline as the SMTP relay. 1 credit per recipient; suppressed recipients are not charged and permanent failures are refunded. The From domain must be verified in your account. Requires the SMTP relay & Email API to be enabled for your account.
curl -X POST https://inboxroads.com/api/v1/send \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{
"from": "Shop <orders@shop.com>",
"to": ["jane@example.com"],
"reply_to": "support@shop.com",
"subject": "Your order {{order}}",
"html": "<p>Hi {{first_name|there}}, thanks for order {{order}}.</p>",
"variables": {"first_name": "Jane", "order": "1024"},
"metadata": {"order_id": "1024"},
"tags": ["order"],
"attachments": [{"filename": "invoice.pdf", "content": "<base64>", "content_type": "application/pdf"}]
}'
Use a saved template instead of html: "template_id": "<template id>" with variables. Placeholders are {{name}} or {{name|fallback}}; values are HTML-escaped, templates are never executed as code. Response 202: the message id, recipients and credits charged. Check status with GET /messages/{id}.
Verify email
Single (synchronous): POST /verify with {"email": "…"}. Bulk: {"emails": ["…", "…"]} (up to 50,000) returns 202 with a job id; poll GET /verify/{id}. 1 verification credit per address; duplicates and bad syntax are free.
{
"email": "jane@company.com",
"result": "valid", // valid | invalid | risky | unknown
"status": "valid", // detailed: mailbox_error, disposable, role_based, …
"sub_status": "deliverable",
"mx_found": true, "disposable": false, "role_based": false, "free_provider": false
}
Contacts & lists
POST /contacts creates or updates by email: email, first_name, last_name, company, phone, country, custom_fields, lists (list ids). Suppressed addresses are stored as unsubscribed. GET /contacts?email=… looks one up; GET /lists, POST /lists manage lists.
Campaigns
POST /campaigns creates a draft (name, sender_id, subject, html or template_id, lists, segments, exclude_lists). POST /campaigns/{id}/send sends now or schedules with send_at; credits are reserved at send time. GET /reports/campaigns/{id} returns metrics and link clicks.
Domains
POST /domains adds a sending domain and returns the DNS records to publish; POST /domains/{id}/verify checks DNS now; GET /domains/{id} shows status.
Webhooks
Manage endpoints in the app or with /webhooks. Events:
delivered— Email delivered to the receiving serverbounced— Email bounced (hard, soft or blocked)opened— Recipient opened an email (first open)clicked— Recipient clicked a tracked linkcomplained— Recipient marked an email as spamunsubscribed— Recipient unsubscribedverification.completed— A verification (single or bulk) finishedcampaign.completed— A campaign finished sendingwarmup.status_changed— An IP warm-up schedule changed statusautomation.completed— A contact finished an automationrelay.sent— SMTP/API message handed to the sending providerrelay.failed— SMTP/API message could not be deliveredrelay.suppressed— SMTP/API recipient skipped (suppressed)relay.delivered— SMTP/API message deliveredrelay.bounced— SMTP/API message bouncedrelay.complained— SMTP/API recipient complained
Each request body is {"id", "type", "created_at", "data"}. Event ids are stable, so you can de-duplicate. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2, 6 and 12 hours; a 2xx response stops retries. Verify the signature:
// X-InboxRoads-Signature: t=1760000000,v1=5f2b…
[$t, $v1] = [/* parse t= and v1= */];
$expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);
if (! hash_equals($expected, $v1) || abs(time() - $t) > 300) {
abort(400);
}
// Node.js
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - t) < 300;
Automations
Enrol a lead in a webhook-triggered automation: POST /automations/{id}/trigger with email, optional contact fields, custom_fields and an event_id for idempotency. Each automation also has a private URL (/hooks/automations/…) you can paste into forms, CRMs, Zapier or n8n without an API key.
AI
POST /ai/campaign with prompt (plus optional objective, audience, tone, language, offer, cta, length, variations, followups) queues a draft; poll GET /ai/requests/{id} for the structured result. Counts toward your plan's monthly AI generations. Nothing is sent automatically.
Reports & usage
GET /reports/overview?from=2026-09-01&to=2026-09-30 — totals (sent, delivered, hard/soft bounces, opens, clicks, unsubscribes, complaints and rates), mailbox-provider breakdown, per-domain results and a daily series. GET /usage — API calls, emails, verifications and credits. GET /credits — balances (including what is usable for sending vs. verification).
SMTP relay
Host: smtp.inboxroads.com
Port: 587 (STARTTLS) · 465 (SSL/TLS) · 2525 (STARTTLS)
Username: your SMTP username (ir-…)
Password: shown once when you create the credential
From: an address on a verified domain
n8n
There is no native InboxRoads node yet; n8n works today with HTTP Request and Webhook nodes. In your account, Developers → Integrations has a downloadable workflow:
- Store your API key in n8n as the variable
INBOXROADS_API_KEY. - Webhook node receives a new lead.
- HTTP Request →
POST https://inboxroads.com/api/v1/verifywith{"email": "{{ $json.body.email }}"}. - IF
resultequalsvalid. - HTTP Request →
POST https://inboxroads.com/api/v1/send, orPOST /automations/{id}/triggerto hand the lead to an InboxRoads automation (wait, follow up if not opened, AI copy, CRM update). - Add an n8n Webhook URL as an InboxRoads webhook endpoint to react to
opened,clickedorbounced.
Code examples
PHP (Guzzle)
$client = new GuzzleHttp\Client(['base_uri' => 'https://inboxroads.com/api/v1/', 'headers' => ['Authorization' => 'Bearer '.getenv('INBOXROADS_KEY')]]);
$res = $client->post('verify', ['json' => ['email' => 'jane@example.com']]);
echo json_decode($res->getBody(), true)['result'];
Laravel
// .env: MAIL_MAILER=smtp MAIL_HOST=smtp.inboxroads.com MAIL_PORT=587 MAIL_ENCRYPTION=tls
// MAIL_USERNAME=ir-… MAIL_PASSWORD=…
Mail::to($user)->send(new OrderShipped($order));
// or the HTTP API
Http::withToken(config('services.inboxroads.key'))
->post('https://inboxroads.com/api/v1/send', ['from' => 'you@yourdomain.com', 'to' => [$user->email], 'subject' => 'Welcome', 'html' => $html]);
Node.js (Nodemailer)
const transporter = nodemailer.createTransport({ host: 'smtp.inboxroads.com', port: 587, secure: false,
auth: { user: process.env.IR_SMTP_USER, pass: process.env.IR_SMTP_PASS } });
await transporter.sendMail({ from: 'you@yourdomain.com', to: 'jane@example.com', subject: 'Hi', html: '<p>Hello</p>' });
Python
import requests
r = requests.post('https://inboxroads.com/api/v1/send', headers={'Authorization': f'Bearer {KEY}'},
json={'from': 'you@yourdomain.com', 'to': ['jane@example.com'], 'subject': 'Hi', 'text': 'Hello'})
print(r.status_code, r.json()['id'])