API reference
Base URL: https://api.techcoreexperts.org. All requests and responses use JSON over HTTPS. Times are ISO 8601 in UTC.
- Create an account and confirm your email address.
- Add and verify your sending domain in Domains (publish the DNS records shown).
- Create an API key in API keys. Copy it: it is shown only once.
- Send a request as shown below. A test key (
mv_test_…) processes the message without delivering it.
Authentication
Send your key in the Authorization header. Keys carry scopes (for example messages:send) and can be restricted to IP ranges and sending domains. Keep keys on your server; never put them in a browser or mobile app.
Authorization: Bearer mv_live_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Sending a message
POST /v1/messages
Idempotency-Key: 6d1f9c3e-invoice-4821
{
"from": "Acme Billing <billing@acme.co.ke>",
"to": ["customer@example.com"],
"subject": "Invoice 4821",
"html": "<p>Your invoice is attached.</p>",
"text": "Your invoice is attached.",
"reply_to": "accounts@acme.co.ke",
"tags": ["invoice"],
"metadata": { "invoice_id": "4821" },
"attachments": [{ "filename": "invoice-4821.pdf", "content": "<base64>", "content_type": "application/pdf" }],
"send_at": "2026-10-20T07:00:00Z",
"unsubscribe": false
}
Response 202 Accepted:
{
"batch_id": "0192…",
"status": "accepted",
"sandbox": false,
"messages": [{ "id": "0192…", "to": "customer@example.com", "status": "queued" }]
}
202 means accepted for delivery, not delivered. Track progress with GET /v1/messages/{id} or webhooks. Statuses: scheduled, queued, processing, accepted_by_mta (handed to the receiving server), delivered, deferred (temporary failure, retrying), bounced, failed, suppressed, sandboxed, cancelled.
Set "unsubscribe": true for newsletters and other bulk mail: the message gets RFC 8058 one-click List-Unsubscribe headers (required by Gmail and Yahoo for bulk senders) and recipients who use it are added to your suppression list.
Templates
Store content once and send with variables. Variables are HTML-escaped; undefined variables are errors, so a typo never sends a broken message.
{
"from": "admissions@school.ac.ke",
"to": "parent@example.com",
"template": "admission-confirmation",
"variables": { "first_name": "Amina", "programme": "Form One" }
}
Idempotency
Send an Idempotency-Key header (any unique string up to 255 characters) so a retried request never sends twice. Repeating the same key and body within 24 hours returns the original response with Idempotent-Replayed: true; reusing it with a different body returns 422 idempotency_key_reused.
Webhooks
Register an HTTPS endpoint to receive events such as message.delivered, message.bounced, message.complained, message.unsubscribed and domain.verified. Each request carries Mailvora-Signature: t=<unix>,v1=<hex> plus Mailvora-Event-Id, Mailvora-Event-Type and Mailvora-Delivery-Id headers, an HMAC-SHA256 of t + "." + body with your endpoint secret. Reject signatures older than five minutes. Failed deliveries are retried with backoff.
$payload = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_EH_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $payload, getenv('WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
http_response_code(401); exit;
}
Errors
Errors share one shape. code is stable for programs; message is safe to show; request_id helps support find the request.
{ "error": { "code": "quota_exceeded", "message": "Daily sending limit reached (200/200).", "request_id": "req_…" } }
| HTTP | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key / api_key_revoked / api_key_expired | The key is wrong, revoked or expired. |
| 403 | insufficient_permissions | The key lacks the scope this endpoint needs. |
| 403 | api_key_ip_not_allowed | The request came from an IP address outside the key's allowlist. |
| 403 | sender_domain_not_allowed | The key is restricted to other sending domains. |
| 403 | domain_not_verified (details.reason) | The From domain is not verified for your account. |
| 409 | idempotency_in_progress | A request with the same Idempotency-Key is still being processed. |
| 422 | validation_failed / idempotency_key_reused | The request is invalid, or the key was reused with a different body. |
| 429 | rate_limited / quota_exceeded | Too many requests per minute, or the daily/monthly plan limit is reached. |
| 5xx | internal_error / service_unavailable | Retry with the same Idempotency-Key and exponential backoff. |
Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/messages | messages:send | Send one message to one or more recipients (one message per recipient). |
| GET | /v1/messages | messages:read | List messages, newest first. Filters: status, q, tag, batch_id; cursor pagination. |
| GET | /v1/messages/{id} | messages:read | A message with its delivery timeline. |
| POST | /v1/messages/{id}/cancel | messages:send | Cancel a scheduled message before it is sent. |
| GET | /v1/messages/export.csv | messages:read | Export message history as CSV. |
| GET | /v1/templates | templates:read | List templates. |
| POST | /v1/templates | templates:write | Create a template (subject, html, text, variables). |
| POST | /v1/templates/{id}/preview | templates:read | Render a saved template with sample variables. |
| GET | /v1/suppressions | suppressions:read | Addresses that will not be sent to (bounces, complaints, unsubscribes). |
| POST | /v1/suppressions | suppressions:write | Add an address to the suppression list. |
| DELETE | /v1/suppressions/{email} | suppressions:write | Remove an address from the suppression list. |
| GET | /v1/domains | domains:read | Sending domains and their DNS verification status. |
| GET | /v1/webhooks | webhooks:read | Webhook endpoints. |
| POST | /v1/webhooks | webhooks:write | Register an HTTPS endpoint for delivery events. |
| GET | /v1/usage | usage:read | Daily and monthly usage against your plan limits. |
| GET | /v1/mailboxes | mailboxes:read | Hosted mailboxes (institutional accounts). |
Limits
- Requests per minute per key, recipients per request, and daily/monthly sends depend on your plan (see pricing).
- Message size up to 10 MB; attachments up to 8 MB in total, at most 10 per message. Executable file types are refused.
- Up to 10 tags, 20 metadata keys (4 KB) and 10 custom
X-headers per message.