useSendEverJust

useSend for AI agents

Patterns for calling useSend safely from AI agents — idempotent sends, delivery checks, webhook-driven reactions, and the MCP server.

useSend is a good fit for autonomous agents: a small REST surface, a single static Bearer token, JSON in and JSON out, an optional idempotency key for safe retries, and a structured status enum you can branch on. This page covers the patterns an agent needs to send mail without double-sending and to react to what happens afterward.

The fastest way to give an agent email is the useSend MCP server — it exposes send / status / domain tools directly to the model, so you write no HTTP glue. Reach for the raw REST patterns below when you are building your own tool wrappers or working outside an MCP host.

Why the API is agent-friendly

  • One base URL, one credential. Everything is under https://mail.everjust.app/api/v1 with Authorization: Bearer us_xxx. No token refresh, no OAuth dance, no per-request signing.
  • JSON request and response. Send returns {"emailId":"..."}; errors return a stable envelope {"error":{"code":"...","message":"..."}} with codes like BAD_REQUEST, UNAUTHORIZED, NOT_FOUND. Easy to parse and branch on.
  • Idempotent sends. An optional Idempotency-Key header makes a retried send return the original result instead of sending twice — the single most important property for a tool an agent may call more than once.
  • Structured status. Delivery state is a fixed enum (QUEUED, SENT, DELIVERED, BOUNCED, COMPLAINED, FAILED, …), so a model can reason about outcomes without scraping prose.
  • No rate limiting. This self-hosted instance has rate limiting disabled, so an agent will not hit RATE_LIMITED (429) under normal load. Still write retries defensively.

SES is currently in sandbox. Until AWS grants production access, sends only succeed to verified addresses or the SES mailbox simulator (success@simulator.amazonses.com, bounce@…, complaint@…). An agent testing end-to-end should target success@simulator.amazonses.com — it accepts and "delivers" without a real inbox. See Sending email for the full sandbox rules.

Pattern 1 — Send a transactional message

The core action. to and from are required; you need subject (or a templateId) and text or html.

curl -sS -X POST https://mail.everjust.app/api/v1/emails \
  -H "Authorization: Bearer us_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "EverJust <hello@send.everjust.app>",
    "to": "success@simulator.amazonses.com",
    "subject": "Your report is ready",
    "text": "The nightly report finished. Reply if anything looks off."
  }'
# -> {"emailId":"clz8k2p9x0001..."}
import { UseSend } from "usesend-js";

const usesend = new UseSend("us_xxx", "https://mail.everjust.app/api/v1");

const { data, error } = await usesend.emails.send({
  from: "EverJust <hello@send.everjust.app>",
  to: "success@simulator.amazonses.com",
  subject: "Your report is ready",
  text: "The nightly report finished. Reply if anything looks off.",
});

if (error) {
  // structured: error.code, error.message
  throw new Error(`send failed: ${error.code} ${error.message}`);
}
console.log(data.emailId);

Pattern 2 — Idempotency for safe retries

Agents retry: a step re-runs after a timeout, a workflow resumes, a tool call is attempted twice. Attach an Idempotency-Key you derive deterministically from the unit of work (an order id, a task id, a hash of the payload). A repeated call with the same key returns the original emailId instead of sending a second email.

curl -sS -X POST https://mail.everjust.app/api/v1/emails \
  -H "Authorization: Bearer us_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821-confirmation" \
  -d '{
    "from": "EverJust <hello@send.everjust.app>",
    "to": "success@simulator.amazonses.com",
    "subject": "Order 4821 confirmed",
    "html": "<p>We are on it.</p>"
  }'
const key = `order-${order.id}-confirmation`; // deterministic per task

const { data, error } = await usesend.emails.send(
  {
    from: "EverJust <hello@send.everjust.app>",
    to: order.email,
    subject: `Order ${order.id} confirmed`,
    html: "<p>We are on it.</p>",
  },
  { idempotencyKey: key }
);

