MongoDB & Migrations
MongoDB & Migrations
Message Center uses a dedicated MongoDB cluster with 10 active collections managed through 9 schema migrations. All migrations are idempotent and applied in ascending version order.
Collections
| Collection | Purpose |
|---|---|
users | Authenticated users — email, bcrypt hash, super_admin flag, locale |
workspaces | Tenant isolation boundaries — slug, name, owner, is_system |
memberships | User ↔ workspace assignments — role, is_moderator |
campaigns | Shadow records for Core jobs — external_id, job_id, status, ownership |
sender_names | Sender identity entities — name, workspace, status |
sender_name_revisions | Versioned sender parameters — saddr, ston/snpi/dton/dnpi, approval state |
audit_logs | Compliance event log — action, user, resource, timestamp (TTL) |
invitations | Pending workspace invitations — token hash, expires_at (TTL) |
join_requests | Self-service workspace join requests |
meta | Schema version tracking |
proxy_sessionsexisted before v2 and was dropped during migration.
Migration History
v1 — initial-schema-and-indexes
Creates all initial collections and their indexes:
users: unique index onemailworkspaces: uniqueslug; index onowner_idmemberships: unique(workspace_id, user_id); indexes onuser_idand(workspace_id, role)sender_names: unique partial(workspace_id, name)for active statuses; compound(workspace_id, status, created_at)sender_name_revisions: unique(workspace_id, sender_name_id, revision_number); compound(workspace_id, status)campaigns: unique(workspace_id, external_id)and(workspace_id, job_id); compound(workspace_id, status, mode, created_at)audit_logs: compound(workspace_id, timestamp),(workspace_id, user_id),(workspace_id, resource_type, resource_id); TTL index ontimestamp(value fromAUDIT_RETENTION_DAYS)invitations: unique partial(workspace_id, email)for pending records; uniquetoken_hash; TTL index onexpires_at
v2 — drop-proxy-sessions-collection
Drops the proxy_sessions collection. After the service-to-service auth rework, the proxy JWT is cached in process memory — no per-user session storage is needed.
v3 — add-join-requests-collection
Creates join_requests with:
- Unique
(workspace_id, user_id)— one pending request per user per workspace - Compound
(workspace_id, status, created_at)for queue display
v4 — archive-workspace-and-user-state-fields
- Backfills
users: addsdeleted_at,blocked_at,previous_email(allnull) - Backfills
workspaces: addsis_system: false - Creates the system
archiveworkspace (slug: "archive",is_system: true) - Adds indexes on
users.deleted_at,users.blocked_at, andcampaigns.(created_by, status)
Requires at least one super_admin user. Run
make seedbefore this migration on a fresh database.
v5 — add-user-wizard-hints-preference
Backfills show_wizard_hints: true on all users documents. Controls whether the campaign creation wizard shows inline hints.
v6 — audit-log-compound-index-and-ttl-sync
Adds compound index (workspace_id, resource_type, resource_id, timestamp DESC) on audit_logs for efficient paginated per-resource audit queries.
TTL sync: if AUDIT_RETENTION_DAYS changed since the previous migration run, this migration drops and recreates the ttl_audit_logs index with the updated expireAfterSeconds value. MongoDB does not propagate TTL changes to existing indexes automatically.
v7 — campaigns-workspace-created-index-for-observability
Adds index (workspace_id, created_at DESC) on campaigns to support the 14-day daily series aggregation on the workspace overview dashboard.
v8 — add-campaign-archive-flags
- Backfills
campaigns: addsarchived_at,archived_by,archived_reason(allnull) - Marks legacy records (campaigns physically moved to the archive workspace before v8) with
archived_at: <now>,archived_reason: "user_deleted" - Adds compound index
(workspace_id, archived_at, created_at DESC)for archive scope queries
v9 — add-membership-is-moderator-flag
Backfills is_moderator: false on all memberships documents. When true on an author membership, this flag grants moderator-level permissions on campaigns and sender revisions in that workspace.
Running Migrations
Migrations run in version order and skip versions already applied (tracked in meta.schema_version). Running against an already-migrated database is safe.
Development
make migrate
Docker
docker run --env-file .env core-admin yarn migrate
Kubernetes
kubectl run -it --rm migrate \
--image=<your-registry>/core-admin:latest \
--restart=Never \
-- yarn migrate
Set environment variables via --env flags or a Secret reference. Run migrations before the new app pods start on every deploy.
Production Docker Compose
make prod-migrate
Verifying the Schema Version
After migrations complete, the meta collection contains:
{ "_id": "schema", "schema_version": 9, "updated_at": "..." }
You can also check from the Diagnostics page (/diagnostics) — the DB schema tile shows the current version number alongside the expected value (9).
Rollback Notes
There are no down migrations. Rollback procedure:
- Restore MongoDB from a backup taken immediately before the deploy.
- Deploy the previous application image.
- Verify the schema version matches the previous release.
Before every deploy, ensure a backup completed successfully. See Backups & Recovery for backup strategy.
Next Steps
- Seeding & Bootstrap — create the super admin account after fresh migrations
- Backups & Recovery — MongoDB backup and restore