Issue 4: an SMTP add-on for e-mail notifications.
- SmtpSetup modal: server (host/port/encryption/user/pass), from/to, and
individual toggles for the events you want mailed — device offline, low
battery, automation messages. "Send test e-mail" button. Config stored
encrypted on the addon row; a runtime mailer is built from it (no .env edits).
- NotificationService: notify(event, …) sends only if SMTP is set up AND that
event is enabled; send() for the test.
- Triggers: automation "notify" actions e-mail (when enabled);
notifications:sweep (every 5 min) e-mails on NEW device-offline / low-battery
with per-condition dedup + battery hysteresis, so it's one mail per event.
6 tests (gating, per-event toggle, config persist + password-keep, sweep dedup).
Suite 82 green, 12/12 tabs clean; card + modal browser-verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex R15 on the Ring bridge flagged two issues:
- [P1] Concurrent first-sight messages (retained info + motion on bridge
startup) could create duplicate devices — no uniqueness on ring_id. Added a
partial unique index on (config->>'ring_id'); resolveDevice now creates and,
on the unique violation, re-fetches the winner (race-safe).
- [P2] The install-gate cache was never invalidated, so ingest was dropped for
up to 15s after install and devices kept being created for up to 15s after
uninstall. AddonService now forgets the shared cache key on install/uninstall.
+3 tests (unique-index guard, repeated-messages-reuse-device, cache
invalidation). Suite 43 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First half of the Ring integration (handoff §12: ring-mqtt, cloud token auth):
- Addons registry (static catalogue) + Addon model with encrypted config, so
the Ring refresh token / credentials are never stored in plaintext.
- AddonService: install / uninstall (wipes secrets) / saveConnection (blank
password keeps the stored one) / markStatus (from the bridge over MQTT).
- Addons page (new sidebar tab) with per-addon install, Cloud badge, live
status pill, and a Ring connect modal capturing email/password/2FA — we
never call Ring ourselves; creds are handed to the ring-mqtt sidecar.
- Uninstall behind a wire-elements confirm (R5). Fixed the confirm target:
Livewire names App\Livewire\Addons\Index as "addons" (Index suffix dropped),
and only $wire.$dispatch (not Alpine $dispatch) reaches the modal listener.
- DE/EN localization (R16); doorbell/package/cloud icons.
7 AddonTest cases (registry, install, encryption-at-rest, blank-password
keep, uninstall wipe, status guard). Suite 33 green; 12/12 tabs clean; full
install→connect→connecting→uninstall UI flow browser-verified, 0 console errors.
Backend bridge (ring-mqtt container) + Ring MQTT normalizer land next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>