ESEN

API v1

Base URL: https://zend24.com/api/v1 · Auth: Authorization: Bearer sk_live_...

POST /emails

Sends an email. Fields: from, to (string or array, max 50), subject, and html, text or template + variables. Optional: cc, bcc, reply_to.

{
  "from": "App <noreply@yourdomain.com>",
  "to": ["one@example.com"],
  "subject": "Welcome, {{name}}",
  "template": "welcome",
  "variables": { "name": "Ana" }
}
→ 200 { "id": "01J2ZK7QW9..." }

Examples in your language:

curl -X POST https://zend24.com/api/v1/emails \
  -H "Authorization: Bearer sk_live_TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "App <noreply@yourdomain.com>",
    "to": "one@example.com",
    "subject": "Welcome, Ana",
    "html": "<h1>Thanks for your purchase</h1>"
  }'
const res = await fetch("https://zend24.com/api/v1/emails", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_TU_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "App <noreply@yourdomain.com>",
    to: "one@example.com",
    subject: "Welcome, Ana",
    html: "<h1>Thanks for your purchase</h1>",
  }),
});
console.log(await res.json());
import requests

requests.post(
    "https://zend24.com/api/v1/emails",
    headers={"Authorization": "Bearer sk_live_TU_API_KEY"},
    json={
        "from": "App <noreply@yourdomain.com>",
        "to": "one@example.com",
        "subject": "Welcome, Ana",
        "html": "<h1>Thanks for your purchase</h1>",
    },
)
<?php
$ch = curl_init('https://zend24.com/api/v1/emails');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_TU_API_KEY', 'Content-Type: application/json'],
  CURLOPT_POSTFIELDS => json_encode([
    'from' => 'App <noreply@yourdomain.com>', 'to' => 'one@example.com',
    'subject' => 'Welcome, Ana', 'html' => '<h1>Thanks for your purchase</h1>',
  ]),
]);
echo curl_exec($ch);
require 'net/http'
require 'json'

uri = URI('https://zend24.com/api/v1/emails')
req = Net::HTTP::Post.new(uri, {
  'Authorization' => 'Bearer sk_live_TU_API_KEY',
  'Content-Type'  => 'application/json'
})
req.body = {
  from: 'App <noreply@yourdomain.com>', to: 'one@example.com',
  subject: 'Welcome, Ana', html: '<h1>Thanks for your purchase</h1>'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body
package main

import ("bytes"; "io"; "net/http")

func main() {
	body := []byte(`{"from":"App <noreply@yourdomain.com>","to":"one@example.com","subject":"Welcome, Ana","html":"<h1>Thanks for your purchase</h1>"}`)
	req, _ := http.NewRequest("POST", "https://zend24.com/api/v1/emails", bytes.NewBuffer(body))
	req.Header.Set("Authorization", "Bearer sk_live_TU_API_KEY")
	req.Header.Set("Content-Type", "application/json")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	println(string(out))
}

Attachments

Add the attachments field (array). Each item: filename, content (the file contents in base64) and optional content_type (MIME). Up to 20 files and 1 MB total.

{
  "from": "App <noreply@yourdomain.com>",
  "to": "one@example.com",
  "subject": "Your July invoice",
  "html": "<h1>Thanks for your purchase</h1>",
  "attachments": [
    {
      "filename": "factura.pdf",
      "content_type": "application/pdf",
      "content": "JVBERi0xLjQKJ..."   # base64
    }
  ]
}

POST /emails/batch

Up to 100 sends in one call: { "emails": [ {...}, {...} ] }. Returns a result per index.

GET /emails/{id} · GET /emails

Individual status (status, opens, clicks) or paginated list with ?limit=&before= (cursor).

GET /domains · GET /suppressions · DELETE /suppressions/{email}

Domain status and suppression list management (DELETE requires a key with full scope).

Webhooks

Events: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked. Every POST includes X-Zend24-Timestamp and X-Zend24-Signature: v1=HMAC_SHA256(ts.body, secret). Verify the signature and reject timestamps older than 5 minutes.

Errors and limits

Errors: { "error": { "type", "message" } } with standard HTTP codes (401 auth, 403 domain, 422 validation, 429 rate limit/quota). The per-minute rate limit depends on your plan.