Postbox — Personal Email

Per-user mailboxes with a webmail interface and native IMAP/SMTP support. Run your own Gmail-equivalent personal mailbox on your Owlat instance.

Postbox gives every team member a personal mailbox on your Owlat domain — with a webmail interface and full IMAP/SMTP so they can use any native client (Apple Mail, Thunderbird, mobile mail apps).

It's separate from marketing campaigns and the shared team inbox. Postbox is for everyday person-to-person email; campaigns are for bulk sends; the team inbox is for shared triage.

Feature flag

Postbox is off by default. Enable it from Settings → Features or run owlat-setup feature postbox on. Activating the flag also activates the personal-mail Docker Compose profile, which starts the IMAP server (apps/imap).

There are two ways to run personal mail on Owlat. A hosted mailbox (the postbox flag) is an Owlat-hosted address on a domain you've verified — Owlat is the mail server. Alternatively you can connect an external mailbox (the separate mail.external flag) and keep using your existing Gmail, Fastmail, or company account, with Owlat syncing it over IMAP/SMTP. They're independent flags; you can run either, both, or neither.

What you get

  • Per-user mailboxes with folders (Inbox, Sent, Drafts, Trash, Spam, Archive) plus custom labels
  • A webmail UI under /dashboard/postbox
  • Native IMAP4rev1 server (port 993, TLS) and SMTP submission for outgoing mail
  • App passwords for connecting native clients without exposing your account password
  • Address aliases — receive mail at multiple addresses into one mailbox
  • Per-mailbox filter rules (Sieve-like): match on from/to/subject/body, then move, label, mark read, star, forward, trash, or discard
  • Forwarding rules
  • Per-identity signatures
  • Vacation auto-responder with date ranges
  • Snooze: hide a thread until a date you pick
  • A Reply Queue — a task list of emails waiting on your reply, with optional AI ask-summaries and urgency ranking
  • Scheduled send and an undo-send window
  • A personal address book that autocompletes recipients
  • Two composer modes (simple rich text, or the full block-based builder)
  • Search with Gmail-style operators
  • Threading across replies / forwards
  • Remote-image blocking and one-click quoted-text collapse when reading
  • Reading privacy aids: dark-mode adaptation of received HTML, tracker-pixel blocking, one-click unsubscribe, and link-destination transparency
  • Smart Inbox grouping (People / Newsletters / Notifications / Receipts) and a consolidated Postbox settings reference
  • Connect an external mailbox (separate mail.external flag): use your own Gmail / Fastmail / company account over IMAP+SMTP, no sending domain required

Setting up your mailbox

Mailboxes aren't created automatically on first visit — they're provisioned explicitly. There are two paths:

  • Admin pre-reservation at invite time — an admin can pick the address for a new member when they're invited, so the mailbox is waiting for them the moment they accept.
  • Self-service from Postbox settings — open Postbox → Settings → Add account, pick a localpart, and choose a verified domain from the dropdown (marcel + @yourdomain). The address is created on submit.
Creating a hosted mailbox is admin-gated

Provisioning a hosted mailbox requires admin permission (mailbox.create runs under admin context). You also need at least one verified domain — verify one under Settings → Domains first.

Once the mailbox exists:

  1. Open Postbox → Settings → Aliases and add any extra addresses you want to receive at. These must be on a domain that's already configured in Owlat with valid MX records pointing at your MTA.
  2. (Optional) Open Settings → Signatures and add a signature for each address you send from.

That's it for receiving mail. To send from a hosted mailbox you also need the sending domain configured under Settings → Domains (same DKIM/SPF setup as the marketing side).

Connect an external mailbox

If you don't want a hosted address — or you have no domain to verify — you can bring your own. The Connect external mailbox capability lets each user link an existing Gmail, Fastmail, iCloud, Outlook.com, or company account over IMAP + SMTP. Owlat keeps it in sync and lets you read, search, and send from it inside the same Postbox UI, without registering a sending domain.

This is a separate feature flag (mail.external) from hosted Postbox. Enabling it activates the external-mail Docker Compose profile, which starts the mail-sync worker (apps/mail-sync) that talks IMAP/SMTP to your provider.

