All systems operational
Email Sending API

Send email through Sendelif

A JSON HTTP API for transactional and marketing email — authenticated with a single header, delivered from warmed, reputation-managed IPs with SPF, DKIM and DMARC handled for you.

Base URL https://api.sendelif.com

Overview

The Sendelif API accepts a message as structured JSON and handles MIME assembly, DKIM signing, queueing, retries and delivery for you. Every request is authenticated with one header, and every response uses the same predictable envelope so you can parse success and failure the same way.

A message is accepted asynchronously: a success response means queued for delivery, not delivered. You learn the final outcome from the delivery-status endpoint or from webhooks.

Before you start

You need two things, both issued and configured by the Sendelif operator:

  • An API key — one per application. It grants full send rights for your app, so treat it as a secret: keep it in an environment variable or a secrets manager, never in source control or a browser bundle.
  • A verified sending domain — your From address must use a domain that has been configured and DNS-verified (SPF + DKIM) on the platform. You cannot send from an arbitrary domain; the platform rejects it.

All requests are made over HTTPS. Plain HTTP is not served for the API — a request to http:// will not reach it.

Authentication

Send your key in the X-Server-API-Key header on every request. There is no OAuth, no bearer token and no login step.

headers
X-Server-API-Key: your-server-api-key
Content-Type: application/json

A missing or invalid key returns an AccessDenied error:

json
{ "status": "error", "time": 0.0, "flags": {},
  "data": { "message": "Must be authenticated as a server.", "code": "AccessDenied" } }

Send a message

POST/api/v1/send/message

Provide the message as structured fields and Sendelif builds the MIME for you.

FieldTypeRequiredNotes
tostring[]yes*Recipient addresses. *At least one of to/cc/bcc.
ccstring[]Carbon-copy recipients.
bccstring[]Blind carbon-copy recipients.
fromstringyesMust be an address on a verified domain. Name <addr@domain> is allowed.
subjectstringrec.Message subject.
reply_tostringReply-To header.
plain_bodystringyes**Plain-text body. **One of plain_body/html_body.
html_bodystringyes**HTML body. Provide both for best rendering.
attachmentsobject[]{ name, content_type, data } where data is base64.
headersobjectExtra headers, e.g. { "X-Entity-Ref": "42" }.
tagstringA label you choose, echoed back in webhooks and status lookups.

Request

curl
curl -s -X POST https://api.sendelif.com/api/v1/send/message \
  -H "X-Server-API-Key: $SENDELIF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "to": ["customer@example.com"],
        "from": "Your App <no-reply@yourdomain.com>",
        "subject": "Your verification code",
        "plain_body": "Your code is 483920. It expires in 10 minutes.",
        "html_body": "<p>Your code is <strong>483920</strong>.</p>"
      }'

Response

json
{
  "status": "success", "time": 0.04, "flags": {},
  "data": {
    "message_id": "95d71f85-4842-4bc2-aef6-811502b86b93@rp.sendelif.com",
    "messages": {
      "customer@example.com": { "id": 42, "token": "BXBUBTeOkfzfKgb6" }
    }
  }
}

Keep the per-recipient id (and token) if you want to look up delivery status later or correlate incoming webhooks.

status: "success" means accepted, not delivered. Delivery is asynchronous — poll the status endpoint or use webhooks for the final outcome.

Delivery status

POST/api/v1/messages/message

Look up the current status and details of a message by its id, expanding the blocks you want.

curl
curl -s -X POST https://api.sendelif.com/api/v1/messages/message \
  -H "X-Server-API-Key: $SENDELIF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "id": 42, "_expansions": ["status", "details"] }'
json
{
  "status": "success", "time": 0.003, "flags": {},
  "data": {
    "id": 42, "token": "VIk7zkLfhdU1ogjO",
    "status": { "status": "Sent", "held": false, "hold_expiry": null,
                "last_delivery_attempt": 1783855418.47 },
    "details": { "rcpt_to": "customer@example.com", "direction": "outgoing",
                 "message_id": "...@rp.sendelif.com", "bounce": false,
                 "received_with_ssl": true }
  }
}

The status.status field is one of:

PendingSentSoftFail HardFailHeldBounced

Pending is queued; Sent was accepted by the recipient's server; SoftFail is a temporary failure that will retry; HardFail is permanent (this includes mail blocked by outbound spam scanning); Held is held for review; Bounced came back after initial acceptance.

Raw MIME send

POST/api/v1/send/raw

If you assemble your own RFC 5322 message, submit it directly. Prefer send/message unless you specifically need control of the raw MIME.

