SupaNet
Building on SupaNet

Email

Send and receive mail, with credentials kept in Supabase Vault.

SupaNet can send and receive email so that agents and users can act over mail - the "morning agent emails me a summary" pattern, or an inbox an agent can read. Two seeded built-in tools, send_email and check_email, become available once an admin configures a provider in Settings → Email.

Sending

Sending goes through an HTTP provider (such as Postmark or Resend), not raw SMTP. Because sending is exfiltration-capable, send_email stays an ordinary tool row, so all the usual controls apply: admin activation, per-agent tool_ids scoping, and the webhook allow_tools gate. On top of that it adds:

  • an optional recipient allowlist (an exact address or an @domain suffix),
  • a rate limit (20 sends per hour), and
  • an email.sent activity-log entry for every send.

Recipient allowlist

The allowlist restricts which email addresses agents and webhooks can send to. When empty, send_email can reach any address — a data exfiltration risk when combined with untrusted input (like a user-submitted email address in a form). The Settings → Email page shows a security warning if an email provider is configured but the allowlist is blank, reminding admins to set allowed recipients.

An @domain rule in the allowlist matches addresses ending with that exact string. For example, @example.com matches user@example.com but not user@sub.example.com (which ends with .example.com, not @example.com). To allow subdomains, add each subdomain as its own rule — the safe default for a send-to allowlist.

Receiving

SupaNet supports two email ingestion paths:

Inbound-parse (provider push)

The provider POSTs each incoming message to the public email-inbound edge function (token-gated like a webhook), which normalizes it into the inbox_messages table. check_email then reads that table — so it is push, not polling.

IMAP polling (user mailboxes)

Members can register IMAP mailboxes (via Inbox → Inboxes → Add inbox) and the workspace automatically polls them. The email-poll edge function is cron-ticked every minute but respects each mailbox's poll_interval_minutes setting (1–1440 minutes; default 5). It:

  • Opens an IMAP TLS connection to the mailbox's host.
  • Searches for messages with a UID greater than the last one ingested.
  • On a fresh inbox, backfills only the ~10 most recent messages.
  • Parses each message's RFC822 (headers, MIME body, base64/quoted-printable transfer encodings).
  • Dedupes on Message-ID.
  • Records any connection or parsing errors on the account's last_error.

The mailbox password lives only in Supabase Vault (same pattern as the email provider key). New mail lands in inbox_messages (source='email') and fires message.received events, so listeners can route them like any other message source.

Testing without waiting

The inbox editor has two testing buttons:

  • Test connection — the imap-test edge function (verify_jwt=true, signed-in users only) logs in and reports the folder's message count, so you can validate an app password before saving.
  • Test message — inserts a synthetic source='email' row and fires message.received, letting you wire and verify a listener end-to-end without waiting for real mail.

A fresh inbox is also polled on the next 1-minute tick instead of waiting for its full interval, since it has no last_checked_at yet.

The pure RFC822 parser lives in supabase/functions/_shared/imap.ts and is unit-tested in tests/imap_test.ts.

Where credentials live

Email credentials live only in Supabase Vault. The non-secret config sits in a public.integrations row with a pointer to the secret; the actual key is never a table column, never in a client payload, and never logged.

The flow is tight:

  • The client writes the key solely through an admin-gated, security-definer RPC (set_email_integration), which checks admin status in its body.
  • Edge functions read the decrypted key only through a service-role-only RPC (read_email_secret).

Runs everywhere

send_email and check_email execute through the shared built-ins module, so they work identically in all three loops - chat, webhook, and scheduler. That is what makes a scheduled agent able to email you without any special wiring.

On this page