Open the connect flow

Go to Postbox → Settings → External mailbox (or Add account → Connect external mailbox).

Pick a preset or enter your server details

Quick-setup buttons pre-fill IMAP/SMTP host and port for Gmail, Fastmail, iCloud, and Outlook.com. Otherwise enter your IMAP host/port and SMTP host/port manually, with the SSL toggle per server.

Enter your credentials

Provide the email address, username (defaults to the email address), and password. For Gmail / Outlook you'll need an app password with 2-factor enabled. Credentials are stored encrypted at rest — read queries never return them.

Test, then connect

Hit Test connection to verify IMAP and SMTP independently, then Connect mailbox. The mail-sync worker connects and begins syncing; the connection shows a live status (Connecting, Connected, Authentication failed, or Connection error).

External vs. hosted

An external mailbox is not authoritative on your local MTA — inbound mail is still delivered by your provider and synced in, not by Owlat's MX. Outbound mail goes through your provider's own SMTP. You can update credentials or disconnect at any time from the same page; disconnecting stops sync but retains already-synced messages.

Composing mail

From any folder click Compose. The composer supports rich text, attachments, reply/forward, drafts (auto-saved), and switching identities if you have multiple sending addresses on the mailbox. Recipient fields autocomplete from your personal address book, which builds itself automatically from the people you send to (separate from the org-shared CRM contacts).

There are two composer modes:

  • Simple — a fast in-house rich-text editor for everyday person-to-person mail.
  • Full — the block-based Owlat email builder, for when you want a designed, branded message.

Either way the body is rendered through the same email pipeline (boilerplate, CSS inlining, dark-mode handling) before it ships.

Text snippets (canned responses)

Snippets are reusable canned responses you insert while composing. Type / at the start of a line (or after a space) and a picker opens, filtered as you keep typing; Enter or Tab inserts the chosen snippet and Esc dismisses. Each snippet can have a short shortcut alias (shown as /hello) so you can jump straight to it.

Snippet bodies may carry a {{firstName}} placeholder, resolved at insert time from the first recipient's address-book name (an unknown name inserts a visible [firstName] marker rather than an empty greeting). Manage snippets under Postbox → Settings → Snippets.

Markdown typing shortcuts

The Simple composer converts Markdown as you type, Notion-style. At the start of a line, a trailing space turns - or * into a bullet list, 1. into an ordered list, # into an H1, ## into an H2, and > into a blockquote. Inline, **bold**, *italic*, and `code` convert on the closing character. The next Undo (Cmd/Ctrl+Z) after a conversion restores the literal marker text.

Emoji shortcodes & smileys

Type : followed by a couple of letters to open an emoji picker (:smi → 🙂); Enter / Tab inserts and Esc closes, leaving your typed text untouched. Common ASCII smileys also convert on the following space — :), :(, :D, ;), <3, and their :-) variants become the matching emoji.

Inline images

Paste or drag an image straight into the message body and it's embedded inline as a cid: content-ID attachment (not a remote URL, so it renders for the recipient without loading anything external). Images are downscaled client-side before upload; deleting the image from the body drops the attachment. This is separate from adding a file as a regular attachment.

Ghost-text autocomplete

With the ai flag on and Writing suggestions enabled (on by default), the composer offers an inline, greyed-out completion as you write — press Tab to accept it, Esc or any other keystroke to dismiss. It runs on a fast, cheap model, is debounced and rate-limited, and shows nothing when the model isn't confident. It is advisory: nothing is inserted until you press Tab. Toggle it under Postbox → Settings (see Postbox settings).

Tone rewrite

Select a few words in the Simple composer (with the ai flag on) and a floating pill offers to rewrite the selection: Shorter, Friendlier, More formal, Fix grammar, or Translate into another language. The rewrite is shown as an Original vs Rewritten preview with Apply / Discard — nothing changes until you apply it.

Writing voice (write in my voice)

