@superjolt/sdk/email — send, list, read, reply, forward, listThread. You send from any sender the environment owns: its free sandbox sender (every project's first env gets one; add to any env on request) or an address on a verified domain you've connected. Every send dispatches for real. Read the inbox three ways: from code (SDK), from a browser (webmail), or from a desktop mail client (IMAP).
The dev brings their own Mailgun, SendGrid, or AWS SES account and their own domain. Superjolt layers a real inbox on top (IMAP read + SMTP submission, one password per address) and surfaces the whole thing through one @superjolt/sdk/email namespace. The deployed app’s only credential is SUPERJOLT_ENV_TOKEN — provider keys never reach the VM.
Sandbox address — works on day zero
Every project gets a free <slug>@sandbox.superjolt.email address auto-provisioned when the project is created. No DNS dance, no provider connection, no setup — open the dashboard or run create_project and the address is yours. The MCP create_project response includes the address inline.
import { email } from '@superjolt/sdk';
// Works in a fresh project — no `email_connect_*` or `email_add_domain` needed.
await email.send({
from: '[email protected]',
to: ['<your real address>'],
subject: 'Hello from the sandbox',
text: 'Try clicking the link in this email.',
});
Behaviour mirrors a regular inbox you’d connect yourself, with platform safety rails:
- Sends from the sandbox sender dispatch through the Superjolt sandbox sending pool (per the Sender resolution section below) — your mail actually goes out while you build, rate-limited to 20/hour, 100/day per project, with
[sandbox]prepended to the subject so recipients can never mistake a sandbox send for production mail. - Inbound mail to
<slug>@sandbox.superjolt.emaillands in the project’s inbox just like a regular tenant inbox — read and reply to it from code, in webmail, or any IMAP client, exactly like a domain inbox. Useful for testing OAuth confirmation flows, magic-link sign-in, signup-form replies, and anything else that needs a receiver before you own one. - Recipient suppression: a bounce or complaint from a sandbox send adds the recipient to a platform-side suppression list. Subsequent sandbox sends to that address fail synchronously with a 4xx — same behaviour from any project, so shared reputation stays clean.
Tenants who want to disable sandbox sending (e.g. to force their team onto production traffic) can flip the email.sandbox.sendEnabled entitlement off via admin. Inbound and reads stay open regardless. To go live with your own domain + reputation, follow the regular setup: email_connect_* → email_add_domain → publish DNS → email_create_inbox.
Send
import { email } from '@superjolt/sdk';
const m = await email.send({
from: '[email protected]',
to: ['[email protected]'],
subject: 'Welcome',
text: 'Thanks for signing up.',
// optional: html, cc, bcc, replyTo, inReplyTo, tags, idempotencyKey, attachments
});
The from-address’s domain must be a verified tenant domain (added once via email_add_domain — domains are tenant-level and shared across the tenant’s projects) bound to a provider credential. A from-domain the tenant hasn’t verified is rejected with 400 before any provider call.
Sender resolution
email.send takes an explicit from — the address you send as. It must be a sender the send’s environment owns:
- A sandbox sender (
prj-…@sandbox.superjolt.email). Every project’s first environment (main) gets one auto-provisioned, soemail.sendworks day zero with no provider connection — sends go out through the shared sandbox pool, subject-prefixed[sandbox]. Any other environment can add one on request (email_add_sandbox_sender). - An address on a connected, verified domain you’ve added. Until an environment has either a sandbox sender or a connected domain it has no sender, and
email.sendrefuses rather than silently dropping.
There is no “test captures, live sends” split and no magic default-sender or EMAIL_FROM config var: every send dispatches for real, and the from is always explicit. Whether a send is sandbox or your own domain follows the sender you choose, not the environment.
Every send still validates inputs (from-domain registered for the resolved sender, entitlement enabled, balance positive), writes the row, persists the raw RFC822 bytes so IMAP fetches resolve, and emits an email.delivered webhook once the provider confirms drop-off.
Messages carry the environmentId they were sent under. Read them back with the same SDK calls — the token resolves its environment server-side:
const sent = await email.list({ folder: 'sent' });
Or from an agent, list_email. The dashboard surfaces each environment’s mail under that environment.
Read
An inbox is one mailbox you can reach three ways — from code, from a browser, or from a desktop mail client. All three read the same messages; pick whichever fits the moment.
From code (the SDK)
const messages = await email.list({ folder: 'inbox', limit: 50 });
const thread = await email.listThread(messageId);
const message = await email.read(messageId);
Reads are scoped to the environment the calling token resolves to: a token can only see rows on its own environment, so an idempotency key can be reused across environments without colliding (the unique is on (environmentId, idempotencyKey)). Inbound delivery and SMTP submission land on the environment that owns the receiving address.
From a browser (webmail)
webmail.superjolt.com is a full browser mail client for any Superjolt inbox — it works identically for a sandbox inbox and one on your own domain. Two ways to sign in:
- Inbox credentials — the address plus its IMAP/SMTP password (the one
email_create_inboxoremail_get_inbox_credentialsreturns). - One click from the dashboard — a tenant owner or admin hits Open in webmail on the inbox in the dashboard’s Inboxes section; a single-use handoff token signs you in with no password re-entry.
It’s a three-pane client (folders · message list · reading pane) across the standard folders — Inbox, Sent, Drafts, Archive, Spam, Trash. You can star, mark read/unread, reply, forward, archive, and delete; compose with a rich-text editor and drag-drop or pasted attachments (each message is capped at the same size limit as an SDK send), and download an incoming message’s attachments individually or all at once as a zip. One session can hold several mailboxes at once, a storage indicator shows how much of your quota is used, and a draft an agent left for you carries a badge and asks you to confirm before it sends.
From a desktop client (IMAP/SMTP)
Prefer a native app? email_get_inbox_credentials returns the IMAP+SMTP host, port, username, and password to plug into Apple Mail, Thunderbird, iOS Mail, or any mail client — the same credential webmail uses.
Reacting to mail — events, not a webhook you host
| Event | When |
|---|---|
email.received | Inbound message landed in an inbox. |
email.delivered | Provider confirms drop-off of an outbound send (the sandbox pool confirms the same way). |
email.bounced | Provider returned a permanent failure for an outbound send. |
The zero-config way for an app inside a VM to react is the SDK events feed — a long-poll off the injected token, so there’s no public endpoint to expose and no signature plumbing:
import { events } from '@superjolt/sdk';
events.listen(async (e) => {
if (e.type === 'email.received') {
const msg = e.data; // { messageId, from, to, subject, ... }
// triage, auto-reply, file it, hand to the LLM, …
}
});
Every event carries the environmentId it belongs to, and the feed only surfaces events for the calling token’s own environment. The data payloads are compact: email.received gives { messageId, from, to, subject, … }, and email.delivered gives messageId, providerMessageId, from, to, subject, and tags.
Reference
email_setup_guide— MCP-driven setup walks the agent through connecting a provider, adding a domain, applying DNS, and creating the first inbox.email_add_domain/email_create_inbox— both MCP and dashboard.email_connect_mailgun/email_connect_sendgrid/email_connect_ses— bring-your-own provider credentials.