Background Services
When to run the logs-service and automations-service workers alongside the APIs, and how work reaches them.
For local startup, see Local Setup. For queue inspection, see Logs & Troubleshooting. For gateway registration mechanics, see Backend & API Gateway.
The two services
| Service | Nx project | Port | Registers in Redis | Queues read |
|---|---|---|---|---|
| Logs | logs-service (backend/services/logs) | 3301 | erxes-service-logs | logs-put_log, logs-activity_log, logs-segment-{changed,forget,rebuild,reconcile} |
| Automations | automations-service (backend/services/automations) | 3302 | erxes-service-automations | automations-trigger, automations-action, automations-aiAgent |
Both are plain Express apps exposing /health (JSON via createHealthRoute) but no GraphQL subgraph. They do not appear in getPlugins(), so the gateway never waits for them or routes GraphQL to them. Each registers itself manually on startup: the address under erxes-service-<name> and {"dbConnectionString": MONGO_URL} under erxesservice:config:<service-name>, using LOAD_BALANCER_ADDRESS when set.
The automations service additionally mounts webhook routes (webhookRoutes) and a /trpc router serving the automations.trigger and automations.completeDeferredAction procedures that plugins call back into.
How work reaches the queues
All queues are BullMQ queues on the shared Redis, named <service>-<queue>. Producers call sendWorkerQueue(service, queue); workers call createMQWorkerWithListeners(service, queue, handler): both live in mq-worker.ts. Notable producers:
sendWorkerQueue('logs', 'put_log'): event/audit log entries written bylogHandler, which wraps pluginapiHandlersand log-enabled code paths.logHandlerfirst checkscheckServiceRunning('logs')(theerxes-service-logskey); with no logs service registered, the work still executes but nothing is persisted.sendWorkerQueue('logs', 'activity_log'): activity feed entries.sendWorkerQueue('logs', 'segment-*'): segment membership jobs produced throughSEGMENT_QUEUESincore-modules/segments.sendWorkerQueue('automations', 'trigger'): automation trigger dispatch fromsendAutomationTriggerincore-modules/automations.sendWorkerQueue('gateway', 'update-apollo-router'): production supergraph recomposition; see Backend & API Gateway.
The gateway also creates a gateway-service-discovery queue for its Bull Board UI at /bullmq-board.
How plugins register automation and log behavior
Plugins declare background behavior in the meta object passed to startPlugin: no direct queue access needed:
meta.automations:startAutomationswrites the config intoerxesservice:config:<plugin>.meta.automationsand mounts typed tRPC producers on the plugin's/trpcrouter (receiveActions,setProperties,checkCustomTrigger,findObject,generateAiContext,loadAiKnowledgeDocumentBatch,lookupAiTool). The automations service reads every plugin's meta from Redis (getPlugin(name).config.meta.automations) and calls these procedures while executing triggers and actions.meta.afterProcess: registersafterMutation,afterAuth,afterApiRequest,afterDocumentUpdated, andafterDocumentCreatedproducers the same way.meta.logs: declares log content types so event and activity entries are labeled correctly.meta.segments:initSegmentProducersregisters the plugin's segment producers.meta.importExport: starts an in-process import/export worker on the plugin.
Data layout
The logs service resolves tenant models per subdomain like an API, then splits storage:
activityLogsanduserslive in the tenant database (same as the app DB).logsdocuments go to a separate database named<tenant-db>_logsviadb.useDb(...)in connectionResolvers.ts; forMONGO_URL=.../erxes, that iserxes_logs.
This is also where the automatic undo journal lands: writes through erxes-api-shared models are journaled to <tenant>_logs so System Logs → Undo can revert updates and deletes. The journal capture always runs; without the logs service, entries are queued but never persisted. LOG_RETENTION_DAYS (default 365) sets the log document TTL.
Enabling services locally
scripts/start-api-dev.js (behind pnpm dev:apis) reads three variables and runs nx run-many -t serve -p core-api <plugin>_api <service>-service gateway:
ENABLED_PLUGINS=sales
ENABLED_PLUGINS_ONLY_API=posclient
ENABLED_SERVICES=automations,logs
ENABLED_SERVICES entries are mapped to <name>-service Nx projects (automations → automations-service). Use base names with no _api/_ui/_service suffixes, and restart pnpm dev:apis after changing the list.
Production notes
CI publishes erxes/erxes-next-logs and erxes/erxes-next-automations images. Deploy a container per service you need with the same MONGO_URL, REDIS_*, JWT_TOKEN_SECRET, and DOMAIN environment as the APIs. A gateway plus Core API alone does not persist logs or execute automations. ENABLED_SERVICES is a development-startup variable; it does not create containers.
Segment worker concurrency is tunable per queue: SEGMENT_CONCURRENCY_CHANGED (10), SEGMENT_CONCURRENCY_FORGET (5), SEGMENT_CONCURRENCY_REBUILD (1), SEGMENT_CONCURRENCY_RECONCILE (3).