Email clone
Capture outbound email across Resend, SendGrid v3, and SMTP in one clone — inspect inboxes, simulate bounces and spam traps, and advance the virtual clock for domain verification and delivery.
Clone ID: email · Binary: clones-email · SDK: email · Registry alias: sendgrid · Dashboard: Email explorer
Start here
| Question | Answer |
|---|---|
| Best for | Welcome emails, password reset, receipts, SendGrid dynamic templates, and bounce paths |
| Connect with | Resend/SendGrid SDK pointed at clone URL, route mode for api.sendgrid.com / api.resend.com, SMTP to port 1025, or MCP inbox tools |
| Known limits | No delivery to the real internet — all messages stay in the clone |
| Seeds | Empty by default; seed domains via JSON or create them over REST |
Quick start
import { email } from "@molar/clones";
const clone = await email.start({ seed: "welcome-flow", runId: "run-email-01" });
await clone.send({
from: "hello@example.com",
to: ["user@example.com"],
subject: "Welcome to Pro",
html: "<p>Thanks!</p>",
});
await clone.advanceClock("5s");
const messages = await clone.inbox("user@example.com");
MCP tools
| Tool | Description |
|---|---|
molar_clone_spawn | Spawn Email clone (kind: "email", optional seed) |
email_inbox | Read captured messages for a recipient (adminUrl, runId, recipient) |
email_mark_bounce | Mark recipient as hard-bounced for deliverability tests |
REST API coverage
| Provider shape | Endpoints |
|---|---|
| Resend | POST /emails, POST /emails/batch, GET /emails, GET /emails/:id |
| Resend domains | POST /domains, list/get/delete; status moves to verified on clock advance |
| Resend audiences | POST /audiences, POST /audiences/:id/contacts |
| SendGrid v3 | POST /v3/mail/send with dynamic_template_data rendering |
| SMTP | Mailpit-compatible capture on 127.0.0.1:1025 (raw messages land in the same inbox) |
| Mailpit-compat | GET /api/v1/messages, POST /api/v1/chaos |
| Admin | Inbox list, bounce/spam-trap flags |
Set RESEND_API_KEY or SendGrid key to the derived test key from the SDK spawn response.
Seeds and initial state
There is no bundled welcome seed. Typical patterns:
// Seed a verified domain via admin JSON
await clone.adminPost(`/_clone/seed?runId=${clone.runId}`, {
domains: [{ name: "example.com" }],
});
// Or create via REST
await clone.request("/domains", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "example.com" }),
});
await clone.advanceClock("1m");
Dashboard preset Checkout Stripe + Receipt pairs stripe + email for payment confirmation mail.
Limits
| Limit | Detail |
|---|---|
| Attachments | In-memory per message |
| Multi-recipient | Supported; inbox keyed by to address |
| Delivery timing | Advance clock after send to reach delivered |
| Cross-clone | Checkout receipt (stripe + email), password reset (auth + email) — Scenarios |
Inbox & bounce
await clone.markBounce("bad@example.com");
await clone.markSpamTrap("trap@spam.org");
const inbox = await clone.inbox("user@example.com");
Environment variables
MOLAR_CLONE_EMAIL_URL, MOLAR_CLONE_EMAIL_ADMIN_URL, RESEND_API_KEY (derived test key)