body
{
  "mail_from": "no-reply@yourdomain.com",
  "rcpt_to": ["customer@example.com"],
  "data": "<base64 of the full MIME message>"
}

SMTP submission

If your stack can't use the HTTP API, submit over SMTP instead:

  • Host / port: mail1.sendelif.com, port 25.
  • Auth: AUTH PLAIN — username can be anything, password is your SMTP key (issued alongside the API key).
  • The same rules apply: verified From, outbound scanning, send limits.

The HTTP API is the recommended path — it's TLS end-to-end from your app, returns the message id/token for status lookups, and gives structured errors.

Sending rules & limits

These are enforced by the platform — design your integration around them:

  • From must be a verified domain. Sending from a domain not configured for your app is rejected with UnauthenticatedFromAddress. There is no Sender-header fallback to spoof a domain you don't own.
  • Outbound content is scanned. Every message is scored on the way out; anything over the threshold is blocked (returned as HardFail, not delivered) to protect the shared sending reputation. Normal mail scores well under it — avoid spammy subjects, all-caps and link shorteners.
  • Daily send limit. Each app has a per-day cap that rises as the sending IPs warm up. Exceeding it defers messages. Ask the operator for your current cap.
  • Bulk mail must be unsubscribable. For non-transactional mail, include a working List-Unsubscribe header and honor it — required by Gmail and Yahoo bulk-sender rules.
  • No native idempotency keys. The API does not de-duplicate retries. Only retry on network or 5xx errors, and track which of your own events you've already dispatched.

Webhooks

To receive delivery events instead of polling, give the operator an HTTPS endpoint and they subscribe it to your app's events. Payloads are JSON and include the message id, token, status, recipient and your tag. Common event types:

MessageSentMessageDeliveredMessageDeliveryFailed MessageHeldMessageBounced

Always verify the X-Postal-Signature header against the platform's public key before trusting a payload, and respond 2xx quickly — do heavy work asynchronously.

Errors & retries

Every response uses the same envelope. On error, status is "error" and data carries a code and a human message.

CodeMeaningWhat to do
AccessDeniedMissing / invalid API keyCheck the X-Server-API-Key header.
NoRecipientsNo to/cc/bcc givenAdd at least one recipient.
UnauthenticatedFromAddressFrom domain not verified for your appUse a verified domain.
ValidationErrorA field failed validationRead message, fix the payload.

The API returns HTTP 200 with status: "error" for most application errors. Always branch on the status field, not on the HTTP status code alone. And because there's no idempotency key, a retry after a successful accept sends twice.

Code examples

Python

python
import os, requests

r = requests.post(
    "https://api.sendelif.com/api/v1/send/message",
    headers={"X-Server-API-Key": os.environ["SENDELIF_API_KEY"]},
    json={
        "to": ["customer@example.com"],
        "from": "Your App <no-reply@yourdomain.com>",
        "subject": "Welcome",
        "html_body": "<p>Thanks for signing up.</p>",
    },
    timeout=20,
)
body = r.json()
if body["status"] != "success":
    raise RuntimeError(f"send failed: {body['data']}")
message_id = body["data"]["messages"]["customer@example.com"]["id"]

Node.js

javascript
const res = await fetch("https://api.sendelif.com/api/v1/send/message", {
  method: "POST",
  headers: {
    "X-Server-API-Key": process.env.SENDELIF_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: ["customer@example.com"],
    from: "Your App <no-reply@yourdomain.com>",
    subject: "Welcome",
    html_body: "<p>Thanks for signing up.</p>",
  }),
});
const body = await res.json();
if (body.status !== "success") throw new Error(JSON.stringify(body.data));

PHP

php
$ch = curl_init("https://api.sendelif.com/api/v1/send/message");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "X-Server-API-Key: " . getenv("SENDELIF_API_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "to" => ["customer@example.com"],
    "from" => "Your App <no-reply@yourdomain.com>",
    "subject" => "Welcome",
    "html_body" => "<p>Thanks for signing up.</p>",
  ]),
]);
$body = json_decode(curl_exec($ch), true);
if (($body["status"] ?? "") !== "success") { throw new Exception("send failed"); }

What's coming

The following are planned and not yet available — don't build against them until announced. The core send/message shape above is intended to stay backward-compatible when they ship.

  • Per-app scoped, rotatable, expiring API tokens (replacing the single per-server key).
  • Per-request rate-limit headers (X-RateLimit-*) and clearer 429 semantics.
  • A sandbox / test mode that validates and accepts without actually sending.
  • Automatic one-click List-Unsubscribe (RFC 8058) injection for marketing mail.
  • Self-service webhook management and delivery-event streaming.