Under Postbox → Settings → Writing voice (needs the ai flag) you can turn on Personalize AI drafts. Owlat learns a lightweight profile — greetings, sign-offs, formality, brevity, languages, emoji use — from a sample of your own sent mail, and folds it into AI reply suggestions and rewrites so they sound like you. It's per-mailbox and off by default; a Refresh now button re-learns it. It only shapes the advisory drafts you review — it never sends anything.

Scheduled send and undo-send

  • Undo send: after you hit Send, the message sits in a short pending window (30 seconds by default) before it actually dispatches. Click Undo within the window and the draft returns to the composer untouched.
  • Scheduled send: pick a future date and time and the draft is held until then, dispatched by a safety-net cron so it goes out even if nothing else is running.

Reading mail

Message bodies render in a sandboxed frame for safety. Two reading aids:

  • Remote images are blocked by default — external images don't load (and can't phone home) until you click Show images on the message. Inline and data-URI images always render.
  • Quoted text is collapsed — the original thread that a reply quotes is hidden behind a toggle, so you see the new message first and can expand the history when you want it.

Dark-mode rendering

When the app is in dark mode, Postbox adapts each received HTML message rather than forcing every mail onto a jarring white card:

  • Simple messages are re-themed — plain, mostly-text mail is rendered on a dark background with light text, and only low-contrast inline text colors are lightened. Colors that already read well, and any element that sets its own background, are left alone.
  • Designed messages stay light — mail that brings its own layout and background colors (newsletters, branded templates) keeps its exact colors and is shown as a light "paper" card floating on the dark background, so nothing is distorted.

A per-message toggle in the reader (a sun / moon button, shown only while the app itself is in dark mode) flips a single message the other way: Render this message on a light background / Render this message in dark mode. The choice is per-message and resets on reload. In light mode every message renders exactly as sent.

Attachment Quick Look

Click a previewable attachment to open it in an in-app lightbox instead of downloading it — images and PDFs are supported. When a message has several previewable attachments, the lightbox shows an "N of M" counter and you move between them with the ←/→ arrow keys or the on-screen chevrons. Each preview has Open in new tab, Download, and Close actions; anything that can't be embedded falls back to a download prompt.

Tracker & privacy badge

Postbox detects tracking pixels — remote images that are 1×1, hidden, or served from a known open-tracker host — in the sanitized message body. When a message contains them, the blocked-images banner names the count ("Images blocked — N tracking pixels detected.") and a shield badge in the header opens a popover explaining what was found.

Crucially, this is a two-step reveal: clicking Show images loads the real remote images but keeps the tracking pixels stripped, and a second banner appears ("N tracking pixels kept blocked."). Only clicking Load everything also loads the trackers. So you can see a message's real images without confirming to the sender that you opened it.

The same protection covers the AI agent: when the inbound pipeline assembles a message body as context for the model, remote images and tracking pixels are stripped first (inline data:/cid: images are kept), so the agent reading every inbound automatically never resolves a remote pixel and never leaks an open back to the sender.

One-click unsubscribe

When a message advertises a List-Unsubscribe header, Postbox shows an Unsubscribe chip that uses the safest method available:

  • RFC 8058 one-click (List-Unsubscribe-Post) — after a confirmation prompt, Owlat sends the standard one-click POST from the server (not your browser), over HTTPS only, with an SSRF guard and without following redirects. On success the chip flips to Unsubscribed.
  • Mailto — opens a pre-filled unsubscribe message for you to send.
  • Plain link — opens the sender's unsubscribe page in a new tab.

If the one-click request fails it falls back to opening the unsubscribe page. Unsubscribing never modifies the message.

To make phishing harder, every link in a received message gets its real destination host as a native hover tooltip (any sender-supplied tooltip is discarded — it could lie). When a link's visible text looks like one domain but points at another, Postbox appends a muted → real-host.com marker after it. Known tracking parameters (utm_*, fbclid, gclid, …) are stripped from links while the rest of the URL is preserved.

Today view (the inbox landing surface)

The inbox opens on Today — a focused single column showing the day's work and nothing else: a For you list of emails waiting on your reply (the same items as the Reply Queue), today's mail (everything received since your local midnight, plus anything still unread from yesterday), and a centered Show past mails (n) affordance that expands older mail inline. Mail the Smart Inbox classifies as newsletters, notifications, or receipts doesn't clutter Today — one quiet roll-up line ("12 newsletters & receipts auto-filed · view") links to the Categories view instead.

