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:
aspmx1.migadu.com(priority 10)aspmx2.migadu.com(priority 20)
Domains on this account: alai.no, bilko.io, bilko.cloud, bilko.company, snowit.ba, basicconsulting.no, basicfakta.no, lumiscare.com
Migadu Admin Access
| Item | Value / Location |
|---|---|
| Admin login | [email protected] |
| API key | Vaultwarden item "migadu keyy" (86-char token — do NOT print) |
| IMAP host | imap.migadu.com |
| SMTP host | smtp.migadu.com |
| Web UI | https://admin.migadu.com |
Migadu API Quirks (DO NOT FORGET)
- GET aliases — response key is
address_aliases, notaliases. - Create alias — must send JSON body
{"local_part": "...", "destinations": ["..."]}with headerAccept: application/json(omitting Accept = HTML response, silent fail). - Alias destinations MUST be same-domain. Cross-domain targets (e.g.
[email protected] → [email protected]) return HTTP 400. Route to a real mailbox on the same domain instead. - No catch-all rewrites — verified via
/rewritesendpoint (empty on all domains). Any email to a non-existent local-part that has no alias bounces. - App-passwords for new mailboxes are created via
PUT /v1/domains/{domain}/mailboxes/{local_part}and stored as Vaultwarden items (never in logs). - Migadu catch-all copy ([email protected]): [email protected] is configured as a global catch-all copy recipient for all outgoing ALAI-managed-domain mail. This means emails sent FROM any ALAI account will also appear in alem's INBOX. Because
alemiterates before product accounts in the daemon list, it ingests those Message-IDs first; the UNIQUE(message_id) constraint causes product-account inserts to be no-ops. This affects ingest attribution for ALAI-origin probes only — external (non-ALAI) mail is not affected. See Section 6 for forwarding removal note.
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.
| Domain | Real mailboxes (local parts) |
|---|---|
alai.no | john, alem, dev, post, admin |
bilko.io | admin, sales, privacy |
bilko.cloud | admin, sales |
bilko.company | admin, sales |
snowit.ba | admin, info, asmir, enis |
basicconsulting.no | john, info |
lumiscare.com | hello, admin |
Note: lumiscare.com and basicfakta.no areis on this Migadu account but arehas notno actively polled mailboxes in John's loop.
Note: is hello@lumiscare.lumiscare.comCareSafety-gatedALAI'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 1719 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), 1719 accounts are registered in email-inbox.db → email_accounts.
Original 6 Accounts (pre-MC #103182)
| Account name (DB key) | Email address | Vault item |
|---|---|---|
john | [email protected] | existing |
info | [email protected] | existing |
alai | [email protected] | existing |
dev | [email protected] | existing |
alem | [email protected] | existing |
gmail | [email protected] | existing |
11 New Product/Role Accounts (added MC #103182,#103182 2026-06-08)round 1)
| Account name (DB key) | Email address | Vault item name |
|---|---|---|
post-alai | [email protected] | Migadu — [email protected] |
admin-alai | [email protected] | Migadu — [email protected] |
sales-bilko-io | [email protected] | Migadu — [email protected] |
privacy-bilko-io | [email protected] | Migadu — [email protected] |
admin-bilko-io | [email protected] | Migadu — [email protected] |
sales-bilko-cloud | [email protected] | Migadu — [email protected] |
admin-bilko-cloud | [email protected] | Migadu — [email protected] |
sales-bilko-company | [email protected] | Migadu — [email protected] |
admin-bilko-company | [email protected] | Migadu — [email protected] |
info-snowit | [email protected] | [email protected] IMAP |
admin-snowit | [email protected] | Migadu — [email protected] |
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 address | Vault item name |
|---|---|---|
hello-lumiscare | [email protected] | Migadu — [email protected] |
admin-lumiscare | [email protected] | Migadu — [email protected] |
Note on [email protected] forwarding: A Migadu direct forward from [email protected] → [email protected] 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 [email protected] 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 ([email protected],round [email protected], [email protected], [email protected], [email protected])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 to | Why |
|---|---|---|
[email protected] | [email protected] | alai.no contact form was sending to this dead address — all website contact submissions were lost |
[email protected] | [email protected] | bilko.io landing mailto link |
[email protected] | [email protected] | bilko.io Bosnian support address on legal/terms pages |
[email protected] | [email protected] | bilko.io legal/terms page |
[email protected] | [email protected] | bilko.io security disclosure address |
[email protected] | [email protected] | bilko.cloud landing mailto |
[email protected] | [email protected] | bilko.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
| Product | Contact form path | Where mail ends up |
|---|---|---|
| alai.no website | Vercel serverless: ~/business/ALAI-Holding-AS/web/api/contact.js (nodemailer) |
Sends to [email protected] (which now aliases to [email protected] — 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)
| Address | Reason not polled |
|---|---|
[email protected] | Personal mailbox belonging to Asmir (SnowIT partner). He reads his own mail. |
[email protected] | Personal mailbox belonging to Enis. Same reason. |
Any | CareSafety hard-stop boundary — health/patient-adjacent |
Important distinction: lumiscare.com (ALAI's Migadu domain — hello@, admin@) IS polled. caresafetyinnovations.com (external operator) is the hard boundary, not lumiscare.com.
7. ComponentsDaemon ChangedArchitecture (MC— #103182)Production Path
AllUnderstanding 5the filesdaemon werepath modifiedis additivelycritical (nowhen existingdebugging rowsingest issues or adding accounts.
Production Execution Path
- LaunchAgent:
com.john.email-agent— startsemail-agent-wrapper.sh, setsHIMALAYA_DISABLED=1via plistEnvironmentVariableskey. - Wrapper:
~/system/daemons/email-agent-wrapper.sh— thin shell wrapper, does not set HIMALAYA_DISABLED itself. - Daemon:
~/system/daemons/email-agent.js— whenHIMALAYA_DISABLED=1, all 19 accountsremoved)use the legacy unseen-fetch IMAP path (direct node-imap, proven stable).
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.
- himalaya-adapter.js ACCOUNT_MAP — must list all 19 accounts (currently L34–56).
- ~/.config/himalaya/config.toml — must have 19
[accounts.*]stanzas (verified: grep count = 19). - When run without
HIMALAYA_DISABLED=1(bare wrapper invocation), the himalaya binary is called and times out after 120s per account (~82 min total for 19 accounts). This is expected and non-destructive but slow. Production LaunchAgent always sets the env flag.
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.
| # | File | What |
|---|---|---|
| 1 | ~/system/tools/email-inbox.js |
(a) INSERT OR IGNORE INTO email_accounts (name, email) VALUES ('<name>', '<email>') (b) emails table CHECK constraint !ddlRow.sql.includes("'<name>'")). All existing rows and all 25 columns |
| 2 | ~/system/tools/mail-native.js |
VAULT_NAMES |
| 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 |
ACCOUNTS |
| 7 | ~/system/tools/email-action-hard-check.js |
ALL_MONITORED_ACCOUNTS |
| 8 | Vaultwarden (via 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 /). 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).
| File | Lines changed |
|---|---|
email-inbox.js | L159–172 (seeds) + L141–208 (CHECK migration, 17-account guard) |
mail-native.js | L76–88 (11 VAULT_NAMES entries) |
email-imap-db-audit.js | L51 (ACCOUNTS 5→16) |
email-action-hard-check.js | L14–22 (ALL_MONITORED_ACCOUNTS 17 accounts) |
email-agent.js | L1853–1861 (fetch loop), L1889–1895 (last_checked_at loop) |
Files Changed in MC #103182 (round 2 — LumisCare + BLOCKER-2 fix)
| File | Lines changed |
|---|---|
email-inbox.js | L212–311 !ddlRow2.sql.includes("'hello-lumiscare'")); 2 new email_accounts seed rows |
mail-native.js | L90–91 (hello-lumiscare + admin-lumiscare VAULT_NAMES) |
himalaya-adapter.js | L34–56 (ACCOUNT_MAP expanded to 19 entries) |
~/.config/himalaya/config.toml | 2 new [accounts.*] stanzas (19 total) |
email-agent.js | L1862 (fetch loop), L1899 (last_checked_at loop), L2459–2468 (counts map) |
email-action-hard-check.js | L24 (hello-lumiscare + admin-lumiscare in ALL_MONITORED_ACCOUNTS) |
email-imap-db-audit.js | L60 (both accounts ACCOUNTS |
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 for fix.recommended.
8.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:
- Read current DDL:
SELECT sql FROM sqlite_master WHERE type='table' AND name='emails' - Guard the migration: check that the new account name is NOT already in the DDL (idempotency)
- In a transaction:
CREATE TABLE emails_new (...same schema + extended CHECK...)→INSERT INTO emails_new SELECT * FROM emails→ assert row count matches →DROP TABLE emails→ALTER TABLE emails_new RENAME TO emails→ recreate indexes → COMMIT - 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
-
Verify the mailbox exists in Migadu.
Check viausing the admin API key ("migadu keyy" in Vaultwarden).curlGET-s -H "Authorization: Token <api-key>" "https://admin.migadu.com/v1/domains/{domain}/mailboxes"mailboxes
If it does not exist, create it via the admin UI or API first. -
Create an app-password for the mailbox.
Use Migadu admin UI (Mailbox settings > App Passwords) orPUT /v1/domains/{domain}/mailboxes/{local_part}API..
Store the password as a new Vaultwarden item namedMigadu — {email}. -
[Touch-point 2] Add
the accounttomail-native.jsVAULT_NAMES map.
Key = your chosen account name (e.g.sales-newdomain), value = the Vaultwarden item name. -
[Touch-point 3] Add to
himalaya-adapter.jsACCOUNT_MAP.
Add'<name>': '<email>'in the ACCOUNT_MAP object. Without this step the daemon throws "Unknown account" and the account is silently skipped. -
[Touch-point 4] Add stanza to
~/.config/himalaya/config.toml.
Follow the existing pattern for a Migadu account stanza. -
[Touch-point 1a] Add the
accountemail_accounts seed toemail-inbox.js.
AppendanINSERT OR IGNORE INTO email_accounts (name, email) VALUES ('<name>', '<email>')in theaccounts-seed block.Also extend theemailstable CHECK constraint migrationto include the new account name. Add the name to thenewAccountsarray in the migration block. The guard condition must also be updated to include a unique string from the new account. -
[Touch-point 1b — CRITICAL] Add
theaaccountguarded CHECK migration toemail-inbox.jsgetDb().
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. -
[Touch-point 6] Add to
email-imap-db-audit.jsACCOUNTS array. -
[Touch-point 7] Add
the accounttoemail-action-hard-check.jsALL_MONITORED_ACCOUNTS array. -
[Touch-point 5] Add
the accounttoemail-agent.jsfetch-loopcountsarraymap, fetch loop, andlast_checked_atupdateloop.
loopAll(boththree locations must bemirrored).mirrored. -
Run syntax
checks.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 -
Test connectivity.
node ~/system/tools/mail-native.js test --account <name>— expect IMAP OK + SMTP OK. -
Restart the email-agent daemon (LaunchAgent:
com.john.email-agent) so the updated accounts arraytakesand config take effect. -
Proveo ingest probe.
Send a test email from a non-ALAI sender (e.g. gmail account) with subjecttokenINGEST-PROBE-<.accountname>-<timestamp>toThis avoids thenewMigaduaddress.catch-all pre-emption issue (see Section 1 API quirks). Trigger one daemon cycle. Confirm the row appearsinemail_accountsandemailstables withunder the correct account name vianode ~/system/tools/email-inbox.js search "INGEST-PROBE". -
If
it isadding a new alias (not a real mailbox):—create the Migadu alias firstin Migadu. Remember: alias(same-domain destinationMUST be a real mailbox on the same domain. Use the Migadu APIonly, withAccept: application/jsonheaderheader).andThenJSONproceedbodyfrom{"local_part":step"...", "destinations": ["..."]}.3.
9.11. Validation Evidence (MC #103182)#103182 — Final)
Round 1 (17 accounts — 2026-06-08T11:24Z)
| Check | Result |
|---|---|
| Code changes (5 files) verified by Proveo | PASS |
| DB registry — 17 rows in email_accounts | PASS |
| IMAP/SMTP connectivity — 11/11 new accounts | PASS |
| emails table CHECK migration (emails_new rebuild) | PASS — DDL confirmed, 4697 rows preserved |
| Ingest probes — 4/4 probe accounts persist to DB | PASS (round |
| Regression — original 6 accounts | PASS — counts growing, timestamps advancing |
| No-loop / alias dedup (UNIQUE on message_id) | PASS — 0 duplicate message_ids |
| email-action-hard-check.js exit code | PASS — exit 0, |
Blocker found and fixed during round 1 validation: The emails table had a hardcoded CHECK(accountCHECK INcovering ('john','info','alai','dev','alem','gmail')).only Thisthe causedoriginal 6 accounts. INSERT OR IGNORE to silently discard all rows from the 11 new accounts —dropped 27 real emails were dropped before the fixmigration was applied. TheSee schemaSection migration9 was added to email-inbox.js and re-validated. The 27 dropped emails remain infor the IMAPfull mailboxesgotcha and can be recovered via a full-history re-fetch if needed.description.
Round 2 (19 accounts — LumisCare + daemon path — 2026-06-08T13:15Z)
| Check | Result |
|---|---|
| ACCOUNT_MAP (himalaya-adapter.js) has 19 entries | PASS — L34–56 confirmed |
| config.toml has 19 [accounts.*] stanzas | PASS — grep count = 19 |
| email-agent.js counts map has 19 accounts | PASS — 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 proof | PASS — 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 proof | PASS — DB id=9193 |
| sales-bilko-company ingest proof | PASS — DB id=9194 |
| [email protected] forwarding removal (behavioural) | PASS — gmail-origin stored only under hello-lumiscare, not duplicated under alem |
| All 19 last_checked_at fresh | PASS — 2026-06-08T13:09:39Z all accounts |
| No duplicate message_ids | PASS — 0 rows |
| Regression (orig 6 + prior 11) | PASS — row counts growing, timestamps fresh |
FullEvidence 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