Your webhook is the system of record — MailKite is the pipe. Pick per domain how much (if anything) gets kept: short auto-expiring retention by default, zero-retention passthrough so no message body is ever stored, or at-rest encryption with a public key you generate, so what we do store we can't read.
Flip it on for a domain and MailKite never stores a message body — attachments arrive inlined as base64 instead of a stored, signed URL.
Generate an RSA keypair, paste the public key in, and every message we do retain is encrypted to it — only your private key can ever decrypt it.
Set retention mode independently on each domain — a low-sensitivity product can keep default retention while a sensitive one runs zero-retention.
On zero-retention or encrypted domains, attachments skip the signed-URL step entirely and arrive inline in the payload — nothing left behind to fetch later.
Leave a domain on default short retention, or set it to zero-retention passthrough, from the dashboard or the API.
Create an RSA keypair locally, keep the private key secret, and paste the public key into the domain's encryption setting.
Every message on an encrypted domain arrives as an envelope; unwrap it with your private key using the documented decrypt routine.
One call per domain — nothing else in your integration changes, except attachments arrive inline instead of as a link.
Dashboard → Domains → myapp.ai → Privacy
Toggle Zero-retention passthrough ● on
Click [ Save ]
Nothing is stored: attachments arrive inlined as base64
in the webhook instead of as a signed URL.Turn on zero-retention passthrough for myapp.ai — don't store
any message bodies for that domain.import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);
// No message body is stored for this domain from here on.
await mk.request("PUT", "/api/domains/dom_…/retention", { zeroRetention: true });import os
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])
# No message body is stored for this domain from here on.
mk.request("PUT", "/api/domains/dom_…/retention", {"zeroRetention": True})<?php
$mk = new \MailKite\Client(getenv('MAILKITE_API_KEY'));
// No message body is stored for this domain from here on.
$mk->request('PUT', '/api/domains/dom_…/retention', ['zeroRetention' => true]);MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));
// No message body is stored for this domain from here on.
mk.request("PUT", "/api/domains/dom_…/retention", Map.of("zeroRetention", true));mk := mailkite.New(os.Getenv("MAILKITE_API_KEY"))
// No message body is stored for this domain from here on.
_, err := mk.Request("PUT", "/api/domains/dom_…/retention", map[string]any{
"zeroRetention": true,
})require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])
# No message body is stored for this domain from here on.
mk.request("PUT", "/api/domains/dom_…/retention", "zeroRetention" => true)curl -X PUT https://api.mailkite.dev/api/domains/dom_…/retention \
-H "Authorization: Bearer $MAILKITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "zeroRetention": true }' {
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" }{
"domain": { "id": "dom_…", "domain": "myapp.ai", "zero_retention": 1 }
}
// Attachments on a zero-retention domain arrive inlined, no signed URL:
// { "filename": "po.pdf", "contentType": "application/pdf", "content": "JVBERi0x…" } Messages are kept for a short, auto-expiring retention window by default so you can list, inspect, and replay them briefly — after that they're gone, unless you set a longer window on a paid plan.
Zero-retention means nothing is stored at all — your webhook holds the only copy. Encryption means we still store the message, but as ciphertext keyed to your public key, so we can't read it even though it exists.
Yes — retention and encryption are configured per domain, so you can run different policies for different products on the same account.
Start free on unlimited domains — no credit card. Or browse the other solutions.