Opening a message in Today keeps you in the focused column: the conversation appears in a centered reader over the list (the full reader — reply, forward, snooze, AI assist all work there). Esc or a click outside returns to the list exactly where you left it, j/k move to the next or previous conversation without closing, and archiving/trashing/snoozing advances to the next one automatically (per your auto-advance preference). Links to a specific message open the same way while you're in Today mode.

Browse is the full three-pane UI (folder rail, list, reader) for folder work and bulk triage. Switch with the Browse button in the Today header, press b (or Cmd/Ctrl+B) to toggle, and Esc in Browse returns to Today. Pressing / in Today jumps to Browse with search focused. Your last-used mode is saved to your account. Every other folder and search use the regular three-pane UI — every row, shortcut, and action in Today is the same as in Browse.

Inbox view modes

The inbox header has a labeled Flat | Conversations | Categories control — exactly one mode is active at a time:

  • Flat (default) — one row per message, with the hover and j/k keyboard triage.
  • Conversations — one row per thread, with a message count and unread badge.
  • Categories — the Smart Inbox grouping into People, Newsletters, Notifications, and Receipts sections.

Your choice is saved to your account, so it follows you across devices and reloads. The modes are inbox-only — every other folder stays a flat list.

Keyboard shortcuts

Press ? anywhere in Postbox for the full cheat sheet. The single-key vocabulary works in the message list (on the focused row) and while reading (on the open message), and is inert whenever you're typing in a field:

  • Navigate: j/k (or arrows) move, Enter opens, / focuses search, b toggles the inbox between Today and Browse (Esc in Browse returns to Today).
  • Triage: e archive, # or Delete trash, s star, u toggle read, Shift+U mark unread, x select (drives the bulk-actions bar).
  • Organize: h snooze, l add label, v move to folder.
  • Compose: r reply, a reply all, f forward — from the list they open the message with the composer ready; in the composer, Cmd/Ctrl+Enter sends and Cmd/Ctrl+Shift+Enter opens schedule-send.

AI: summarize & suggest replies

When the ai feature flag is on (it needs an LLM provider configured — see Feature flags), each open thread shows a Summarize button (2–4 bullet points) and Suggest replies (a few ready-to-send options; pick one to open a pre-filled reply). The output is advisory — it is never sent automatically, and the thread is treated as untrusted input.

All of the AI features below are gated by the same ai flag, are rate-limited per user, and are advisory — Owlat never sends or edits mail on its own.

AI thread summary

On a long thread (roughly five or more messages, or a lot of text), Postbox shows a cached one-line Summary: strip at the top; click it to expand 2–4 key-point bullets. It's gated by the ai flag and the Auto-summarize long threads setting (on by default), and the summary is cached until a new message arrives, so it isn't regenerated on every open. Fail-soft — if the model is unavailable the strip simply doesn't appear.

Ask about this thread

The reader footer has an Ask about this thread… box (with the ai flag on). Ask a question and Owlat answers grounded only in that conversation's messages — it's told to say so plainly when the thread doesn't contain the answer. Answers are advisory and aren't saved.

Meeting-intent chip

When someone asks to schedule a meeting in plain prose (with no .ics invite attached), Postbox surfaces a quiet "Scheduling request — draft a reply?" chip (with the ai flag on). Click it and the AI suggests a few ready-to-send replies — accepting a proposed time or offering an alternative — which you then review and send. It never confirms a time on its own. Actual calendar invites still render as the invite card instead.

Calendar invites

A message carrying a calendar invite (a text/calendar part, including the inline form Google Calendar and Outlook send) renders an invite card with the summary, time, location, and organizer. Accept / Maybe / Decline sends an RSVP back to the organizer with a standard METHOD:REPLY .ics attached.

Block a sender

Inside an open thread, Block sender is a one-click action: it creates a high-priority filter that routes all future mail from that address to Spam (or moves it to Trash if the mailbox has no Spam folder) and moves the current message to Spam.

