Build Mimestream

KINDAreplaces $4.99/mosaves $59.88/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A native client over an existing mailbox that syncs incrementally rather than refetching, stores messages locally so search and scrolling are instant, models labels honestly including a message in several at once, and sends through the account's normal service so deliverability stays someone else's problem.

estimated effort multi-daythe files for this build are in the project pack

LanguageSwift 6 with AppKitSyncThe provider's history API for incremental changesStorageSQLite with full text searchAuthThe account's own authorisation flow, tokens in the keychainSendingThe account's normal sending service

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree if you own one

    Why The reason to build this is native performance, which exists nowhere else.

    Get it Apple menu, then About This Mac.

    Verify About This Mac reports macOS 13 or higher

  2. installfree

    Why Builds the client and provides Instruments for the list and search work.

    Get it Mac App Store or developer.apple.com/xcode. open ↗

    Verify xcodebuild -version prints 16 or higher

  3. API keyfree for personal use; verification may be required for wider distribution

    Why Without them the client cannot connect to a real mailbox, and a test mailbox does not have the label complexity that matters.

    Get it Create a project in the provider's developer console, enable the mail scopes you need, and configure the redirect. Some providers review applications requesting broad mail access, which takes time.

    Verify The flow completes and returns a token

  4. have readyfree

    Why Sync, search and list performance are all trivial on a small mailbox and decisive on a real one, and label complexity only appears in mail people actually use.

    Get it Your own account. If it is small, generate a large fixture as well, but test against the real one for label behaviour.

    Verify The account connects and reports its message count

  5. 99 US dollars per year

    Why Product phase only. Without it there is no notarization and a warning on other machines.

    Get it Enrol at developer.apple.com/programs. open ↗

    Verify A Developer ID Application certificate appears in Xcode settings

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- accounts(id, address, token_ref, history_id, last_synced_at)
- messages(id, account_id, provider_id, thread_id, from_addr, to_json, subject, snippet, received_at, size, raw_key)
- labels(id, account_id, provider_id, name, kind), message_labels(message_id, label_id) · many to many, which is the point
- threads(id, account_id, provider_id, subject, last_message_at, message_count)
- messages_fts · full text over subject and body
- pending_actions(id, kind, message_id, label_id, created_at, attempts, last_error) · the outbox that makes offline actions safe
- A local action is applied immediately and queued for the server. The queue is what makes the client feel instant without lying.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired~/Library/Application Support/Mail/mail.dbLocal message cache. Contains your mail, so treat the file and its backups accordingly.
OAUTH_CLIENT_IDrequiredreplace-with-client-idFrom the provider's developer console.
INITIAL_SYNC_DAYSrequired90How much mail to fetch before the client becomes usable. Older mail backfills after.
MAX_RAW_CACHE_GBrequired10Cap on stored raw messages. Oldest are evicted and refetched on demand.
ACTION_MAX_ATTEMPTSrequired6Retries before a queued action is surfaced as failed.

The build, in order

  1. Authorising without touching a password

    The account is connected through its own authorisation flow and the token is in the keychain.

    1. Narrowest scopes. Add scopes when a feature needs one, not in advance.

      Files Auth.swift

    2. Referenced from the database, never stored in it. Inspect the file to confirm.

      Files Keychain.swift

    3. A clear reconnect prompt, local data intact.

      Files Auth.swift

    done when · tick each as it passes
    watch out
    • Requesting broad scopes because it is simpler means the consent screen asks for more than you need and users refuse, and it means a token leak is worse than it had to be. Request the narrowest scopes and add one only when a feature actually needs it.
  2. Incremental sync

    A mailbox with a hundred thousand messages syncs changes rather than refetching.

    1. Recent mail first so the client is usable in a minute, older mail backfilled after.

      Files Sync.swift

    2. Pull by history point. Store the point after each successful batch, not at the end.

      Files Sync.swift

    3. Full resynchronisation, tested by corrupting the stored point deliberately.

      Files Sync.swift

    done when · tick each as it passes
    watch out
    • A change cursor expires after a period, and a client that treats an expired cursor as an error stops syncing silently until someone notices missing mail. Detect the specific response and fall back to a full resynchronisation rather than failing.
  3. Labels modelled as labels

    A message in three labels appears in all three, and archiving removes one rather than moving anything.

    1. A join table. Not a folder column with a favourite label.

      Files Labels.swift

    2. Inbox is a label. Archiving removes it and nothing else.

      Files Actions.swift

    3. Rather than choosing one. Users know their messages are in several places.

      Files MessageView.swift

    done when · tick each as it passes
    watch out
    • Mapping labels to a single folder column forces a choice about which label a message lives in, and every subsequent behaviour is wrong: archiving from one view removes it from another, and a message in two labels appears in one. This is the modelling decision that cannot be corrected later without a rewrite.
  4. Instant search and scrolling

    Searching a hundred thousand messages is faster than the web client.

    1. A hundred thousand messages. Every performance claim depends on it existing first.

      Files Fixtures.swift

    2. Full text over subject and body, updated as messages sync.

      Files Search.swift

    3. Render rows, load a window. Never the whole label.

      Files ThreadList.swift

    done when · tick each as it passes
    watch out
    • Loading a whole thread list into memory works until a label has thirty thousand messages, and then the client stalls on opening it. Page the list and render rows, which is what a native list view is for and the reason to build this natively at all.
  5. Actions that work before the network does

    Archiving feels instant and is never lost, even offline.

    1. The view updates at once; the queue makes it true.

      Files Actions.swift

    2. Then surface the failure. A silently dropped action is the worst outcome.

      Files Outbox.swift

    3. Server wins, user is told. Silent reversion looks like a bug and destroys trust.

      Files Reconcile.swift

    done when · tick each as it passes
    watch out
    • Applying locally and never reconciling means a failed action leaves the client showing a state the server does not have, and the message reappears days later with no explanation. Queue with retries, reconcile against the server on the next sync, and surface a conflict rather than flipping the message back silently.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more email.