MongoDB & Migrations

📦v1.0.0📅2026-04-28🔄Updated 2026-04-28👤Admin Team
administrationconfigurationmongodbmigrations

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

CollectionPurpose
usersAuthenticated users — email, bcrypt hash, super_admin flag, locale
workspacesTenant isolation boundaries — slug, name, owner, is_system
membershipsUser ↔ workspace assignments — role, is_moderator
campaignsShadow records for Core jobs — external_id, job_id, status, ownership
sender_namesSender identity entities — name, workspace, status
sender_name_revisionsVersioned sender parameters — saddr, ston/snpi/dton/dnpi, approval state
audit_logsCompliance event log — action, user, resource, timestamp (TTL)
invitationsPending workspace invitations — token hash, expires_at (TTL)
join_requestsSelf-service workspace join requests
metaSchema version tracking

proxy_sessions existed before v2 and was dropped during migration.


Migration History

v1 — initial-schema-and-indexes

Creates all initial collections and their indexes:

  • users: unique index on email
  • workspaces: unique slug; index on owner_id
  • memberships: unique (workspace_id, user_id); indexes on user_id and (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 on timestamp (value from AUDIT_RETENTION_DAYS)
  • invitations: unique partial (workspace_id, email) for pending records; unique token_hash; TTL index on expires_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: adds deleted_at, blocked_at, previous_email (all null)
  • Backfills workspaces: adds is_system: false
  • Creates the system archive workspace (slug: "archive", is_system: true)
  • Adds indexes on users.deleted_at, users.blocked_at, and campaigns.(created_by, status)

Requires at least one super_admin user. Run make seed before 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: adds archived_at, archived_by, archived_reason (all null)
  • 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:

  1. Restore MongoDB from a backup taken immediately before the deploy.
  2. Deploy the previous application image.
  3. Verify the schema version matches the previous release.

Before every deploy, ensure a backup completed successfully. See Backups & Recovery for backup strategy.


Next Steps