Encrypted & signed mail (PGP / S-MIME)

Postbox detects PGP and S/MIME messages and labels them honestly — Encrypted (PGP) or Signed (PGP) · not verified. Owlat does not yet verify signatures or decrypt bodies, so it never shows a misleading "verified" check; encrypted bodies are flagged rather than rendered as gibberish, and inline clear-signed messages show their readable text.

Working through the inbox

Auto-advance after triage

When you archive, trash, snooze, or spam the conversation you're reading, Postbox moves you straight to the next one instead of dumping you back to the list. Choose the behavior under Postbox → Settings → After archiving, deleting or snoozing: Open the next conversation (default), Open the previous conversation, or Go back to the list. It applies in folder views (the search preview stays put), and falls back to the list when there's no adjacent conversation.

Density & the folder rail

Two layout controls keep the inbox as tight or as roomy as you like:

  • DensityComfortable (the roomy default) or Compact (tighter rows with a single-line subject/snippet, Apple-Mail style). Set it under Postbox → Settings → Density.
  • Collapsible folder rail — collapse the left folder rail to a slim icon strip with the button at its foot or Cmd/Ctrl+Shift+D; the collapsed state is remembered per device.

Fast large folders

Folders with more than ~100 conversations render with a windowed list — only the rows on screen are mounted — so scrolling a big inbox stays smooth. Below that size every row renders normally.

Undoable triage

Archiving, trashing, moving, or marking spam (single or in bulk) pops a brief toast — "Archived", "Moved to Trash", "Moved", "Marked as spam" — with an Undo button for about eight seconds. Click Undo, or press Cmd/Ctrl+Z while you're not typing, to put the message(s) back where they were.

To group the inbox into People / Newsletters / Notifications / Receipts, see Smart Inbox.

Contacts

Postbox → Contacts is your personal address book. Addresses you send to are remembered automatically and power recipient autocomplete; the Contacts page lets you search, add, edit, and remove entries, or start a new message to one.

Filters

Settings → Filters lets you create rules that apply to incoming mail. Each rule has a list of match conditions (from / to / cc / subject / body, a custom header by name, message size with greater-than / less-than, and has attachment) and a list of actions: move to folder, add label, mark read, star, forward to…, move to trash, and discard (silent drop). Rules run in order; you can reorder them.

Discard vs. move to trash

Move to trash files the message in your Trash folder. Discard drops it silently — the message is not stored at all. Use discard for noise you never want to see.

Aliases & forwarding

  • Aliases: addresses that route into your mailbox. support@yourdomain and hello@yourdomain can both land in the same inbox without an actual second account.
  • Forwarding: addresses where copies should go out to. Useful for forwarding to an external address while keeping a copy locally.

Both are configured under Postbox → Settings.

Vacation responder

Under Settings → Vacation set a subject, message body, and date range. Owlat replies once per sender for the duration of the window.

Native clients (IMAP / SMTP)

Postbox runs a real IMAP4rev1 server (in apps/imap) and accepts SMTP submission through the MTA. To connect a native client:

  1. Go to Postbox → Settings → App passwords and generate one. The password is shown once; copy it now.
  2. In your mail client, add an account with:
    • IMAP: server mail.<your-domain>, port 993, encryption SSL/TLS, username <your-mailbox-address>, password <app-password>
    • SMTP: server mail.<your-domain>, port 587 (STARTTLS), same username and app password

You can issue multiple app passwords — one per device — and revoke any of them individually.

App-password scope

An app password authenticates a single mailbox. It does not grant access to the Owlat dashboard, API, or other mailboxes. Revoking it kicks the corresponding IMAP/SMTP session within seconds.

Postbox search supports a subset of Gmail's operators. Combine them in a single query.

