June features an integrated ephemeral email platform powered by Stalwart Mail Server using modern JMAP (JSON Meta Application Protocol, RFC 8620 & RFC 8621). This architecture replaces antiquated IMAP/POP3 polling with efficient, stateless JSON-over-HTTP requests and real-time event streaming.
sequenceDiagram
autonumber
actor Sender as External Mail Sender
participant MX as MX (temp.4ofl.dev)
participant Stalwart as Stalwart JMAP Server
participant June as June Application Core
participant DB as SQLite DB
participant SSE as Server-Sent Events Hub
actor User as Discord / Web User
User->>June: Create Inbox (/tempmail or Dashboard)
June->>DB: Store Address Lease & Expiry (Fixed TTL: 60m)
June-->>User: Return Allocated Address (e.g. 7f6899f...@temp.4ofl.dev)
Sender->>MX: Deliver Email via SMTP
MX->>Stalwart: Ingest & Parse MIME Message
par Real-Time Push Notification
Stalwart->>June: Webhook / JMAP Push Notification
June->>SSE: Broadcast New Message Event
SSE-->>User: Live Notification on Dashboard
and On-Demand Query
User->>June: Fetch Inbox / Read Message
June->>Stalwart: JMAP Email/get Request (using resolved accountId)
Stalwart-->>June: Return Parsed JSON (Headers, Text, HTML)
June-->>User: Render Sanitized Email Viewer
end
Legacy mail protocols like IMAP require persistent stateful socket connections, expensive bandwidth-heavy synchronization, and complex MIME parsing on the client.
JMAP provides distinct architectural advantages:
/jmap/session), eliminating socket state issues across container restarts.MAX_JMAP_RESPONSE_BYTES = 2MB), preventing memory exhaustion.When an inbox is created via /tempmail or the web dashboard:
<hex>@temp.4ofl.dev.TEMPMAIL_TTL_SECONDS=3600) is recorded in SQLite alongside the owning Discord user ID.Email/query and Email/get)To fetch messages for an address, June’s JMAP client (src/utils/tempmail.js) first queries the JMAP session endpoint (/jmap/session) to extract the resolved account ID from session.primaryAccounts['urn:ietf:params:jmap:mail'] (e.g. "june"), and then constructs an authenticated JMAP invocation:
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
[
"Email/query",
{
"accountId": "<resolved_account_id>",
"filter": { "to": "7f6899f3a1@temp.4ofl.dev" },
"sort": [{ "property": "receivedAt", "isAscending": false }],
"limit": 10
},
"call-0"
],
[
"Email/get",
{
"accountId": "<resolved_account_id>",
"#ids": {
"resultOf": "call-0",
"name": "Email/query",
"path": "/ids"
},
"properties": ["id", "blobId", "threadId", "from", "to", "subject", "receivedAt", "preview", "hasAttachment"]
},
"call-1"
]
]
}
The server evaluates the query and returns the matching email headers in a single atomic response.
It is important to understand the boundary between June’s SQLite lease and Stalwart’s message store:
When rendering email bodies on Discord or the web dashboard:
onload, onerror), and external CSS tracking pixels are scrubbed before display.