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(routehealth.dashboard): the panel dashboard, translated and failures first since 2.13./oh-dear-health-check-results: Spatie's Oh Dear endpoint, registered only whileOH_DEAR_HEALTH_CHECK_SECRETis set, serving the last run's results (HEALTH_OH_DEAR_FRESH_RESULTSoff).GET /api/health-check(Http\Controllers\Backend\HealthCheckController, bearerEVOLVE_HEALTH_CHECK_TOKEN): system information (PHP, Laravel, Evolve version, extensions, memory), project configuration, infrastructure status, environment details, thetrackingblock (type, emits, override, every public ID, secrets-set booleans, consent policy,sourcesfrom the settings overlay) and thecontentblock (last change, who, what,changes_30d,changes_90d,editors_90d, last login) read from the activity log'seventcolumn (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 ofevolve.modules, the core switchregionand every installed module provider (so package modules such as signage appear too). Each entry hasenabled(the switch),active(switched on and dependencies installed,ModuleServiceProvider::isActive()),sourceandmissing_packages(the suggested packages the module needs but the install lacks,ModuleServiceProvider::missingPackages(), empty for most modules).sourceisenv(anEVOLVE_MODULE_*variable),config(a key inevolve.modules, published or shipped) ordefault(nothing switches it, the module's own default applies).Support\ModuleSwitchesresolves the switches and records the source inevolve.module-sources, so it survives a config cache. A publishedmoduleslist replaces the shipped one, so a module missing from it reportsdefault.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) andscout24(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.