Send email with Postmark
Transactional email is the mail your app sends one recipient at a time — receipts, password resets, magic links, notifications. This guide sends one through Postmark, over both the API and SMTP, then shows the same send on MailKite.
What you'll need
- A domain you control, with access to its DNS records.
- A Server API token from a Postmark server in your account.
Part 1 — Send with Postmark
1. Authenticate your domain
So your mail passes authentication and lands in the inbox, add your domain
under Sender Signatures → Domains and publish the records Postmark
shows you — a DKIM TXT record and a custom Return-Path
CNAME:
Type Name Value
TXT 20240101._domainkey.yourdomain.com k=rsa; p=… (DKIM shown in your dashboard)
CNAME pm-bounces.yourdomain.com pm.mtasv.net (custom Return-Path) Postmark shows the exact per-domain DKIM selector and value in the dashboard — copy them from there, then click Verify.
2. Send over the API
POST to Postmark's email endpoint with your Server API token in
the X-Postmark-Server-Token header. The From address
must be on the domain you just authenticated:
curl -X POST https://api.postmarkapp.com/email \
-H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"From": "hello@yourdomain.com",
"To": "ada@example.com",
"Subject": "Your invoice #1042",
"HtmlBody": "<p>Thanks! Your receipt is attached.</p>",
"TextBody": "Thanks! Your receipt is attached.",
"MessageStream": "outbound"
}' ← 200 OK
{
"To": "ada@example.com",
"SubmittedAt": "2024-01-01T12:00:00.0000000-05:00",
"MessageID": "b7bc2f4a-e38e-4336-af7d-e6c392c2f817",
"ErrorCode": 0,
"Message": "OK"
} A 200 with "ErrorCode": 0 means Postmark accepted the message.
3. Or send over SMTP
Prefer SMTP? Point your app at Postmark's relay. Your Server API token is
both the username and the password, and the
X-PM-Message-Stream header selects the message stream:
Host: smtp.postmarkapp.com
Port: 587 # STARTTLS (25 or 2525 also work)
Username: $POSTMARK_SERVER_TOKEN # your Server API token
Password: $POSTMARK_SERVER_TOKEN # the same token
Header: X-PM-Message-Stream: outbound That's a transactional send on Postmark: authenticate the domain, then call the API or the SMTP relay.
Part 2 — The same, on MailKite
When we built sending into MailKite, we wanted three things to be true: one set of DNS records, one API key that works for both HTTP and SMTP, and a body you don't have to nest. Here's the same message.
1. Authenticate your domain
Publish three TXT records — SPF, a MailKite DKIM key, and DMARC.
The same domain also receives mail once its MX points at MailKite:
Type Name Value
TXT yourdomain.com v=spf1 include:mailkite.dev ~all
TXT mailkite._domainkey.yourdomain.com v=DKIM1; k=rsa; p=…
TXT _dmarc.yourdomain.com v=DMARC1; p=none; The exact DKIM value is shown in the dashboard; MailKite verifies the records for you over DNS.
2. Send over the API
One POST /v1/send with a flat from/to/
subject/html body. A 202 means it's
queued:
Pick your language in the card — the dashboard and the AI assistant do the same send, and every SDK takes the same fields:
Messages → Compose
From hello@myapp.ai
To ada@example.com
Subject Your invoice #1042
Body Thanks! Receipt attached.
Click [ Send ]Email ada@example.com from hello@myapp.ai
with subject "Your invoice #1042" and body "Thanks! Receipt attached."import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
const { id, status } = await mk.send({
from: "hello@myapp.ai",
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});import os
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])
res = mk.send({
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>",
})<?php
$mk = new \MailKite\Client(getenv('MAILKITE_API_KEY'));
$res = $mk->send([
'from' => 'hello@myapp.ai',
'to' => 'ada@example.com',
'subject' => 'Your invoice #1042',
'html' => '<p>Thanks! Receipt attached.</p>',
]);MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));
Object res = mk.send(Map.of(
"from", "hello@myapp.ai",
"to", "ada@example.com",
"subject", "Your invoice #1042",
"html", "<p>Thanks! Receipt attached.</p>"
));mk := mailkite.New(os.Getenv("MAILKITE_API_KEY"))
res, err := mk.Send(mailkite.Message{
From: "hello@myapp.ai",
To: "ada@example.com",
Subject: "Your invoice #1042",
HTML: "<p>Thanks! Receipt attached.</p>",
})require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])
res = mk.send(
"from" => "hello@myapp.ai",
"to" => "ada@example.com",
"subject" => "Your invoice #1042",
"html" => "<p>Thanks! Receipt attached.</p>"
)curl https://api.mailkite.dev/v1/send \
-H "Authorization: Bearer $MAILKITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@myapp.ai",
"to": "ada@example.com",
"subject": "Your invoice #1042",
"html": "<p>Thanks! Receipt attached.</p>"
}' Messages → Sent
● queued msg_2Hk9… ada@example.com Your invoice #1042Sent — message msg_2Hk9…, status queued.← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" }← 202 Accepted
{ "id": "msg_2Hk9…", "status": "queued" } 3. Or send over SMTP
The same key is your SMTP password — no separate credential to generate:
import nodemailer from "nodemailer";
const tx = nodemailer.createTransport({
host: "smtp.mailkite.dev",
port: 587, // STARTTLS · use 465 for implicit TLS
auth: {
user: "mailkite", // any username works
pass: process.env.MAILKITE_API_KEY, // your mk_live_… key
},
});
await tx.sendMail({
from: "hello@myapp.ai", // an address on a verified domain
to: "ada@example.com",
subject: "Your invoice #1042",
html: "<p>Thanks! Receipt attached.</p>",
});import os, smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "hello@myapp.ai" # an address on a verified domain
msg["To"] = "ada@example.com"
msg["Subject"] = "Your invoice #1042"
msg.set_content("Thanks! Receipt attached.")
# Standard library — no dependency to install.
with smtplib.SMTP("smtp.mailkite.dev", 587) as s:
s.starttls()
s.login("mailkite", os.environ["MAILKITE_API_KEY"]) # password = your key
s.send_message(msg)<?php
use PHPMailer\PHPMailer\PHPMailer;
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = 'smtp.mailkite.dev';
$mail->SMTPAuth = true;
$mail->Username = 'mailkite'; // any username
$mail->Password = getenv('MAILKITE_API_KEY'); // your mk_live_… key
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587; // or 465 for implicit TLS
$mail->setFrom('hello@myapp.ai'); // an address on a verified domain
$mail->addAddress('ada@example.com');
$mail->Subject = 'Your invoice #1042';
$mail->Body = 'Thanks! Receipt attached.';
$mail->send();Properties p = new Properties();
p.put("mail.smtp.host", "smtp.mailkite.dev");
p.put("mail.smtp.port", "587"); // 465 for implicit TLS
p.put("mail.smtp.auth", "true");
p.put("mail.smtp.starttls.enable", "true");
Session s = Session.getInstance(p, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
// username = anything · password = your mk_live_… key
return new PasswordAuthentication("mailkite", System.getenv("MAILKITE_API_KEY"));
}
});
Message msg = new MimeMessage(s);
msg.setFrom(new InternetAddress("hello@myapp.ai")); // a verified domain
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse("ada@example.com"));
msg.setSubject("Your invoice #1042");
msg.setText("Thanks! Receipt attached.");
Transport.send(msg);auth := smtp.PlainAuth("", "mailkite", os.Getenv("MAILKITE_API_KEY"), "smtp.mailkite.dev")
msg := []byte("From: hello@myapp.ai\r\n" + // an address on a verified domain
"To: ada@example.com\r\n" +
"Subject: Your invoice #1042\r\n\r\n" +
"Thanks! Receipt attached.\r\n")
// STARTTLS on 587; SendMail upgrades the connection before AUTH.
err := smtp.SendMail("smtp.mailkite.dev:587", auth,
"hello@myapp.ai", []string{"ada@example.com"}, msg)require "mail"
Mail.defaults do
delivery_method :smtp, {
address: "smtp.mailkite.dev",
port: 587, # 465 for implicit TLS
user_name: "mailkite", # any username
password: ENV["MAILKITE_API_KEY"], # your mk_live_… key
enable_starttls_auto: true,
}
end
Mail.deliver do
from "hello@myapp.ai" # an address on a verified domain
to "ada@example.com"
subject "Your invoice #1042"
body "Thanks! Receipt attached."
end See Send API and SMTP relay for the full reference — attachments, headers, and delivery events.
A little about MailKite
MailKite is programmable email for developers and AI agents. One API key and
one set of DNS records cover both directions: send with a single
POST (or the SMTP relay), and the same verified domain receives
replies as a signed webhook of clean JSON.
Behind the send, MailKite routes across multiple providers to keep you in the
inbox — and any address can be handed to an agent
that reads and replies on its own. Sending and receiving are on the
free tier.
Next: the Send API for attachments and delivery tracking, or the Quickstart to get a verified domain and API key.