Skip to content

Health system internals ​

Evolve's health checks run on spatie/laravel-health, feed the /evolve/health dashboard and the Oh Dear endpoint, and the api/health-check endpoint reports configuration and editorial activity to Fleet. The user side is on Health Checks and Oh Dear.

Registration ​

EvolveServiceProvider::registerHealthChecks() registers each check behind its toggle in health.evolve_health_check.enabled_checks (core/config/health.php, merged, deliberately not publishable). Check classes live in core/src/Health/Checks/. Checks that belong to a module live in that module and register in its bootModule() with Health::checks([...]), which appends to the list: the email directory check (email), the Shopify API check (shopify/src/Health, toggle shopify_api) and the Scout24 API check (scout24/src/Health, toggle scout24_api). The two API checks moved out of the core in 2.21; Racerfish\Evolve\Health\Checks\External\* remain as deprecated subclasses with the same check names until 3.0. A disabled module never boots, so its checks do not exist even with the toggle on. Spatie refuses two checks with the same name, so a project must not register a module check a second time. Core checks run every minute (database, cache, disk space, optimized app, debug mode, environment consistency, canonical host, tracking, shadowed config, API keys, timezone, module switches, schedule, queue when a real driver is configured); the mailer check and the Shopify and Scout24 API checks run hourly and store skipped until due. treat_skipped_as_failure is false.

Running ​

schedule() in the provider schedules health:check, health:schedule-check-heartbeat and, when queueCheckEnabled(), health:queue-check-heartbeat every minute, and prunes results daily after HEALTH_KEEP_HISTORY_DAYS. Results go to the Eloquent store (health_check_result_history_items, optionally on HEALTH_DB_CONNECTION). evolve:health-purge cleans by hand; the dashboard's Run button executes health:check --no-notification.

Endpoints ​

  • /evolve/health (route health.dashboard): the panel dashboard, translated and failures first since 2.13.

  • /oh-dear-health-check-results: Spatie's Oh Dear endpoint, registered only while OH_DEAR_HEALTH_CHECK_SECRET is set, serving the last run's results (HEALTH_OH_DEAR_FRESH_RESULTS off).

  • GET /api/health-check (Http\Controllers\Backend\HealthCheckController, bearer EVOLVE_HEALTH_CHECK_TOKEN): system information (PHP, Laravel, Evolve version, extensions, memory), project configuration, infrastructure status, environment details, the tracking block (type, emits, override, every public ID, secrets-set booleans, consent policy, sources from the settings overlay) and the content block (last change, who, what, changes_30d, changes_90d, editors_90d, last login) read from the activity log's event column (2.9.1). The block always ships, with nulls when there is no data.

    Since 2.21 two more blocks serve the Fleet check before modules and drivers leave the core in 3.0:

    • modules: one entry per module, from the keys of evolve.modules, the core switch region and every installed module provider (so package modules such as signage appear too). Each entry has enabled (the switch), active (switched on and dependencies installed, ModuleServiceProvider::isActive()), source and missing_packages (the suggested packages the module needs but the install lacks, ModuleServiceProvider::missingPackages(), empty for most modules). source is env (an EVOLVE_MODULE_* variable), config (a key in evolve.modules, published or shipped) or default (nothing switches it, the module's own default applies). Support\ModuleSwitches resolves the switches and records the source in evolve.module-sources, so it survives a config cache. A published modules list replaces the shipped one, so a module missing from it reports default.
    • drivers: video (driver, Vimeo token set), llm (default provider, OpenAI key set), email (driver, Brevo key set), shopify (number of stores, null without the table) and scout24 (credentials set). Secrets only report whether they are set.

Fleet, the agency inventory, polls api/health-check for every instance and also probes /mcp/evolve.

Notifications ​

With HEALTH_NOTIFICATIONS_ENABLED, failures go through the mail channel only, only_on_failure, throttled to one per hour. Since 2.18 Health\AlertNotifiable is bound in place of Spatie\Health\Notifications\Notifiable and routes to AlertNotifiable::address(): evolve.mail.alert-address (the MailSettings overlay, env fallback HEALTH_WEBMASTER_ADDRESS), then health.notifications.mail.to. The mailer check probes the transport on every run and sends one real test mail a day to HEALTH_CHECK_EMAIL_RECIPIENT (fallback the same alert address, never the from address); its meta carries from_address, notification_address, alert_address and a *_source for each. Inbox placement is not verified (2.16.1).

Adding a check ​

Subclass Spatie\Health\Checks\Check in core/src/Health/Checks/, return Result::ok(), warning() or failed(), register it in registerHealthChecks() behind a new toggle, and add the toggle to SettingsManager::BOOLEANS so the Health tab offers it. Two rules: pass a message only to warning() and failed() (a message on ok() becomes a notification text), and give expensive checks a cadence (->hourly()) so they never block the every-minute run. Tests: core/tests/Unit/MailerHealthCheckTest.php, core/tests/Feature/HealthCheckRegistrationTest.php.