Skip to content
Mailghor

Email API

Send transactional email over HTTPS or SMTP. Requests and answers are JSON.

Getting started

Before your first request:

  1. Choose an Email API plan.
  2. Add your domain under Email API › Domains and publish its SPF and DKIM records.
  3. Create an API key under Email API › API keys.

Base URL: https://api.mailghor.com/v1

Authenticate every request with your key: Authorization: Bearer mg_…

Keys start with mg_. A sending-only key can send but not read or cancel emails; use a full-access key for that.

Send an email

POST /emails — send one email to up to 50 recipients (to, cc and bcc together).

FieldTypeDescription
fromstringSender, e.g. "Acme <[email protected]>". The domain must be verified.
tostring | string[]Recipients.
cc, bccstring | string[]Optional copies.
reply_tostring | string[]Optional reply address.
subjectstringSubject line.
html, textstringThe body; at least one of them.
attachmentsobject[]{ filename, content (base64), content_type?, content_id? }; up to 25 MB in all.
headersobjectExtra headers, e.g. { "X-Entity-Ref-ID": "123" }.
tagsobject[]{ name, value } pairs, shown in logs and webhooks.
scheduled_atISO 8601Send later, up to 30 days ahead.
curl
curl -X POST https://api.mailghor.com/v1/emails \
  -H "Authorization: Bearer $EMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234" \
  -d '{
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Your order is on its way",
    "html": "<p>Thanks for shopping with us.</p>",
    "tags": [{ "name": "category", "value": "order" }]
  }'
Node.js
const res = await fetch("https://api.mailghor.com/v1/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EMAIL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Your sign-in code",
    text: "Your code is 482913",
  }),
});
const { id } = await res.json();

The answer is { "id": "…" }: keep it to look the email up or match webhook events.

Send an Idempotency-Key header to retry safely: a request with a key you used before returns the first email's id instead of sending again.

Send a batch

POST /emails/batch sends up to 100 emails in one request. Each item takes the same fields as a single email, except attachments and scheduled_at. Either every email is accepted or none is.

curl
curl -X POST https://api.mailghor.com/v1/emails/batch \
  -H "Authorization: Bearer $EMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "emails": [
    { "from": "[email protected]", "to": "[email protected]", "subject": "Hi A", "text": "…" },
    { "from": "[email protected]", "to": "[email protected]", "subject": "Hi B", "text": "…" }
  ] }'

# → { "data": [{ "id": "…" }, { "id": "…" }] }

Retrieve and cancel

GET /emails/{id} returns an email with each recipient's status. POST /emails/{id}/cancel cancels a scheduled email before it is sent.

curl
curl https://api.mailghor.com/v1/emails/4f1c2d8e-… \
  -H "Authorization: Bearer $EMAIL_API_KEY"

# Cancel a scheduled email:
curl -X POST https://api.mailghor.com/v1/emails/4f1c2d8e-…/cancel \
  -H "Authorization: Bearer $EMAIL_API_KEY"
StatusDescription
queued / scheduledAccepted, waiting to go.
sentHanded to the mail server.
deliveredAccepted by the recipient's server.
deferredThe recipient's server asked us to try again later.
bouncedRejected for good; the address is suppressed.
complainedMarked as spam; the address is suppressed.
suppressedNot sent: the address is on your suppression list.
failed / canceledNot sent.

SMTP

Anything that sends over SMTP can use the Email API without code changes.

SMTP
Host:      smtp.mailghor.com
Port:      587 (STARTTLS) or 465 (TLS)
Username:  shown under Email API › SMTP
Password:  one of your API keys

Messages sent over SMTP go through the same checks and appear in the same logs. Use port 587 with STARTTLS, or 465 with TLS.

Webhooks

Add endpoints under Email API › Webhooks. We send a POST with a JSON body for each event you choose.

EventDescription
email.sentHanded to the mail server.
email.deliveredAccepted by the recipient's server.
email.delivery_delayedDelivery is being retried.
email.bouncedRejected for good.
email.complainedMarked as spam by the recipient.
email.suppressedSkipped because the address is suppressed.
email.failedCould not be sent.
Payload
{
  "type": "email.delivered",
  "created_at": "2026-11-14T09:51:20.000Z",
  "data": {
    "email_id": "4f1c2d8e-…",
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Your order is on its way",
    "recipients": ["[email protected]"],
    "tags": { "category": "order" }
  }
}

Requests are signed the Standard Webhooks way: check webhook-signature against an HMAC-SHA256 of id.timestamp.body, keyed with your endpoint's secret.

Checking the signature (Node.js)
import crypto from "node:crypto";

// secret: the whsec_… value from the panel. body: the raw request body (a string).
export function verify(secret, headers, body) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // older than 5 minutes
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest("base64");
  return headers["webhook-signature"].split(" ").some((part) => part === `v1,${expected}`);
}

Answer with a 2xx status within 10 seconds. Otherwise we retry for about a day; an endpoint that keeps failing is turned off, and you can turn it back on in the panel.

Errors and limits

Errors come back with an HTTP status and a JSON body: { "statusCode": 400, "message": "…" }. Messages are in English; send x-locale: bn for Bangla.

CodeDescription
400Something in the request is wrong; the message says what.
401The API key is missing or not valid.
403Not allowed: no plan, the domain isn't verified, the key can't do this, or sending is paused.
404The email wasn't found.
413The message is too large.
429Too many emails per minute, the warm-up cap, or this month's volume is used up.
5xxSomething went wrong on our side; try again.
  • Up to 50 recipients per email, 100 emails per batch.
  • Up to 25 MB per email, attachments included.
  • Scheduling up to 30 days ahead.
  • The per-minute rate and monthly volume depend on your plan.