Developers
API reference
A small REST API over HTTPS. All endpoints are under /api/v1. There's also a Node SDK and an optional SMTP relay.
Authentication
Send your API key as a Bearer token. Live keys (smk_live_…) deliver; test keys (smk_test_…) validate and record but never deliver.
Authorization: Bearer smk_live_…
Send an email
POST /api/v1/email — requires the From domain to be registered & verified. Returns 202 with { id, status } where status is sent | test | suppressed.
curl -X POST https://sovmail.co.uk/api/v1/email \
-H "Authorization: Bearer smk_live_…" -H "content-type: application/json" \
-d '{
"from": "noreply@mail.your-company.co.uk",
"to": "user@example.org",
"subject": "Welcome",
"text": "Thanks for signing up.",
"html": "<p>Thanks for signing up.</p>"
}'
Send with a stored template instead of subject/body:
{ "from": "...", "to": "...", "templateId": "…", "variables": { "name": "Ada" } }
Add "track": true to record delivery status (a delivered status and delivered/deferred events) for a message — handy for confirming sends while testing. It's off by default and overrides your account default; it's delivery status only, never open/click tracking or link rewriting. Bounces and complaints are always recorded regardless.
Code examples
The API is a plain HTTPS POST with a Bearer token, so it drops into any language or framework. Set SOVMAIL_KEY in your environment and use the snippet for your stack.
Node.js (fetch)
const res = await fetch("https://sovmail.co.uk/api/v1/email", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOVMAIL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "noreply@mail.your-company.co.uk",
to: "user@example.org",
subject: "Welcome",
text: "Thanks for signing up.",
}),
});
if (!res.ok) throw new Error(`Sovereign Mail: ${res.status}`);
const { id } = await res.json();
NestJS (@nestjs/axios)
import { Injectable } from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { firstValueFrom } from "rxjs";
@Injectable()
export class MailService {
constructor(private readonly http: HttpService) {}
async send(to: string, subject: string, text: string) {
const { data } = await firstValueFrom(
this.http.post(
"https://sovmail.co.uk/api/v1/email",
{ from: "noreply@mail.your-company.co.uk", to, subject, text },
{ headers: { Authorization: `Bearer ${process.env.SOVMAIL_KEY}` } },
),
);
return data; // { id, status }
}
}
PHP (Guzzle)
$client = new GuzzleHttp\Client();
$res = $client->post('https://sovmail.co.uk/api/v1/email', [
'headers' => ['Authorization' => 'Bearer ' . getenv('SOVMAIL_KEY')],
'json' => [
'from' => 'noreply@mail.your-company.co.uk',
'to' => 'user@example.org',
'subject' => 'Welcome',
'text' => 'Thanks for signing up.',
],
]);
$id = json_decode((string) $res->getBody(), true)['id'];
Symfony (HttpClient)
use Symfony\Contracts\HttpClient\HttpClientInterface;
public function send(HttpClientInterface $client): string
{
$response = $client->request('POST', 'https://sovmail.co.uk/api/v1/email', [
'auth_bearer' => $_ENV['SOVMAIL_KEY'],
'json' => [
'from' => 'noreply@mail.your-company.co.uk',
'to' => 'user@example.org',
'subject' => 'Welcome',
'text' => 'Thanks for signing up.',
],
]);
return $response->toArray()['id'];
}
Laravel (HTTP client)
use Illuminate\Support\Facades\Http;
$response = Http::withToken(env('SOVMAIL_KEY'))
->post('https://sovmail.co.uk/api/v1/email', [
'from' => 'noreply@mail.your-company.co.uk',
'to' => 'user@example.org',
'subject' => 'Welcome',
'text' => 'Thanks for signing up.',
]);
$id = $response->json('id');
Python (requests)
import os, requests
res = requests.post(
"https://sovmail.co.uk/api/v1/email",
headers={"Authorization": f"Bearer {os.environ['SOVMAIL_KEY']}"},
json={
"from": "noreply@mail.your-company.co.uk",
"to": "user@example.org",
"subject": "Welcome",
"text": "Thanks for signing up.",
},
timeout=15,
)
res.raise_for_status()
message_id = res.json()["id"]
Ruby (Net::HTTP)
require "net/http"
require "json"
uri = URI("https://sovmail.co.uk/api/v1/email")
req = Net::HTTP::Post.new(uri,
"Authorization" => "Bearer #{ENV['SOVMAIL_KEY']}",
"Content-Type" => "application/json")
req.body = {
from: "noreply@mail.your-company.co.uk",
to: "user@example.org", subject: "Welcome", text: "Thanks for signing up.",
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
id = JSON.parse(res.body)["id"]
Go (net/http)
payload, _ := json.Marshal(map[string]string{
"from": "noreply@mail.your-company.co.uk",
"to": "user@example.org",
"subject": "Welcome",
"text": "Thanks for signing up.",
})
req, _ := http.NewRequest("POST", "https://sovmail.co.uk/api/v1/email", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SOVMAIL_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
Batch send
POST /api/v1/email/batch — up to 500 messages; each returns its own result.
{ "messages": [ { "from": "...", "to": "a@x.org", "subject": "Hi", "text": "1" }, … ] }
Templates
GET /api/v1/templates · POST /api/v1/templates — bodies support {{variables}}, {{#if}}/{{#unless}} conditionals, and layouts.
Messages & usage
GET /api/v1/messages — recent sends. Filter with ?status= (e.g. delivered, bounced), ?track=true|false (delivery-tracked messages only), ?q= (recipient/subject) and ?limit= (max 500). Each row includes track. GET /api/v1/usage — plan, monthly limit, used, remaining.
Webhooks
PUT /api/v1/webhook sets a receiver URL and returns a signing secret; we POST events signed with HMAC-SHA256 in X-Sovmail-Signature. We always send message.sent (accepted by the MTA); for messages with delivery tracking on we also send message.delivered when the recipient MTA confirms delivery.
Errors
Standard HTTP codes: 401 bad key · 402 over quota · 403 unverified/suspended/unverified domain · 404 template not found · 422 failed content checks · 429 rate limited. Bodies are { "error": "…" }.