Email API
Send transactional email over HTTPS or SMTP. Requests and answers are JSON.
Getting started
Before your first request:
- Choose an Email API plan.
- Add your domain under Email API › Domains and publish its SPF and DKIM records.
- 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).
| Field | Type | Description |
|---|---|---|
from | string | Sender, e.g. "Acme <[email protected]>". The domain must be verified. |
to | string | string[] | Recipients. |
cc, bcc | string | string[] | Optional copies. |
reply_to | string | string[] | Optional reply address. |
subject | string | Subject line. |
html, text | string | The body; at least one of them. |
attachments | object[] | { filename, content (base64), content_type?, content_id? }; up to 25 MB in all. |
headers | object | Extra headers, e.g. { "X-Entity-Ref-ID": "123" }. |
tags | object[] | { name, value } pairs, shown in logs and webhooks. |
scheduled_at | ISO 8601 | Send later, up to 30 days ahead. |
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" }]
}'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 -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 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"| Status | Description |
|---|---|
queued / scheduled | Accepted, waiting to go. |
sent | Handed to the mail server. |
delivered | Accepted by the recipient's server. |
deferred | The recipient's server asked us to try again later. |
bounced | Rejected for good; the address is suppressed. |
complained | Marked as spam; the address is suppressed. |
suppressed | Not sent: the address is on your suppression list. |
failed / canceled | Not sent. |
SMTP
Anything that sends over SMTP can use the Email API without code changes.
Host: smtp.mailghor.com
Port: 587 (STARTTLS) or 465 (TLS)
Username: shown under Email API › SMTP
Password: one of your API keysMessages 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.
| Event | Description |
|---|---|
email.sent | Handed to the mail server. |
email.delivered | Accepted by the recipient's server. |
email.delivery_delayed | Delivery is being retried. |
email.bounced | Rejected for good. |
email.complained | Marked as spam by the recipient. |
email.suppressed | Skipped because the address is suppressed. |
email.failed | Could not be sent. |
{
"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.
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.
| Code | Description |
|---|---|
400 | Something in the request is wrong; the message says what. |
401 | The API key is missing or not valid. |
403 | Not allowed: no plan, the domain isn't verified, the key can't do this, or sending is paused. |
404 | The email wasn't found. |
413 | The message is too large. |
429 | Too many emails per minute, the warm-up cap, or this month's volume is used up. |
5xx | Something 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.