Skip to content
Browse the docs

Frameworks and agents · Published 2026-11-09 · Updated 2026-11-09 · AgentiSend

Send email with Nodemailer

Send email with Nodemailer through an API. The transport posts the message to POST /emails and returns the message id, or throws the refusal's fix.

To send email with Nodemailer through an API, pass createTransport() from nodemailer-agentisend-transport to nodemailer.createTransport. Nodemailer still builds the message. The transport posts that message to POST /emails and returns the id from the response as messageId. A refusal is thrown with the API's fix on the error, so the caller can read what to change.

Install

npm install nodemailer nodemailer-agentisend-transport

Environment

VariableRequiredWhat it is
AGENTISEND_API_KEYyesA key with sending_access. createTransport({ apiKey }) overrides it.
MAIL_FROMyesThe From address, on a domain you have verified.
AGENTISEND_BASE_URLnoDefaults to https://api.agentisend.com. createTransport({ baseUrl }) overrides it.

The swap

import nodemailer from 'nodemailer';
import { createTransport } from 'nodemailer-agentisend-transport';

const transporter = nodemailer.createTransport(createTransport());
const info = await transporter.sendMail({ from, to, subject, text });
console.log(info.messageId);
swap

from, to, subject and text are the fields Nodemailer already takes. createTransport() reads the key and the base URL from the environment.

What is posted

replyTo is sent as reply_to. An attachment's bytes go out as base64 content, with filename, content_type and content_id when the message had them. A list.unsubscribe value is sent as a List-Unsubscribe header, and every other header set on the message is sent in headers. The id in the response is info.messageId. Read that message with GET /emails/{id}. Create the key the transport sends with POST /api-keys.

curl -sS -X POST https://api.agentisend.com/emails \
  -H "Authorization: Bearer $AGENTISEND_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"from":"billing@yourdomain.com","to":"delivered@simulator.agentisend.com","subject":"Your receipt","text":"Thanks — the details are attached."}'
201
{
  "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "simulated": true,
  "warnings": []
}
Response
curl -sS -X GET https://api.agentisend.com/emails/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42 \
  -H "Authorization: Bearer $AGENTISEND_API_KEY"
200
{
  "api_key_id": "example",
  "attachments": [],
  "bcc": [],
  "cc": [],
  "created_at": "2026-09-04T09:14:00.000Z",
  "domain": "example",
  "from": "example",
  "headers": {},
  "html": "example",
  "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "last_event": "queued",
  "message_id": "example",
  "object": "email",
  "provider_message_id": "example",
  "reply_to": [],
  "scheduled_at": "2026-09-04T09:14:00.000Z",
  "status": "queued",
  "subject": "example",
  "tags": {},
  "template_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "template_version": -9007199254740991,
  "text": "example",
  "to": [],
  "updated_at": "2026-09-04T09:14:00.000Z",
  "warnings": []
}
Response
curl -sS -X POST https://api.agentisend.com/api-keys \
  -H "Authorization: Bearer $AGENTISEND_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"example","permission":"full_access"}'
201
{
  "budget_per_period": 5000,
  "created_at": "2026-09-04T09:14:00.000Z",
  "domain_scope": "example",
  "expires_at": "2026-09-04T09:14:00.000Z",
  "id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
  "last_used_at": "2026-09-04T09:14:00.000Z",
  "loop_guard": "off",
  "name": "example",
  "object": "api_key",
  "period": "monthly",
  "permission": "full_access",
  "previous_key_expires_at": "2026-09-04T09:14:00.000Z",
  "rate_ceiling_per_minute": 600,
  "request_count_30d": -9007199254740991,
  "rotated_at": "2026-09-04T09:14:00.000Z",
  "scopes": [],
  "system": false,
  "token": "as_abababababababababababababababababababababababababababababababab",
  "token_prefix": "as_abababab"
}
Response

What comes back when it goes wrong

Every refused request carries code, message, fix and docs_url. The thrown error's fix is that field, unchanged, and its code is the catalogue code. The error catalogue lists every code.

Next