ALAI Mail Topology — Migadu Domains, Mailbox Inventory, John's 19-Account Ingest Loop (2026-06-08)

ALAI Mail Topology & John's Email Ingest Loop

Last updated: 2026-06-08 (v2 — 19 accounts, daemon-path docs, himalaya touch-points)  |  MC: #103182  |  Built by: FlowForge  |  Validated by: Proveo (Angie Jones) — PASS


1. Mail Infrastructure — Migadu (Single Account)

All ALAI product domains are hosted on one Migadu account. MX records for every domain point to the same two servers:

Domains on this account: alai.no, bilko.io, bilko.cloud, bilko.company, snowit.ba, basicconsulting.no, basicfakta.no, lumiscare.com

Migadu Admin Access

ItemValue / Location
Admin loginalem@alai.no
API keyVaultwarden item "migadu keyy" (86-char token — do NOT print)
IMAP hostimap.migadu.com
SMTP hostsmtp.migadu.com
Web UIhttps://admin.migadu.com

Migadu API Quirks (DO NOT FORGET)


2. Real Mailbox Inventory

These are the real mailboxes that exist in Migadu (verified 2026-06-08 via admin API). Only real mailboxes can be used as alias destinations.

DomainReal mailboxes (local parts)
alai.nojohn, alem, dev, post, admin
bilko.ioadmin, sales, privacy
bilko.cloudadmin, sales
bilko.companyadmin, sales
snowit.baadmin, info, asmir, enis
basicconsulting.nojohn, info
lumiscare.comhello, admin

Note: basicfakta.no is on this Migadu account but has no actively polled mailboxes in John's loop.

Note: lumiscare.com is ALAI's Migadu domain (our infrastructure). It is distinct from caresafetyinnovations.com, which remains a hard-stop boundary (see Section 6).


3. John's Email Ingest — All 19 Monitored Accounts

John's email ingest is managed by ~/system/tools/email-inbox.js and polled by ~/system/daemons/email-agent.js. As of MC #103182 final state (2026-06-08), 19 accounts are registered in email-inbox.db → email_accounts.

