Transactional Email API for Apps and AI Agents
Mailfully is a transactional email API for developers. Your app sends receipts, password resets and sign-in links with one API call, and can receive email by webhook. 5,000 emails a month are free, and paid plans start at $19.
Past a paid plan's allowance, you pay $0.40 per 1,000 emails.
import { Mailfully } from "mailfully";
const mailfully = new Mailfully({
apiKey: process.env.MAILFULLY_API_KEY ?? "",
});
const { data } = await mailfully.emails.send({
from: "you@yourdomain.com",
to: "customer@example.com",
subject: "Welcome aboard",
html: "<p>Thanks for signing up!</p>",
});Features
Custom domains, receiving, templates, webhooks, suppressions, and a log for all messages.
API
One authenticated POST sends an email. A typed REST API under /v1, snake_case on the wire, with a single error envelope. The official mailfully SDK returns a data-or-error result and never throws.
Per-message logs
Every message keeps its full event timeline (accepted, message id, delivery, bounce, complaint), down to the exact reply from the receiving mail server.
Templates
Reusable email templates with variable placeholders, managed through the API and dashboard. Change an email's copy without editing code.
Security
Scoped API keys authenticate every request. We store only a hash, never your secret. Tenant data is isolated by construction and encrypted at rest, and the API and dashboard answer over HTTPS only.
Custom sending domains
Verify your own sending domain with guided DKIM, SPF, and DMARC records, then send as you@yourdomain.com with authentication aligned before your first message goes out.
Receiving email
Receive mail at a domain you own or at a mailfully.app address we host. Mail arrives by webhook and through the API, with attachments and spam, virus, SPF, DKIM and DMARC results. Received mail counts toward the plan's monthly emails, with no separate fee for receiving.
Webhooks
Delivery, bounce, and complaint events arrive as signed, retried webhooks, with a per-endpoint circuit breaker and replay.
Suppressions
Hard bounces and complaints are suppressed automatically and stay out of future sends. Inspect, add, and remove entries anytime.
Analytics Dashboard
The bounce meter warns at 2%, half the 4% threshold in our sending policy, so you have time to clean a list before sending is affected. Break any figure down by recipient domain or tag, and the per-message log behind it is one click away.

Install the SDK
The official SDK is a typed wrapper over fetch on Node 24+ with zero dependencies of its own, and every call returns a data-or-error result, so failures are values you handle rather than exceptions that crash a background job. Skip the SDK entirely if you prefer: the REST API needs nothing installed at all.
npm install mailfully
# or: pnpm add mailfully
# yarn add mailfullyREST API and Node.js SDK
Send with one HTTPS call from any language, or use the official Node.js client and CLI from npm. Every message gets the same logging and suppression checks.
Sending infrastructure
Mail goes out over sending infrastructure with years of reputation behind it. Logs, billing, suppression, and abuse handling are the layer we built on top.
Templates With Variables
Store the markup once, then send it by id with the variables filled in. Placeholders are plain {{ product }} , so there is no template language to learn, and values are HTML-escaped on the way in. Edit the body in the dashboard or over the API; the send call never changes.
// The template owns the markup.
await mailfully.emails.send({
from: "orders@yourdomain.com",
to: "customer@example.com",
subject: "Your order is confirmed",
template: {
id: "tmpl_01JQ8ZC3S8W0V6M4T2Y7N9RXKD",
variables: {
product: "Vintage Macintosh",
price: "$499.00",
},
},
});Webhooks
Event types (delivered, bounced, complained, failed, delayed, opened, clicked, suppressed, received) arrive as one JSON envelope, signed with Svix-compatible webhook-signature headers you can verify with an off-the-shelf library. Delivery is at-least-once behind a per-endpoint circuit breaker, so dedupe on webhook-id and answer with any 2xx.
import type { MailfullyEventType } from "mailfully";
export async function POST(req: Request) {
const event = (await req.json()) as {
type: MailfullyEventType;
data: { email_id: string; to: string[] };
};
if (event.type === "email.bounced") {
await deactivateAddress(event.data.to);
}
// Any 2xx acks it.
return new Response(null, { status: 204 });
}Test Mode
Swap mf_live_ for mf_test_ and the same code runs the same path (same API, same delivery pipeline, same webhooks) into the mailbox simulator instead of a real inbox. Address a message to bounce@example.com and the bounce event fires for you to build against. Test sends never touch your reputation or quota, and are never metered.
# Development — the real send path, into the mailbox simulator.
MAILFULLY_API_KEY=mf_test_xxxxxxxxxxxxxxxx
# Production
MAILFULLY_API_KEY=mf_live_xxxxxxxxxxxxxxxxEmail for AI Agents
- Turn on a hosted mailfully.app address in your dashboard settings. It needs no DNS.
- Give each agent its own API key, and revoke it if it leaks.
- Use test mode so an agent can send without mailing a real person.
Reviews
“We moved off SendGrid without rewriting anything. The send is one POST, and the SDK hands back errors as values, so our retry code got shorter.”
“The per-message log is why we stayed. When a customer says the receipt never arrived, I open the message and read what the receiving server actually said.”
“Domain setup was the DNS records and a verify button. Test keys go through the same pipeline as live ones, so CI sends real mail into the simulator and asserts on the bounce webhook.”
Switching from another provider? See how Mailfully compares with Resend, Postmark, SendGrid, Brevo, Mailgun and Mailchimp Transactional.
From the Blog
How to set up SPF, DKIM, and DMARC for your sending domain
Set up SPF, DKIM, and DMARC for your sending domain, check a real message, and fix common DNS and alignment mistakes.
Getting Outbound Email to Work on DigitalOcean
Our Ghost install on a DigitalOcean droplet couldn't send its own invites, and weeks with support went nowhere. Some providers also answer on a high port like 2587, and one number in the config gets a droplet sending again.
Send your transactional mail from a subdomain
SPF doesn't cascade to subdomains, and every name carries exactly one record. Put those together and sending from your root domain leaves you one DNS edit away from breaking every email your company sends.
Start Free. Best in Class Pricing to Grow
5,000 emails a month free, then plans from $19/mo, with $0.40 per 1,000 emails past a paid plan's allowance.
Start free