Build email into your stack.
Official SDKs, framework packages, a CLI, and an OpenAPI 3.1 specification. Choose the integration that fits your application.
The packages are being published to their registries. Until they are, the API reference shows every call over HTTP and the OpenAPI description is available now.
Official SDKs and framework packages
Every SDK covers the whole API, retries safely with an idempotency key, walks every page of a list, raises typed errors and verifies webhook signatures.
Node.js
TypeScript types for every request and response; ESM and CommonJS; no dependencies.
Publishing soonQuickstartnpm install zapsmtpPython
Sync and async clients, typed models, httpx underneath.
Publishing soonQuickstartpip install zapsmtpPHP
PSR-18 HTTP client, Guzzle by default; typed models with array access.
Publishing soonQuickstartcomposer require zapsmtp/zapsmtp-phpLaravel
A mail transport for your Mailables, a facade for the API and verified webhook events.
Publishing soonFramework guidecomposer require zapsmtp/zapsmtp-laravelGo
Standard library only; context on every call; range over every page.
Publishing soonQuickstartgo get github.com/zapsmtp/zapsmtp-goRuby
Standard library only; an ActionMailer delivery method for Rails.
Publishing soonQuickstartgem install zapsmtpRails
Set the delivery method and Action Mailer sends through the API; a Rack middleware verifies webhooks.
Publishing soonFramework guidegem "zapsmtp" # GemfileJava
Builders for every request, immutable models, java.net.http underneath.
Publishing soonQuickstartimplementation("com.zapsmtp:zapsmtp-java:1.0.0").NET
Async API with cancellation, dependency injection, IAsyncEnumerable paging.
Publishing soonQuickstartdotnet add package ZapSMTP
Your first template send and webhook check
Send the sign-in code template with its variables, then verify the signature of the webhook that follows: with a test key and a simulator address, an email.delivered event arrives, and the verification returns it as an event object or throws. Same language, both steps.
import { ZapSMTP } from 'zapsmtp';
const zap = new ZapSMTP(); // reads ZAP_API_KEY, on your server
await zap.emails.send({
from: 'Halden <security@haldenoutfitters.com>',
to: 'sofia.marin@example.com',
template: {
id: 'sign-in-code',
variables: {
name: 'Sofia',
code: '482 913',
expires_in: '10 minutes',
device: 'Safari on iPhone',
location: 'Portland, US',
time: '9:41 AM',
},
},
});import { verifyWebhook } from 'zapsmtp';
// the raw body, before any JSON parsing
const event = verifyWebhook(rawBody, req.headers, process.env.ZAP_WEBHOOK_SECRET);from zapsmtp import ZapSMTP
zap = ZapSMTP() # reads ZAP_API_KEY, on your server
zap.emails.send(
from_="Halden <security@haldenoutfitters.com>",
to="sofia.marin@example.com",
template={
"id": "sign-in-code",
"variables": {
"name": "Sofia",
"code": "482 913",
"expires_in": "10 minutes",
"device": "Safari on iPhone",
"location": "Portland, US",
"time": "9:41 AM",
},
},
)from zapsmtp import verify_webhook
event = verify_webhook(request.get_data(), request.headers, os.environ["ZAP_WEBHOOK_SECRET"])<?php
require 'vendor/autoload.php';
$zap = new \ZapSMTP\Client(); // reads ZAP_API_KEY, on your server
$zap->emails->send([
'from' => 'Halden <security@haldenoutfitters.com>',
'to' => 'sofia.marin@example.com',
'template' => ['id' => 'sign-in-code', 'variables' => [
'name' => 'Sofia',
'code' => '482 913',
'expires_in' => '10 minutes',
'device' => 'Safari on iPhone',
'location' => 'Portland, US',
'time' => '9:41 AM',
]],
]);$event = \ZapSMTP\Webhook::verify(
file_get_contents('php://input'),
getallheaders(),
getenv('ZAP_WEBHOOK_SECRET'),
);use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Mail;
use ZapSMTP\Laravel\Mail\InteractsWithZapSMTP;
class SignInCode extends Mailable
{
use InteractsWithZapSMTP;
public function __construct(array $variables)
{
$this->from('security@haldenoutfitters.com', 'Halden');
$this->zapsmtpTemplate('sign-in-code', $variables);
}
}
// MAIL_MAILER=zapsmtp in .env; then, on your server:
Mail::to('sofia.marin@example.com')->send(new SignInCode([
'name' => 'Sofia',
'code' => '482 913',
'expires_in' => '10 minutes',
'device' => 'Safari on iPhone',
'location' => 'Portland, US',
'time' => '9:41 AM',
]));$event = \ZapSMTP\Webhook::verify(
file_get_contents('php://input'),
getallheaders(),
getenv('ZAP_WEBHOOK_SECRET'),
);client := zapsmtp.NewClient("") // "" reads ZAP_API_KEY, on your server
_, err := client.Emails.Send(ctx, &zapsmtp.SendEmailParams{
From: zapsmtp.String("Halden <security@haldenoutfitters.com>"),
To: []string{"sofia.marin@example.com"},
Template: &zapsmtp.TemplateRef{ID: "sign-in-code", Variables: map[string]any{
"name": "Sofia",
"code": "482 913",
"expires_in": "10 minutes",
"device": "Safari on iPhone",
"location": "Portland, US",
"time": "9:41 AM",
}},
})body, _ := io.ReadAll(r.Body)
event, err := zapsmtp.VerifyWebhook(body, r.Header.Get("Zap-Signature"), os.Getenv("ZAP_WEBHOOK_SECRET"))require "zapsmtp"
zap = ZapSMTP::Client.new # reads ZAP_API_KEY, on your server
zap.emails.send(
from: "Halden <security@haldenoutfitters.com>",
to: "sofia.marin@example.com",
template: { id: "sign-in-code", variables: {
name: "Sofia",
code: "482 913",
expires_in: "10 minutes",
device: "Safari on iPhone",
location: "Portland, US",
time: "9:41 AM"
} }
)event = ZapSMTP::Webhook.verify(request.raw_post, request.headers, ENV.fetch("ZAP_WEBHOOK_SECRET"))# config/environments/production.rb
config.action_mailer.delivery_method = :zapsmtp
class SessionMailer < ApplicationMailer
def sign_in_code(variables)
mail(from: "Halden <security@haldenoutfitters.com>", to: "sofia.marin@example.com", body: "", zapsmtp: {
template: { id: "sign-in-code", variables: variables },
subject: nil # use the template's subject
})
end
end
SessionMailer.sign_in_code({
name: "Sofia",
code: "482 913",
expires_in: "10 minutes",
device: "Safari on iPhone",
location: "Portland, US",
time: "9:41 AM"
}).deliver_laterevent = ZapSMTP::Webhook.verify(request.raw_post, request.headers, ENV.fetch("ZAP_WEBHOOK_SECRET"))import com.zapsmtp.ZapSMTP;
import com.zapsmtp.params.SendEmailParams;
import com.zapsmtp.params.TemplateRef;
import java.util.Map;
ZapSMTP zap = ZapSMTP.fromEnv(); // reads ZAP_API_KEY, on your server
zap.emails().send(SendEmailParams.builder()
.from("Halden <security@haldenoutfitters.com>")
.to("sofia.marin@example.com")
.template(TemplateRef.builder().id("sign-in-code").variables(Map.of(
"name", "Sofia",
"code", "482 913",
"expires_in", "10 minutes",
"device", "Safari on iPhone",
"location", "Portland, US",
"time", "9:41 AM"
)).build())
.build());WebhookEvent event = Webhooks.verify(rawBody, headers, System.getenv("ZAP_WEBHOOK_SECRET"));using ZapSMTP;
var zap = new ZapSMTPClient(); // reads ZAP_API_KEY, on your server
await zap.Emails.SendAsync(new SendEmailParams
{
From = "Halden <security@haldenoutfitters.com>",
To = new[] { "sofia.marin@example.com" },
Template = new TemplateRef
{
Id = "sign-in-code",
Variables = new Dictionary<string, object?>
{
["name"] = "Sofia",
["code"] = "482 913",
["expires_in"] = "10 minutes",
["device"] = "Safari on iPhone",
["location"] = "Portland, US",
["time"] = "9:41 AM",
},
},
});var body = await new StreamReader(Request.Body).ReadToEndAsync();
var evt = Webhooks.Verify(body, Request.Headers["Zap-Signature"].ToString(), secret);A zap_test_… key runs the whole pipeline and delivers nothing; the simulator address acts out a delivery. Pass the raw body to the verification, before any JSON parsing. Webhook events
The CLI and the OpenAPI description
The zapsmtp command has a command for every API operation, prints tables in a terminal and JSON when piped, and exits non-zero with the error code and request id.
The whole API is in an OpenAPI 3.1 description: import it into Postman or Insomnia, or generate a client for a language without an SDK. Each operation’s x-sdk-method names the SDK method that calls it.
npm install -g zapsmtp-cli
zapsmtp login # checks the key, keeps it readable by you only
zapsmtp test # an email to the simulator, then its events
zapsmtp send --from "Acme <hello@acme.com>" --to jane@example.org \
--subject "Your receipt" --html-file receipt.html --attach ./invoice.pdf
zapsmtp emails list --status bounced --all --json | jq '.[].to'
zapsmtp domains verify <domain-id>Coming from another provider
Field and event maps, template conversion, suppression import and a gradual rollout plan, provider by provider. Applications that send over SMTP only change the host and credentials.
Everything else you need
AI tools
The MCP server, Agent Skills, llms.txt and Markdown docs for coding assistants.
AI toolsAPI reference
Every endpoint with parameters, responses, errors and examples in nine languages.
API referenceChangelog
What changed in the API, the dashboard and the SDKs.
ChangelogSecurity
Scoped and expiring API keys, signed webhooks and how we handle your data.
SecuritySupport
Questions about an integration or a migration.
Contact us
Send your first email today.
Create a test key and send to the simulator, over HTTP today or with your SDK once it is published.