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:

  1. A verified domain. Add it in your dashboard and copy the DNS records it shows you. We check them every minute.
  2. Approval. Submit your business details; we review them, usually the same day. This protects everyone’s delivery, including yours.
  3. 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.

Base addresshttps://api.gotimail.com
FormatJSON over HTTPS. Plain HTTP is refused.

Authentication

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_xxxxxxxxxxxxxxxxxxxxxxxx

You 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

EndpointPOST /v1/send
curl -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

FieldRequiredNotes
fromyesMust use a domain you have verified. The address only — put the display name in fromName.
fromNamenoShown instead of the bare address.
toyesUp to 50 addresses per request.
subjectyesUp to 500 characters.
html / textone of themSend both where you can; some clients only read text.
replyTonoWhere replies go.
marketingnotrue 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

EndpointGET /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.

CodeMeaning
400Something in the request is wrong — a missing subject, a malformed address.
401The key is missing, wrong or revoked.
403Allowed 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.
409Every recipient is on your blocked list, or the delivery provider refused the message. The reason is included verbatim.
429Too 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.

Serversmtp.gotimail.com
Port2525
EncryptionSTARTTLS — required
Username and passwordCreated on the SMTP page in your dashboard

The 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

LimitValue
Requests300 a minute per account
Recipients per request50
Message size (SMTP)25 MB
Daily emails500 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.