Skip to main content
AI & Automation

Email System

Full email platform with CF Email Routing inbound, Resend outbound, R2 attachments, FTS5 search, contacts, labels, filters, autoresponder, scheduled send, templates, and keyboard shortcuts

February 26, 2026 Updated March 1, 2026

Email System (v5.0.0)

The email system at /admin/email is a full email platform for @Arcturus-Prime.com addresses. Inbound via Cloudflare Email Routing, outbound via Resend, storage in D1 + R2, with FTS5 search, contacts, labels, filters, autoresponder, scheduled send, templates, aliases, and keyboard shortcuts.

Architecture

Inbound:      Sender → MX (CF Email Routing) → Arcturus-Prime-email-receiver Worker
              → blocked sender check → D1 storage → R2 attachments
              → contacts auto-populate → filter rules → autoresponder
              → Gmail forward

Outbound:     Admin/User UI → /api/{admin,user}/email → Resend API → D1
              → contacts auto-populate → delivery status via webhooks

Scheduled:    Admin UI → D1 email_scheduled → CF Cron (every 1 min)
              → Worker scheduled handler → Resend API → D1 (sent folder)

Attachments:  Inbound: Worker → R2 (metadata in D1)
              Outbound: UI upload → API → R2 + Resend
              Download: API → R2 (fallback D1 BLOB for legacy)

Search:       FTS5 virtual table (auto-synced via triggers)

Mailboxes

Managed in email_mailboxes D1 table. Default set (seed via gear icon):

AddressDisplay Name
[email protected]Arcturus-Prime Admin (default)
[email protected]Arcturus-Prime
[email protected]Arcturus-Prime Support
[email protected]daniel

Each mailbox has: address, display name, signature, is_default, is_active. When sending, the from field uses ${display_name} <${address}> format.

Aliases

Aliases route additional addresses to a target mailbox. The worker resolves aliases on inbound: if no direct mailbox match, checks email_aliases for a matching alias_address and routes to target_mailbox_id.

Storage

D1 (Structured Data)

  • emails — all inbound/outbound messages with metadata
  • email_mailboxes — mailbox definitions with signatures
  • email_attachments — attachment metadata + r2_key pointer (legacy rows may have D1 BLOB content)
  • email_contacts — auto-populated address book
  • email_labels + email_label_map — custom labels
  • email_filter_rules — automated sorting rules
  • email_autoresponders — per-mailbox out-of-office
  • email_scheduled — time-delayed outbound queue
  • email_blocked_senders — spam/block patterns
  • email_templates — reusable compose templates
  • email_aliases — address routing

R2 (Binary Data)

  • Bucket: Arcturus-Prime-email-attachments
  • Key format: attachments/{email_id}/{attachment_id}/{filename}
  • Limit: 25 MB per attachment (R2 supports up to 5 GB, capped for email sanity)
  • Free tier: 10 GB storage, 1M Class A ops, 10M Class B ops, zero egress
  • Virtual table emails_fts indexed on: subject, body_text, from_address, to_address
  • Auto-synced via INSERT/UPDATE/DELETE triggers on emails table
  • Search API uses FTS5 MATCH with relevance ranking, fallback to LIKE if FTS table missing

Features

Contacts (Auto-Populated)

  • Worker upserts sender into contacts on every inbound email
  • API upserts recipients on every outbound send
  • Tracks: email, name, last_contacted, contact_count, is_favorite
  • Autocomplete in compose To/CC/BCC fields (debounced search)
  • Favorite contacts shown first

Signatures

  • Per-mailbox signature stored in email_mailboxes.signature
  • Auto-appended to compose body below -- separator
  • Editable in compose modal

Labels

  • Custom labels with name and color
  • Assign/remove labels from emails
  • Sidebar shows label list with counts, click to filter
  • Label pills displayed on email list items

Filter Rules

  • JSON conditions: [{field, operator, value}] — field: from/to/subject/body, operator: contains/equals/matches (regex)
  • JSON actions: [{type, params}] — type: move/label/star/mark_read/delete
  • Worker evaluates rules after storing inbound email, first match wins
  • Priority ordering, toggle active/inactive

Autoresponder

  • Per-mailbox out-of-office with subject and body
  • Optional date range (start_date/end_date)
  • Tracks responded_to JSON set to avoid re-sending to same address
  • Sends via Resend from the mailbox’s address

