API reference

Base URL: https://api.techcoreexperts.org. All requests and responses use JSON over HTTPS. Times are ISO 8601 in UTC.

  1. Create an account and confirm your email address.
  2. Add and verify your sending domain in Domains (publish the DNS records shown).
  3. Create an API key in API keys. Copy it: it is shown only once.
  4. 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_…" } }
HTTPCodeMeaning
401invalid_api_key / api_key_revoked / api_key_expiredThe key is wrong, revoked or expired.
403insufficient_permissionsThe key lacks the scope this endpoint needs.
403api_key_ip_not_allowedThe request came from an IP address outside the key's allowlist.
403sender_domain_not_allowedThe key is restricted to other sending domains.
403domain_not_verified (details.reason)The From domain is not verified for your account.
409idempotency_in_progressA request with the same Idempotency-Key is still being processed.
422validation_failed / idempotency_key_reusedThe request is invalid, or the key was reused with a different body.
429rate_limited / quota_exceededToo many requests per minute, or the daily/monthly plan limit is reached.
5xxinternal_error / service_unavailableRetry with the same Idempotency-Key and exponential backoff.

Endpoints

MethodPathScopeDescription
POST/v1/messagesmessages:sendSend one message to one or more recipients (one message per recipient).
GET/v1/messagesmessages:readList messages, newest first. Filters: status, q, tag, batch_id; cursor pagination.
GET/v1/messages/{id}messages:readA message with its delivery timeline.
POST/v1/messages/{id}/cancelmessages:sendCancel a scheduled message before it is sent.
GET/v1/messages/export.csvmessages:readExport message history as CSV.
GET/v1/templatestemplates:readList templates.
POST/v1/templatestemplates:writeCreate a template (subject, html, text, variables).
POST/v1/templates/{id}/previewtemplates:readRender a saved template with sample variables.
GET/v1/suppressionssuppressions:readAddresses that will not be sent to (bounces, complaints, unsubscribes).
POST/v1/suppressionssuppressions:writeAdd an address to the suppression list.
DELETE/v1/suppressions/{email}suppressions:writeRemove an address from the suppression list.
GET/v1/domainsdomains:readSending domains and their DNS verification status.
GET/v1/webhookswebhooks:readWebhook endpoints.
POST/v1/webhookswebhooks:writeRegister an HTTPS endpoint for delivery events.
GET/v1/usageusage:readDaily and monthly usage against your plan limits.
GET/v1/mailboxesmailboxes:readHosted 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.
Help