Original 6 Accounts (pre-MC #103182)

Account name (DB key)Email addressVault item
johnjohn@basicconsulting.noexisting
infoinfo@basicconsulting.noexisting
alaijohn@alai.noexisting
devdev@alai.noexisting
alemalem@alai.noexisting
gmailalembasic@gmail.comexisting

11 Product/Role Accounts (added MC #103182 round 1)

Account name (DB key)Email addressVault item name
post-alaipost@alai.noMigadu — post@alai.no
admin-alaiadmin@alai.noMigadu — admin@alai.no
sales-bilko-iosales@bilko.ioMigadu — sales@bilko.io
privacy-bilko-ioprivacy@bilko.ioMigadu — privacy@bilko.io
admin-bilko-ioadmin@bilko.ioMigadu — admin@bilko.io
sales-bilko-cloudsales@bilko.cloudMigadu — sales@bilko.cloud
admin-bilko-cloudadmin@bilko.cloudMigadu — admin@bilko.cloud
sales-bilko-companysales@bilko.companyMigadu — sales@bilko.company
admin-bilko-companyadmin@bilko.companyMigadu — admin@bilko.company
info-snowitinfo@snowit.bainfo@snowit.ba IMAP
admin-snowitadmin@snowit.baMigadu — admin@snowit.ba

2 LumisCare Accounts (added MC #103182 round 2 — CEO directive 2026-06-08)

CEO directive: LumisCare must be in John's reading loop. lumiscare.com is ALAI's own Migadu domain — these are operational mailboxes, not CareSafety-boundary addresses.

Account name (DB key)Email addressVault item name
hello-lumiscarehello@lumiscare.comMigadu — hello@lumiscare.com
admin-lumiscareadmin@lumiscare.comMigadu — admin@lumiscare.com

Note on hello@lumiscare.com forwarding: A Migadu direct forward from hello@lumiscare.com → alem@alai.no was active since 2026-05-24. This was removed 2026-06-08 so the mailbox is polled directly under hello-lumiscare with clean labeling. Before removal, LumisCare contact mail appeared in the DB under alem (Migadu ingested the forwarded copy first). After removal, external mail to hello@lumiscare.com is stored under hello-lumiscare only. Confirmed behaviourally: gmail-origin probe stored as DB id=9195 under hello-lumiscare, not duplicated under alem.

App-passwords for the 5 newly created admin@* mailboxes (round 1) were generated via the Migadu API and stored as Vaultwarden items. Vault IDs: 558181ec, 8dfe8d2d, 2f38a16a, 7d0f9216, 2fb07c20.


4. Alias Map — Dead-Address Fixes (2026-06-08)

The following addresses were previously advertised (on websites, legal pages, landing pages) but did not correspond to any real mailbox — all mail to them was silently bouncing. Migadu aliases were created to route them to the nearest real same-domain mailbox.

Dead address (was bouncing)Now routes toWhy
info@alai.nojohn@alai.noalai.no contact form was sending to this dead address — all website contact submissions were lost
support@bilko.iosales@bilko.iobilko.io landing mailto link
podrska@bilko.iosales@bilko.iobilko.io Bosnian support address on legal/terms pages
legal@bilko.ioadmin@bilko.iobilko.io legal/terms page
security@bilko.ioadmin@bilko.iobilko.io security disclosure address
support@bilko.cloudsales@bilko.cloudbilko.cloud landing mailto
support@bilko.companysales@bilko.companybilko.company landing mailto

Pre-fix state: Only postmaster@{domain} → admin@{domain} aliases existed. No rewrites, no catch-all. All other non-existent local-parts bounced.
Post-fix: All advertised addresses now deliver to a real monitored mailbox. Nothing bounces.


5. Contact-Form Routing

ProductContact form pathWhere mail ends up
alai.no website Vercel serverless: ~/business/ALAI-Holding-AS/web/api/contact.js (nodemailer) Sends to info@alai.no (which now aliases to john@alai.no — monitored). Was dead before 2026-06-08 fix.
Bilko landing pages Cloudflare Pages function: apps/landing-*/functions/api/lead.js Posts to Slack #ceo channel (C0AFJDP9V6U) + writes to Cloudflare KV (BILKO_LEADS). No email path — separate from IMAP polling.

6. Boundary Accounts — NOT Polled (intentional)

AddressReason not polled
asmir@snowit.baPersonal mailbox belonging to Asmir (SnowIT partner). He reads his own mail.
enis@snowit.baPersonal mailbox belonging to Enis. Same reason.
Any *@caresafetyinnovations.comCareSafety hard-stop boundary — health/patient-adjacent service under external ownership. NOT on ALAI's Migadu account. Never poll. See CareSafety boundary memo in MEMORY.

Important distinction: lumiscare.com (ALAI's Migadu domain — hello@, admin@) IS polled. caresafetyinnovations.com (external operator) is the hard boundary, not lumiscare.com.


7. Daemon Architecture — Production Path

Understanding the daemon path is critical when debugging ingest issues or adding accounts.

Production Execution Path

Himalaya Layer — Present but Bypassed in Production

Even with HIMALAYA_DISABLED=1, the daemon still routes account resolution through himalaya-adapter.js ACCOUNT_MAP. If an account name is missing from ACCOUNT_MAP, the daemon throws Unknown account: <name> and the account is skipped entirely.

Validated (2026-06-08T13:15Z): Zero "Unknown account" errors in both daemon runs (wrapper + legacy). All 19 accounts have last_checked_at = 2026-06-08T13:09:39Z.


8. Components — All 8 Touch-Points

Adding any new account requires updating all 8 of the following. Missing any one will cause silent failures or "Unknown account" errors.

#FileWhat to change
1 ~/system/tools/email-inbox.js (a) Add INSERT OR IGNORE INTO email_accounts (name, email) VALUES ('<name>', '<email>') seed row.
(b) Add a guarded migration block to extend the emails table CHECK constraint to include the new account name. The CHECK constraint is hardcoded and cannot be altered without rebuilding the table (SQLite limitation). The guard must use a unique string from the new account name (e.g. !ddlRow.sql.includes("'<name>'")). All existing rows and all 25 columns must be preserved in the rebuilt table. This is the most error-prone step — see Section 9 for the gotcha detail.
2 ~/system/tools/mail-native.js Add account-name → Vaultwarden item-name entry in VAULT_NAMES map.
3 ~/system/tools/himalaya-adapter.js Add account-name → email entry in ACCOUNT_MAP (L34–56 area). Without this, the daemon throws "Unknown account" and skips the account entirely even in legacy mode.
4 ~/.config/himalaya/config.toml Add a new [accounts.<name>] stanza. Required even when HIMALAYA_DISABLED=1.
5 ~/system/daemons/email-agent.js Add account to counts map (L2459 area). Also confirm it is present in the fetch loop and last_checked_at update loop (both must be mirrored).
6 ~/system/tools/email-imap-db-audit.js Add account to ACCOUNTS constant.
7 ~/system/tools/email-action-hard-check.js Add account to ALL_MONITORED_ACCOUNTS constant.
8 Vaultwarden (via bw CLI) Create app-password item named Migadu — <email> with the IMAP/SMTP password. New admin@ mailboxes require a new app-password generated via Migadu API (PUT /v1/domains/{d}/mailboxes/{lp}). Existing sales@/privacy@/info@ mailboxes may already have creds in Vaultwarden — check before creating.

Files Changed in MC #103182 (round 1 — 11 accounts)

All files modified additively. Round 1 changed 5 files (himalaya touch-points were added in round 2 as BLOCKER-2 fix).

FileLines changed
email-inbox.jsL159–172 (seeds) + L141–208 (CHECK migration, 17-account guard)
mail-native.jsL76–88 (11 VAULT_NAMES entries)
email-imap-db-audit.jsL51 (ACCOUNTS 5→16)
email-action-hard-check.jsL14–22 (ALL_MONITORED_ACCOUNTS 17 accounts)
email-agent.jsL1853–1861 (fetch loop), L1889–1895 (last_checked_at loop)

Files Changed in MC #103182 (round 2 — LumisCare + BLOCKER-2 fix)

FileLines changed
email-inbox.jsL212–311 (second guarded CHECK migration, 19-account guard: !ddlRow2.sql.includes("'hello-lumiscare'")); 2 new email_accounts seed rows
mail-native.jsL90–91 (hello-lumiscare + admin-lumiscare VAULT_NAMES)
himalaya-adapter.jsL34–56 (ACCOUNT_MAP expanded to 19 entries)
~/.config/himalaya/config.toml2 new [accounts.*] stanzas (19 total)
email-agent.jsL1862 (fetch loop), L1899 (last_checked_at loop), L2459–2468 (counts map)
email-action-hard-check.jsL24 (hello-lumiscare + admin-lumiscare in ALL_MONITORED_ACCOUNTS)
email-imap-db-audit.jsL60 (both accounts in ACCOUNTS array)

Known Minor Issue (pre-existing, non-blocking)

After SMTP send via mail-native.js, the IMAP post-send copy to Sent folder times out with ETIMEOUT. Delivery succeeds (Message-ID is logged). This is a cosmetic issue in the IMAP cleanup code — pre-existing, unrelated to MC #103182. Separate MC recommended.


9. GOTCHA — emails Table CHECK Constraint

This is the most dangerous footgun when adding new accounts. Read before touching email-inbox.js.

The emails table in ~/system/databases/email-inbox.db has a hardcoded SQLite CHECK constraint:

account TEXT NOT NULL CHECK(account IN ('john','info','alai','dev','alem','gmail',
  'post-alai','admin-alai',
  'sales-bilko-io','privacy-bilko-io','admin-bilko-io',
  'sales-bilko-cloud','admin-bilko-cloud',
  'sales-bilko-company','admin-bilko-company',
  'info-snowit','admin-snowit',
  'hello-lumiscare','admin-lumiscare'
))

The trap: INSERT OR IGNORE silently discards rows that violate CHECK constraints — no exception is thrown, no warning is logged. If a new account name is not in this list, every email received by that account is permanently lost at ingest time. In MC #103182 this caused 27 real emails to be silently dropped before the issue was caught by Proveo.

The fix: SQLite does not support ALTER TABLE ... MODIFY COLUMN with a new CHECK constraint. The only way to extend it is to rebuild the table:

  1. Read current DDL: SELECT sql FROM sqlite_master WHERE type='table' AND name='emails'
  2. Guard the migration: check that the new account name is NOT already in the DDL (idempotency)
  3. In a transaction: CREATE TABLE emails_new (...same schema + extended CHECK...)INSERT INTO emails_new SELECT * FROM emails → assert row count matches → DROP TABLE emailsALTER TABLE emails_new RENAME TO emails → recreate indexes → COMMIT
  4. Rollback on any error or row count mismatch

The pattern already exists in email-inbox.js — follow it exactly. All 25 columns must be listed explicitly, including the post-migration additions: delegated_to, delegated_at, deadline, body, triaged_at, auto_forwarded.


10. Runbook — How to Add a New Mailbox to John's Loop

  1. Verify the mailbox exists in Migadu.
    Check via GET /v1/domains/{domain}/mailboxes using the admin API key ("migadu keyy" in Vaultwarden).
    If it does not exist, create it via the admin UI or API first.
  2. Create an app-password for the mailbox.
    Use Migadu admin UI (Mailbox settings > App Passwords) or PUT /v1/domains/{domain}/mailboxes/{local_part}.
    Store the password as a new Vaultwarden item named Migadu — {email}.
  3. [Touch-point 2] Add to mail-native.js VAULT_NAMES map.
    Key = your chosen account name (e.g. sales-newdomain), value = the Vaultwarden item name.
  4. [Touch-point 3] Add to himalaya-adapter.js ACCOUNT_MAP.
    Add '<name>': '<email>' in the ACCOUNT_MAP object. Without this step the daemon throws "Unknown account" and the account is silently skipped.
  5. [Touch-point 4] Add stanza to ~/.config/himalaya/config.toml.
    Follow the existing pattern for a Migadu account stanza.
  6. [Touch-point 1a] Add the email_accounts seed to email-inbox.js.
    Append INSERT OR IGNORE INTO email_accounts (name, email) VALUES ('<name>', '<email>') in the seed block.
  7. [Touch-point 1b — CRITICAL] Add a guarded CHECK migration to email-inbox.js getDb().
    Read Section 9 first. Guard: !ddlRow.sql.includes("'<name>'"). Extend CHECK to include new account. Rebuild table in a transaction preserving all 25 columns. Test idempotency.
  8. [Touch-point 6] Add to email-imap-db-audit.js ACCOUNTS array.
  9. [Touch-point 7] Add to email-action-hard-check.js ALL_MONITORED_ACCOUNTS array.
  10. [Touch-point 5] Add to email-agent.js counts map, fetch loop, and last_checked_at loop.
    All three locations must be mirrored.
  11. Run syntax checks on all modified files.
    node --check ~/system/tools/email-inbox.js && node --check ~/system/tools/mail-native.js && node --check ~/system/tools/himalaya-adapter.js && node --check ~/system/daemons/email-agent.js
  12. Test connectivity.
    node ~/system/tools/mail-native.js test --account <name> — expect IMAP OK + SMTP OK.
  13. Restart the email-agent daemon (LaunchAgent: com.john.email-agent) so the updated accounts array and config take effect.
  14. Proveo ingest probe.
    Send a test email from a non-ALAI sender (e.g. gmail account) with subject INGEST-PROBE-<name>-<timestamp>. This avoids the Migadu catch-all pre-emption issue (see Section 1 API quirks). Trigger one daemon cycle. Confirm the row appears under the correct account name via node ~/system/tools/email-inbox.js search "INGEST-PROBE".
  15. If adding a new alias (not a real mailbox): create the Migadu alias first (same-domain destination only, with Accept: application/json header). Then proceed from step 3.

11. Validation Evidence (MC #103182 — Final)

Round 1 (17 accounts — 2026-06-08T11:24Z)

CheckResult
Code changes (5 files) verified by ProveoPASS
DB registry — 17 rows in email_accountsPASS
IMAP/SMTP connectivity — 11/11 new accountsPASS
emails table CHECK migration (emails_new rebuild)PASS — DDL confirmed, 4697 rows preserved
Ingest probes — 4/4 probe accounts persist to DBPASS (round 2 probes after schema fix; DB ids 9052/9056/9057/9059/9062/9063/9064)
Regression — original 6 accountsPASS — counts growing, timestamps advancing
No-loop / alias dedup (UNIQUE on message_id)PASS — 0 duplicate message_ids
email-action-hard-check.js exit codePASS — exit 0, 17 accounts in scope

Blocker found and fixed during round 1 validation: The emails table had a hardcoded CHECK covering only the original 6 accounts. INSERT OR IGNORE silently dropped 27 real emails before the migration was applied. See Section 9 for the full gotcha description.

Round 2 (19 accounts — LumisCare + daemon path — 2026-06-08T13:15Z)

CheckResult
ACCOUNT_MAP (himalaya-adapter.js) has 19 entriesPASS — L34–56 confirmed
config.toml has 19 [accounts.*] stanzasPASS — grep count = 19
email-agent.js counts map has 19 accountsPASS — L2460–2468
Zero "Unknown account" errors (wrapper run)PASS — grep -c = 0 / 40 lines
Zero "Unknown account" errors (legacy/production run)PASS — grep -c = 0
Zero silent drops / CHECK failures (production run)PASS
admin-lumiscare ingest proofPASS — DB id=9070 under admin-lumiscare
hello-lumiscare ingest proof (external sender)PASS — DB id=9195 under hello-lumiscare (gmail-origin probe)
sales-bilko-cloud ingest proofPASS — DB id=9193
sales-bilko-company ingest proofPASS — DB id=9194
hello@lumiscare.com forwarding removal (behavioural)PASS — gmail-origin stored only under hello-lumiscare, not duplicated under alem
All 19 last_checked_at freshPASS — 2026-06-08T13:09:39Z all accounts
No duplicate message_idsPASS — 0 rows
Regression (orig 6 + prior 11)PASS — row counts growing, timestamps fresh

Evidence files: /tmp/evidence-103182/flowforge-build.md, /tmp/evidence-103182/proveo-validation.md, /tmp/evidence-103182/daemon-wrapper-run.log, /tmp/evidence-103182/daemon-legacy-run.log


Revision #2
Created 2026-06-08 11:28:24 UTC by John
Updated 2026-06-08 13:16:09 UTC by John