Scheduled Send

  • “Send Later” with datetime picker in compose modal
  • Stored in email_scheduled table with status: pending/sent/cancelled/failed
  • CF Cron Trigger (every 1 minute) on worker processes due emails
  • Scheduled folder in sidebar with pending count

Blocked Senders / Spam

  • Block by exact address ([email protected]) or wildcard domain (*@domain.com)
  • “Mark as Spam” = move to trash + auto-block sender
  • Worker checks inbound against blocked list before processing — rejects with message.setReject('Sender blocked')

Delivery Status

  • Resend webhook endpoint receives: delivered/bounced/complained/delayed events
  • Updates delivery_status column on emails
  • Status indicators shown in sent folder (colored dots)

Templates

  • Reusable email templates with name, subject, body
  • “Insert Template” button in compose opens template picker
  • Template body replaces compose body, subject auto-fills

Custom Folders

  • User-created folders in email_custom_folders table
  • Name, color (8 presets), optional icon
  • Uses existing folder column with folder UUID
  • Sidebar counts, rename/delete via context menu

Email Forwarding

  • Forward button in reading pane (keyboard: f)
  • Builds forwarded subject (Fwd: ...) and body with original headers
  • Optionally includes user message above forwarded content
  • Admin + user pages

Rich Text Compose

  • Contenteditable editor with formatting toolbar (replaced textarea on admin)
  • Commands: Bold, Italic, Underline, Strikethrough, H/H2 headings
  • Lists (bullet/numbered), Blockquote, Horizontal Rule
  • Link/Unlink, Clear Formatting, Font Size (4 levels), Text Color (7 colors)
  • Sends both html (innerHTML) and body (innerText) to API
  • Reply pre-fills with quoted HTML from original

