5,000 emails. Enough for a small monthly newsletter or a quiet transactional flow.
Casa Mail API
Send transactional email with one HTTP call.
The same infrastructure powering the Casa Mail WordPress plugin, exposed as a plain REST API. Bearer-token auth, JSON in, message ID out. GBP 0.001 per email, prepaid credits, no monthly commitment. 100 free trial credits when you sign up.
curl -X POST https://api.onlinestorenotifications.com/v1/send \
-H "Authorization: Bearer $CASAMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient": "customer@example.com",
"from_email": "noreply@yourbrand.com",
"from_name": "Your Brand",
"subject": "Your order has shipped",
"body_html": "<p>Thanks for your order!</p>",
"body_text": "Thanks for your order!"
}'
{
"ok": true,
"stream": "txn",
"id": 1104932,
"idempotency_key": "abc123..."
}
/v1/send. Bearer auth. Idempotent replays. No SDK required.
Sign up
Get your API key in under a minute.
Enter your email, project name, and website. We send a 6-digit code to verify the email, then hand you a bearer token and 100 free credits. No card required for the trial.
Your API key
Store this now. We only keep a hash on our side and cannot show it again. Rotation requires operator action.
- Tenant slug:
- Plan:
api - Credit balance: credits ()
Quickstart
Three steps from signup to sent.
The API is a single endpoint that accepts a JSON body and returns a durable message ID. Retry safely with an idempotency key. No polling required.
Sign up above
Enter your email and website, verify the OTP, and grab your bearer token. 100 credits added immediately.
POST to /v1/send
Bearer auth in the header, JSON body with recipient, from_email, subject, and body. HTTP 200 means the message is durably accepted.
Top up when you need more
Once your balance approaches zero the API returns 402 with a topup URL. Add credits from the dashboard in one click via Stripe Checkout.
(Optional) Verify your own domain
Add your own sending domain via the domain endpoints for stronger deliverability. Publish three DNS records; the API verifies them for you.
| Code | Meaning |
|---|---|
200 | Durably accepted. The id in the body is your message ID. |
401 | Missing or invalid Authorization header. |
402 | Credit balance exhausted. Body includes topup_url. |
422 | Body validation failed. Body includes error and detail. |
429 | Rate-limited (60/min per API key). Retry after 60s. |
451 | Content blocked by scanner. Contact support if unexpected. |
Endpoints
The full API in one table.
All endpoints are HTTPS on api.onlinestorenotifications.com. Auth is Authorization: Bearer <api_key> unless noted. Request and response bodies are JSON.
| Method | Path | What it does |
|---|---|---|
POST |
/v1/send |
Send one email. Body: recipient, from_email, from_name, subject, body_html, body_text, optional idempotency_key and headers. Debits one credit on success. |
GET |
/v1/account |
Read tenant info: plan, credit balance, sends today, sends this month, recent credit ledger. Used by the dashboard. |
POST |
/v1/billing/topup |
Create a Stripe Checkout session for a topup. Body: amount_gbp (one of 5, 25, 100, 500). Returns checkout_url. |
POST |
/v1/onboard/request |
Anonymous. Send OTP to an email. Body: email, site_url, site_name, plan: "api". |
POST |
/v1/onboard/verify |
Anonymous. Verify OTP and provision tenant + API key. Body: email, code, site_url, site_name. Returns the bearer once. |
POST |
/v1/email/domain |
Add your own sending domain. Returns the DNS records to publish (DKIM, SPF, Return-Path). See the domain verification section. |
POST |
/v1/email/domain/verify |
Trigger a DNS re-check on your domain. Idempotent, safe to poll every 30s. |
Code samples
Working /v1/send in three languages.
Every example uses the shared onlinestorenotifications.com sender by default. Once you verify your own domain via /v1/email/domain, put your address in from_email.
curl -X POST https://api.onlinestorenotifications.com/v1/send \
-H "Authorization: Bearer $CASAMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient": "customer@example.com",
"from_email": "noreply@onlinestorenotifications.com",
"from_name": "Acme Notifications",
"subject": "Your order has shipped",
"body_html": "<p>Track it at <a href="https://acme.example/o/1234">acme.example</a>.</p>",
"body_text": "Track it at https://acme.example/o/1234"
}'
<?php
$body = [
'recipient' => 'customer@example.com',
'from_email' => 'noreply@onlinestorenotifications.com',
'from_name' => 'Acme Notifications',
'subject' => 'Your order has shipped',
'body_html' => '<p>Track it at <a href="https://acme.example/o/1234">acme.example</a>.</p>',
'body_text' => 'Track it at https://acme.example/o/1234',
];
$ch = curl_init('https://api.onlinestorenotifications.com/v1/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CASAMAIL_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_RETURNTRANSFER => true,
]);
$res = json_decode(curl_exec($ch), true);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "HTTP $code, id={$res['id']}\n";
const res = await fetch('https://api.onlinestorenotifications.com/v1/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CASAMAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipient: 'customer@example.com',
from_email: 'noreply@onlinestorenotifications.com',
from_name: 'Acme Notifications',
subject: 'Your order has shipped',
body_html: '<p>Track it at <a href="https://acme.example/o/1234">acme.example</a>.</p>',
body_text: 'Track it at https://acme.example/o/1234',
}),
});
const data = await res.json();
console.log(`HTTP ${res.status}, id=${data.id}`);
import os, requests
res = requests.post(
"https://api.onlinestorenotifications.com/v1/send",
headers={
"Authorization": f"Bearer {os.environ['CASAMAIL_API_KEY']}",
"Content-Type": "application/json",
},
json={
"recipient": "customer@example.com",
"from_email": "noreply@onlinestorenotifications.com",
"from_name": "Acme Notifications",
"subject": "Your order has shipped",
"body_html": "<p>Track it at <a href='https://acme.example/o/1234'>acme.example</a>.</p>",
"body_text": "Track it at https://acme.example/o/1234",
},
timeout=15,
)
print(f"HTTP {res.status_code}, id={res.json().get('id')}")
Pricing
GBP 0.001 per email. Prepaid credits. Never expire.
The API plan is credit-metered, not subscription-metered. You buy credits in fixed amounts and each send debits one credit. There is no monthly bill, no idle-account fee, and no expiry.
25,000 emails. The typical starting topup for production traffic.
100,000 emails. Bulk-priced for higher-volume transactional apps.
500,000 emails. For established SaaS or e-commerce apps.
- Trial. Every new API tenant gets 100 free credits on signup. No card required.
- Payments. Stripe Checkout, one-off, no subscription. VAT added at checkout for UK/EU customers.
- Balance. Credits never expire. Cancel any time by not topping up.
- 402 responses. When your balance hits zero the API returns HTTP 402 with a
topup_url. Requests do not queue.
Developer FAQ
Common questions before you integrate.
Do I need the WordPress plugin?
No. The API is a plain REST endpoint. The plugin uses the same API under the hood; a direct API integration skips the plugin entirely. Use the plugin only if you want the free 10,000/month WordPress tier plus the WP admin UI.
Is there a free tier on the API?
No monthly free tier on the API plan. You get a one-time 100-credit trial when you sign up, then pay per email via topups. If you want the free 10,000/month plan, install the WordPress plugin instead.
How is authentication handled?
Bearer token in the Authorization header. The key is 64 hex characters, shown exactly once at signup, hashed on our side. Rotation requires operator action.
Can I send from my own domain?
Yes. Register your domain with POST /v1/email/domain, publish the three DNS records (DKIM, SPF, Return-Path) we return, and call POST /v1/email/domain/verify. Until verified, any from_email outside our shared sender is silently rewritten to the canonical address.
Is retry safe?
Yes. Every send accepts an optional 64-hex idempotency_key. Re-POSTing the same key returns the original result, never a duplicate send. If you omit it, we derive one from the recipient + subject + body hash.
What are the rate limits?
60 requests per minute per API key by default, tunable on request for higher-volume tenants. Bursts above 60/min return HTTP 429 with Retry-After: 60.
How does content scanning work?
High-volume transactional lanes are sampled with an LLM scanner. Critical verdicts return HTTP 451 and kill the send. False positives are rare and reviewable via support@onlinestorenotifications.com. Regular transactional traffic (order confirmations, password resets, account emails) is never blocked.
How do I check my balance?
Two options: GET /v1/account for a JSON snapshot, or open /dashboard and paste your API key. The dashboard also surfaces recent topups, sends today, and topup buttons.
What happens when I run out of credits?
New /v1/send calls return HTTP 402 with a topup_url. Existing queued messages are unaffected. Add credits via the dashboard; new calls resume immediately after the Stripe webhook lands (usually under 5 seconds).
Where do I go for support?
Email support@onlinestorenotifications.com. Include your tenant slug (visible in the dashboard) and a request or response ID if you have one.