OperatorExampleMatches
from:from:alice@acme.comMessages from this address
to:to:billing@yourdomainMessages sent to this address
subject:subject:invoiceSubject contains this text
has:attachmenthas:attachmentHas at least one attachment
has:no-attachmenthas:no-attachmentNo attachments (single hyphenated token)
is:unreadis:unreadNot yet marked as read
is:readis:readMarked as read
is:starredis:starredFlagged / starred
in:in:trash, in:sent, in:spamIn a specific system folder
label:label:newslettersHas a specific custom label
before:before:2026-04-01Received before a date (YYYY-MM-DD)
after:after:2026-01-01Received after a date
older_than:older_than:7dOlder than N days (also h hours, m minutes)
newer_than:newer_than:30dNewer than N days (also h, m)

Plain words match against subject, body, sender, and recipient. Quote phrases for an exact match: "quarterly review".

Snooze

Open any thread and click the snooze icon. Pick a date and it disappears from the inbox until then — at which point it reappears at the top, untouched.

How Postbox relates to other Owlat features

PostboxTeam InboxMarketing
Flagpostboxinboxcampaigns / automations / transactional
Per-user mailboxYesNo (shared)n/a
IMAP/SMTPYesNoNo
OutboundOne-to-one mailRepliesBulk + triggered
Receives MX mailYes (own aliases)Yes (shared addresses)No
ThreadingYesYesn/a

You can run any combination — Postbox alone, marketing alone, both together. There's no requirement to enable one before the other.

A connected external mailbox (mail.external) is a third option that fits alongside the hosted Postbox column: it surfaces in the same webmail UI, but inbound mail is delivered and outbound mail is sent by your own provider, so it needs neither MX records nor a verified sending domain on Owlat.

Offline & local cache

Postbox can keep a small, read-only copy of recent mail on the current device so the inbox opens instantly on a cold start and stays readable without a connection.

  • What's stored: the newest ~200 inbox rows and the sanitized bodies of the ~50 most recently read messages, each scoped to the mailbox that cached them. Only the already-sanitized version of each message is cached — never the raw mail.
  • Where: this device's local browser database (IndexedDB). It's device-local and never synced between devices or to the server.
  • Cold start: the inbox and any cached message render immediately from the cache with a subtle "updating…" hint, then reconcile in place the moment the live data arrives (live data always wins).
  • Offline: a quiet banner appears, the cached list and cached bodies stay readable, and actions that need the server are paused with clear affordances.
  • Control it: toggle Store recent mail on this device and use Clear local cache under Postbox → Settings → On this device. It's on by default in the desktop app and off by default in the browser. Leave it off on shared computers — the cache is unencrypted local storage. If the device runs out of storage, caching disables itself silently and says so in settings.

Not yet cached in this version: the folder rail (it still renders from the live query on a cold start), offline sending/queued actions, full-mailbox sync, and encryption at rest. Those are planned follow-ups.

Limits & privacy

  • Mailboxes live in the same Convex deployment as the rest of your data. They aren't shared with other tenants.
  • The optional on-device read cache (above) stores sanitized recent mail unencrypted in the device's local database; it's off by default in the browser and can be cleared at any time.
  • The MTA caps per-message submission size and connection-pool usage, and rate-limits the IMAP authentication endpoint (failed logins are tarpitted) (see Self-hosting → Production).
  • Inbound spam filtering uses the same content scanner that protects campaigns (scan.content flag).
  • File attachments are scanned by ClamAV when scan.files is on.

Troubleshooting

I don't see "Postbox" in the sidebar. The flag isn't on yet. Toggle it from Settings → Features or owlat-setup feature postbox on, then restart the stack.

IMAP says "auth failed". Confirm you're using an app password, not your dashboard password. App passwords are bound to a single mailbox address — use that address as the username.

Outbound mail bounces. The sending domain isn't verified yet. Open Settings → Domains, add the domain, and apply the DNS records shown.

A native client can't reach the IMAP server. Check that port 993 is published on the host and that IMAP_GREETING_HOST resolves to the same hostname your TLS cert is issued for.

My external mailbox shows "Authentication failed". Re-enter the password on Settings → External mailbox → Update credentials. Gmail and Outlook need an app password with 2-factor enabled — your normal account password won't work. The mail.external flag (and its external-mail Docker profile / mail-sync worker) must also be running.

For deeper architecture details (schema, IMAP command flow, MTA integration) see Postbox architecture.