Dark Reader (HTML Email Body)

  • Dark CSS injected directly into iframe srcdoc (not filter:invert)
  • Dark bg (#0a0e1a), cyan links (#22d3ee), light text, cyan-tinted borders
  • Toggle button swaps iframe internal stylesheet live
  • Preference saved to localStorage, defaults ON
  • Wrapper gets gradient top line (cyan→purple), outer cyan glow

CID Inline Image Resolution

  • Scans iframe for img[src^="cid:"] after load
  • Fetches attachment list, matches CID to content_id
  • Downloads attachment as blob, replaces img.src with blob URL

Draft Auto-Save

  • Compose content auto-saves to localStorage every 3 seconds
  • Closing modal saves immediately
  • Reopening “New Email” offers to restore (within 24 hours)
  • Successful send clears draft
  • “Draft saved” indicator in compose footer

Keyboard Shortcuts

KeyAction
j / kNext / previous email
EnterOpen selected email
EscapeClose reading pane / modal
cCompose new email
rReply to current email
fForward current email
eArchive
#Trash
sToggle star
uToggle read/unread
/Focus search
?Show shortcut help overlay
g iGo to inbox
g sGo to sent
g aGo to archive

Inline Image Preview

  • Attachment images render in a gallery div below email body
  • Fetches via API, creates blob URLs

User Email Portal

/user/email — authenticated non-admin users with owner_email on mailbox. API: src/pages/api/user/email.ts (~30 actions, ownership-verified). Page: src/pages/user/email.astro

Features ported from admin: labels, templates, contacts, blocked senders, custom folders, thread view, rich text compose, dark reader, CID images, forwarding, draft auto-save, empty trash. NOT ported (admin-only): AI compose, scheduled send, filter rules, autoresponder, aliases, permanent delete.

Three-Phase Deletion

  1. Trash (folder = 'trash', deleted_at set) — recoverable via “Move to Inbox”
  2. Purge (purged_at set) — visible only in “Purged” folder, recoverable via “Restore”
  3. Permanent Delete (hard DELETE) — only from purge area, requires confirmation

API Reference

GET /api/admin/email

Returns service status and folder stats.

POST /api/admin/email

ActionDescription
listList emails by folder (+ optional mailbox_id)
readGet single email, marks as read
threadGet all emails in a thread
sendSend via Resend + save to D1 sent folder
moveMove to folder (inbox/sent/archive/trash)
starToggle starred
searchFTS5 search on subject/from/to/body
statsFolder counts + unread + purged
mark-readMark email as read
mark-unreadMark email as unread
attachmentsList attachment metadata for an email
download-attachmentReturn attachment content (R2 first, D1 fallback)
delete-attachmentDelete from R2 + D1
pollLightweight polling for new emails since timestamp
deletePurge from trash
purge-all-trashPurge all trashed emails
purgedList purged emails
permanent-deleteHard delete from purge
restoreRestore from purge to inbox
mailboxesList mailboxes
create-mailboxCreate new mailbox
update-mailboxUpdate mailbox settings
seed-mailboxesCreate default mailboxes
update-signatureSet mailbox signature
composeAI draft (SSE stream via brain-chat)
summarizeAI thread summary (SSE stream)
contactsList/search contacts
contact-deleteRemove contact
contact-favoriteToggle favorite
labelsCRUD labels
label-emailAdd/remove label from email
list-by-labelEmails filtered by label
filter-rulesCRUD filter rules
autoresponderGet/set autoresponder per mailbox
schedule-sendCreate scheduled email
list-scheduledList pending scheduled emails
cancel-scheduledCancel scheduled email
blocked-sendersList blocked senders
block-senderBlock email/domain pattern
unblock-senderRemove block
mark-spamTrash + auto-block sender
webhook-resendDelivery status webhook
templatesList templates
create-templateCreate template
update-templateUpdate template
delete-templateDelete template
aliasesList aliases
create-aliasCreate alias
delete-aliasDelete alias

CF Email Worker

Lives at workers/email-receiver/, deploys separately from main site.

Inbound flow:

  1. Check sender against email_blocked_senders — reject if blocked
  2. Parse MIME with postal-mime
  3. Extract headers (From, To, Subject, Message-ID, In-Reply-To, CC, Date)
  4. Resolve threading via In-Reply-To lookup
  5. Match to address to mailbox (direct match → alias fallback)
  6. Store email in D1
  7. Store attachments in R2 (metadata in D1)
  8. Auto-populate sender into contacts
  9. Evaluate filter rules (first match wins)
  10. Check autoresponder (send via Resend if active)
  11. Forward to Gmail

Cron handler (every 1 min):

  1. Query email_scheduled where status = 'pending' and scheduled_at <= now()
  2. Resolve From address from mailbox
  3. Send via Resend API
  4. Save to emails table as outbound
  5. Update scheduled status to sent/failed

Files

FilePurpose
src/pages/api/admin/email.tsAPI route (~45 actions)
src/pages/admin/email.astroAdmin inbox UI
src/lib/admin-db.tsD1 email methods (~55 methods)
src/lib/email-client.tsBrowser-side typed API client (~45 functions)
workers/email-receiver/src/index.tsCF Email Worker (inbound + cron)
workers/email-receiver/wrangler.tomlWorker config (D1 + R2 + cron)
src/pages/user/email.astroUser email portal UI
src/pages/api/user/email.tsUser email API (~30 actions)
src/config/modules/email.tsModule manifest
migrations/0002-0019D1 migrations (18 files)

Environment Variables

VariableWherePurpose
RESEND_API_KEYCF Pages env + Worker envResend API for sending
RESEND_FROM_EMAILCF Pages envDefault from address fallback
ADMIN_DBwrangler.toml D1 bindingD1 database (Pages + Worker)
EMAIL_ATTACHMENTSwrangler.toml R2 bindingR2 bucket (Pages + Worker)
FORWARD_TOWorker env (optional)Gmail forwarding address

Migrations

MigrationPurpose
0002emails table with indexes
0004email_mailboxes table, adds mailbox_id
0005deleted_at and purged_at columns
0006email_attachments table (D1 BLOB)
0007email_attachments content_id column
0008email_attachments r2_key column
0009email_contacts table
0010email_mailboxes signature column
0011emails_fts FTS5 virtual table + triggers
0012email_labels + email_label_map tables
0013email_filter_rules table
0014email_autoresponders table
0015email_scheduled + email_blocked_senders + delivery_status
0016email_templates + email_aliases tables
0017email_attachments nullable content column
0018owner_email column on email_mailboxes
0019email_custom_folders table
emailresendcloudflared1r2mailboxcontactslabelsfilterstemplates