The same Idempotency-Key header applies to POST /v1/emails/batch. Choose keys tied to the work, not the wall clock — task-1234, not a timestamp — so a retry produces the same key.

Pattern 3 — Check delivery by id

After sending, poll the email to see how far it got. GET /v1/emails/{emailId} returns the record plus an emailEvents array; the last event's status is the current state.

curl -sS https://mail.everjust.app/api/v1/emails/clz8k2p9x0001 \
  -H "Authorization: Bearer us_xxx"
const email = await usesend.emails.get(emailId);
const latest = email.emailEvents.at(-1)?.status;

switch (latest) {
  case "DELIVERED":
    return "ok";
  case "BOUNCED":
  case "COMPLAINED":
  case "REJECTED":
  case "FAILED":
    return "give-up"; // do not retry the same address
  default:
    return "pending"; // QUEUED / SENT — poll again shortly
}

Terminal-failure statuses (BOUNCED, COMPLAINED, REJECTED, FAILED, SUPPRESSED) mean the address will not accept mail — an agent should stop retrying that recipient rather than loop. Full status list is in the API reference.

Pattern 4 — React to outcomes via webhooks

Polling is fine for a one-off send, but for fire-and-continue workflows, let useSend push the outcome to you. Configure a webhook endpoint (dashboard only) and handle events like email.delivered, email.bounced, email.complained, email.opened, and email.clicked. Each request is HMAC-SHA256 signed — verify it on the raw body before acting.

// Express-style handler; needs the raw body, not the parsed one
import { UseSend } from "usesend-js";

const usesend = new UseSend("us_xxx", "https://mail.everjust.app/api/v1");
const wh = usesend.webhooks("whsec_...");

app.post("/webhooks/usesend", (req, res) => {
  let event;
  try {
    event = wh.constructEvent(req.rawBody, { headers: req.headers });
  } catch {
    return res.status(400).end(); // bad signature
  }

  if (event.type === "email.bounced") {
    suppress(event.data); // event.data.bounce = { type, subType, message }
  }
  res.status(200).end(); // must 2xx within 10s, else retried up to 6x
});

See Webhooks for the full event catalog, signature scheme, and payload shapes.

A minimal agent tool

If you are wiring useSend into an agent framework as a callable tool, keep the surface tiny: one function that sends and returns the id, with the idempotency key threaded through so the framework's retry logic is safe.

import { UseSend } from "usesend-js";

const usesend = new UseSend(
  process.env.USESEND_API_KEY!, // us_xxx
  "https://mail.everjust.app/api/v1"
);

/** Tool: send_email — returns the useSend email id. */
export async function sendEmail(args: {
  to: string;
  subject: string;
  body: string;
  idempotencyKey: string; // required: pass a stable per-task key
}) {
  const { data, error } = await usesend.emails.send(
    {
      from: "EverJust <hello@send.everjust.app>",
      to: args.to,
      subject: args.subject,
      text: args.body,
    },
    { idempotencyKey: args.idempotencyKey }
  );
  if (error) return { ok: false, code: error.code, message: error.message };
  return { ok: true, emailId: data.emailId };
}
# The same tool as a shell one-liner an agent can exec.
# $KEY is the deterministic idempotency key for this task.
curl -sS -X POST https://mail.everjust.app/api/v1/emails \
  -H "Authorization: Bearer $USESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d "{
    \"from\": \"EverJust <hello@send.everjust.app>\",
    \"to\": \"$TO\",
    \"subject\": \"$SUBJECT\",
    \"text\": \"$BODY\"
  }"

For agents that read docs

This documentation is published with an /llms.txt index — a machine-readable map of every page, so an agent that ingests docs can discover the API surface without crawling HTML. Point your doc-reading agent at https://mail.everjust.app/llms.txt (docs are served alongside the dashboard).

One API key belongs to one Team (tenant), which maps to one EverJust product. Give each product its own key — never share us_ keys across products. The pre-provisioned Team is EverJust, sending from the verified domain send.everjust.app.