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

ServiceNx projectPortRegisters in RedisQueues read
Logslogs-service (backend/services/logs)3301erxes-service-logslogs-put_log, logs-activity_log, logs-segment-{changed,forget,rebuild,reconcile}
Automationsautomations-service (backend/services/automations)3302erxes-service-automationsautomations-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 by logHandler, which wraps plugin apiHandlers and log-enabled code paths. logHandler first checks checkServiceRunning('logs') (the erxes-service-logs key); 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 through SEGMENT_QUEUES in core-modules/segments.
  • sendWorkerQueue('automations', 'trigger'): automation trigger dispatch from sendAutomationTrigger in core-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: startAutomations writes the config into erxesservice:config:<plugin>.meta.automations and mounts typed tRPC producers on the plugin's /trpc router (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: registers afterMutation, afterAuth, afterApiRequest, afterDocumentUpdated, and afterDocumentCreated producers the same way.
  • meta.logs: declares log content types so event and activity entries are labeled correctly.
  • meta.segments: initSegmentProducers registers 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:

  • activityLogs and users live in the tenant database (same as the app DB).
  • logs documents go to a separate database named <tenant-db>_logs via db.useDb(...) in connectionResolvers.ts; for MONGO_URL=.../erxes, that is erxes_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).

Was this helpful?