Email API
Send email from your own application over HTTPS, or point any existing app at our SMTP server. Everything below works the same in both.
Getting started
Before your first email you need three things, in this order:
- A verified domain. Add it in your dashboard and copy the DNS records it shows you. We check them every minute.
- Approval. Submit your business details; we review them, usually the same day. This protects everyone’s delivery, including yours.
- An API key. Create one on the API keys page. It is shown once.
New accounts start at 500 emails a day. That rises once your bounce and complaint rates look healthy.
https://api.gotimail.comAuthentication
Send your key in the Authorization header. Keep it on your server — never in a browser, a mobile app or a public repository.
Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxYou can restrict a key to particular IP addresses on the API keys page. A restricted key used from anywhere else is refused, and the message names the address we saw — which is usually a server whose outbound IP differs from the one you entered.
Send an email
POST /v1/sendcurl -X POST https://api.gotimail.com/v1/send \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "orders@yourcompany.com",
"fromName": "Your Company",
"to": ["customer@example.com"],
"replyTo": "support@yourcompany.com",
"subject": "Your order is confirmed",
"html": "<p>Thank you for your order.</p>",
"text": "Thank you for your order."
}'Fields
| Field | Required | Notes |
|---|---|---|
from | yes | Must use a domain you have verified. The address only — put the display name in fromName. |
fromName | no | Shown instead of the bare address. |
to | yes | Up to 50 addresses per request. |
subject | yes | Up to 500 characters. |
html / text | one of them | Send both where you can; some clients only read text. |
replyTo | no | Where replies go. |
marketing | no | true for newsletters and offers. We then add the unsubscribe headers inboxes require. |
Answer
{
"id": "0c455a2d-d4b5-4e06-9c00-583f43ffd376",
"status": "sent",
"to": ["customer@example.com"],
"providerMessageId": "010e01a0bac7e7d2-..."
}sent means the message was accepted for delivery, not that it reached the inbox. Delivery, bounces and complaints arrive a moment later — see webhooks.
PHP
$response = wp_remote_post('https://api.gotimail.com/v1/send', [
'headers' => [
'Authorization' => 'Bearer ' . GOTIMAIL_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode([
'from' => 'orders@yourcompany.com',
'to' => [$customerEmail],
'subject' => 'Your order is confirmed',
'html' => '<p>Thank you for your order.</p>',
]),
]);Node.js
const res = await fetch('https://api.gotimail.com/v1/send', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.GOTIMAIL_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({
from: 'orders@yourcompany.com',
to: [customerEmail],
subject: 'Your order is confirmed',
html: '<p>Thank you for your order.</p>',
}),
});
if (!res.ok) throw new Error((await res.json()).message);Python
import requests
requests.post(
"https://api.gotimail.com/v1/send",
headers={"Authorization": f"Bearer {GOTIMAIL_KEY}"},
json={
"from": "orders@yourcompany.com",
"to": [customer_email],
"subject": "Your order is confirmed",
"html": "<p>Thank you for your order.</p>",
},
timeout=15,
).raise_for_status()Personalisation
In campaigns and automations you can write {{first_name}}, {{last_name}} or {{email}} in the subject or body, and we fill them in per recipient. An unknown or empty value renders as nothing rather than leaving the braces visible.
Values are escaped in HTML, so a contact named Tom & Jerry cannot break your layout. In the API, personalise in your own code before calling us.
Avoiding duplicates
If your server times out and retries, you do not want two emails. Send a unique Idempotency-Key header — anything stable, such as your order number — and a repeat with the same key returns the first result instead of sending again.
-H "Idempotency-Key: order-1234"Keys are remembered for 24 hours.
Checking your account
GET /v1/sending-status{
"canSend": true,
"blockers": [],
"dailyCap": 500,
"sentToday": 12,
"trial": true,
"paused": false,
"activeDomains": ["yourcompany.com"]
}Worth calling before a large run. blockers lists anything in the way, in plain words, in the order to fix it.
Errors
Every failure returns JSON with a message written for a person. Show it in your logs; it usually says exactly what to change.
| Code | Meaning |
|---|---|
400 | Something in the request is wrong — a missing subject, a malformed address. |
401 | The key is missing, wrong or revoked. |
403 | Allowed request, refused account: not approved yet, sending paused, daily limit reached, sending from a domain you have not verified, or a key used from an address outside its whitelist. |
409 | Every recipient is on your blocked list, or the delivery provider refused the message. The reason is included verbatim. |
429 | Too many requests in a minute. Wait and retry. |
A 403 or 409 is not a bug to retry in a loop — the message says what a person needs to do.
Webhooks
We can tell your application what happened to each email: delivered, bounced, complained, opened, clicked, rejected. Add your endpoint on the Webhooks page and choose which events you want.
POST https://yourcompany.com/email-events
X-Gotisms-Event: bounced
X-Gotisms-Timestamp: 1789670000
X-Gotisms-Signature: 9f2c...
{
"event": "bounced",
"messageId": "0c455a2d-...",
"to": "customer@example.com",
"from": "orders@yourcompany.com",
"subject": "Your order is confirmed",
"bounceType": "Permanent",
"diagnostic": "550 5.1.1 user unknown",
"at": "2026-09-20T09:14:02.000Z"
}Checking the signature
Rebuild it with your signing secret and compare. If they differ, ignore the request.
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody)
.digest('hex');
if (expected !== signature) return res.sendStatus(401);
res.sendStatus(200); // any 2xx means "received"Use the raw body, not a re-encoded object — whitespace changes the signature. We retry six times over about five hours; after many failures the webhook is switched off until you send a test from the dashboard.
SMTP
If your software already sends email, point it here instead of writing code.
smtp.gotimail.com2525The same rules apply: the from address must use a verified domain, blocked addresses are refused, and your daily limit counts. A refusal comes back as an SMTP error containing the real reason, which your mail log will show.
Limits and rules
| Limit | Value |
|---|---|
| Requests | 300 a minute per account |
| Recipients per request | 50 |
| Message size (SMTP) | 25 MB |
| Daily emails | 500 while on trial, raised on good results |
Rules we enforce for you
- Addresses that hard-bounced or reported spam are blocked automatically — sending to them again is what gets senders shut down.
- Marketing mail always carries a working one-click unsubscribe. It cannot be turned off.
- Sending pauses automatically above 5% bounces or 0.1% spam complaints, and we tell you why.
- Bought, rented or scraped lists are not allowed. Accounts using them are closed.
These are not bureaucracy: inbox providers judge senders on exactly these numbers, and one careless account affects everyone on the platform.
Still stuck?
Write to info@tripfindy.com with the message id from the response — we can see exactly what happened to it.