Skip to main content
Selora Homes Selora Homes
Recipe · v1.0.0 · Released Aug 28, 2026

Door or Window Left Open Alert

Installs a Home Assistant automation that watches your door and window sensors and pushes a notification to your phone when one is left open past a grace period while nobody is home. Add lights and it flashes the room, add a speaker and it announces the warning out loud.

An open door is only a problem once it is forgotten: the back door left after taking the bins out, a window opened to air a room and never shut. This recipe watches every door and window sensor you pick and, when one stays open longer than the grace period you set, pushes a notification to the Home Assistant app on your phone, naming what is open rather than sending a vague alert you have to go and chase down.

Ten minutes is a sensible default: long enough to carry something in and out without nagging, short enough to catch a door that never closed. Lower it for a tighter watch, raise it for a house where doors are held open a lot. It runs on your own sensors with no cloud in the loop.

Only when the house is empty

A door standing open while you are in the kitchen is not worth a notification. A door standing open after everyone has left is. Give the recipe a way to tell the difference and it holds the alert back until the house is empty.

There are two signals and you can use either, or both. Presence sensors cover the house room by room. Tracked phones cover the people: the Home Assistant app reports whether each phone is home, and no extra hardware is needed for it. The alert waits until every signal you picked reads clear.

The moment you leave counts as a trigger in its own right, not just the moment a door opens. Forgetting the back door usually means it was already open for a while before anyone walked out, so an alert that only fired on the door would check whether the house was empty while you were still standing in it, find that it was not, and never fire again. Leaving with something open tells you then and there.

A phone has to actually report itself as away for this to count. One that is switched off or has lost its location is treated as unknown rather than gone, and the recipe stays quiet instead of claiming an empty house it cannot vouch for.

Pick neither signal and the recipe behaves as it always has, alerting whenever an opening is left open past the grace period, whoever is in.

Flashing the lights and announcing out loud

A push notification is easy to miss if your phone is on silent or in another room. Pick one or more lights as the optional role and the recipe flashes them when a door is left open, a hard signal to miss when you are in the room. Pick one or more speakers and the warning is announced out loud over text to speech, which works without a paid Home Assistant Cloud subscription.

Leave both selections empty and the recipe stays notification-only. Add window sensors as the optional contact role and they are watched exactly like the doors.

Files Selora AI's pipeline reads to install this recipe. The manifest declares roles, inputs, and integrations; the package files are Jinja-templated YAML applied to Home Assistant.

# Door or Window Left Open Alert — notify when a door or window is left open
# past a grace period while nobody is home, with optional light-flash and spoken
# announcement.
#
# One required door role (multi-select) plus an optional window contact role,
# combined and watched the same way. Two optional occupancy signals gate the
# alert: room presence sensors and tracked phones. Either, both, or neither may
# be picked; with neither the alert fires whenever an opening is left open, as
# it did before the gate existed.
#
# The automation triggers both on an opening passing the grace period and on the
# last person leaving, because the case this recipe is really for is walking out
# and forgetting the back door. Alerting only on the opening edge would miss it
# whenever the door was already open before anyone left.
slug: door-window-left-open-alert
version: 1.0.0
title: Door or Window Left Open Alert
tagline: When a door or window is left open too long, get a notification that names it, and optionally flash the lights or hear it out loud.
description: >-
  Watches the door and window sensors you pick and fires when one is left open
  longer than the grace period while nobody is home. Sends a notification to
  your phone naming what is open, and optionally flashes lights and announces
  the warning over TTS.
author: Selora AI
released: "2026-08-28"
min_integration_version: "0.12.0"
tags: [security, doors, windows, alerts]
roles:
  # Door sensors — multi-select. binary_sensor + device_class door. min_count 1.
  - id: door_sensors
    title: Door sensors
    kind: binary_sensor
    device_class: door
    min_count: 1
    selection: required
    description: >-
      The door sensors to watch. Any one open past the grace period fires the
      alert.
  # Optional window contact sensors watched the same way. device_class window.
  - id: window_sensors
    title: Window sensors
    kind: binary_sensor
    device_class: window
    min_count: 0
    selection: required
    description: >-
      Optional. Window contact sensors watched the same way as the doors. Leave
      empty to watch doors only.
  # Optional room presence sensors. binary_sensor + occupancy. min_count 0.
  # occupancy is the right class here, unlike unlock-on-arrival: this role only
  # SUPPRESSES an alert, so a room sensor false-positive costs a missed
  # notification rather than an unlocked door.
  - id: presence_sensors
    title: Presence sensors
    kind: binary_sensor
    device_class: occupancy
    min_count: 0
    selection: required
    description: >-
      Optional. Room presence sensors. The alert is held back while any of them
      sees someone, so you are not told about a door you just walked through.
  # Optional tracked phones from the companion app. kind device_tracker.
  # min_count 0. Pairs with presence_sensors: pick either, both, or neither.
  - id: device_trackers
    title: Tracked phones
    kind: device_tracker
    min_count: 0
    selection: required
    description: >-
      Optional. Phones tracked by the Home Assistant app. The alert is held back
      while any of them is home. With no presence sensors or tracked phones
      picked, the alert fires whenever an opening is left open.
  # Optional announcement speakers over TTS. media_player. min_count 0.
  - id: announce_speakers
    title: Announcement speakers
    kind: media_player
    min_count: 0
    selection: required
    description: >-
      Optional. Speakers that announce the open door out loud over TTS in
      addition to the push notification. Leave empty to be notified on your
      phone only.
  # Optional lights to flash on the alert. kind light. min_count 0.
  - id: alert_lights
    title: Lights to flash
    kind: light
    min_count: 0
    selection: required
    description: >-
      Optional. Lights that flash when a door is left open, so the alert is
      visible in the room. Leave empty to be notified on your phone only.
inputs:
  # Grace period, in minutes, a door may stay open before the alert fires.
  - id: open_minutes
    type: number
    label: Grace period (minutes)
    description: >-
      How long a door may stay open before the alert fires.
    default: 10
    min: 0
    max: 240
  # TTS engine entity for the optional spoken announcement. Auto-resolved by the
  # tts_engine resolver from the home's configured tts.* entities, so the wizard
  # never shows this field. tts.speak works without a paid Home Assistant Cloud
  # subscription. The resolver returns an empty string when the home has no TTS
  # engine, and the template then skips the announcement. The default below is
  # only used by the offline recipe-validation gate.
  - id: tts_engine
    type: string
    label: Text-to-Speech engine
    description: >-
      Speech engine entity used for the spoken announcement. Resolved
      automatically from the Text-to-Speech engines configured in Home
      Assistant.
    resolver: tts_engine
    default: "tts.piper"
package_files:
  - package/automations/left_open.yaml.j2
{# Door or Window Left Open Alert: fire when a door or window has been open past
   the grace period and nobody is home.

   Two trigger edges, because either can be the moment the situation becomes
   true. An opening passing the grace period is one. The last person leaving is
   the other, and it is the one that matters most: the case this recipe is for
   is walking out and forgetting the back door, and by then the opening has
   usually been open for a while already. Triggering on the opening alone would
   have evaluated the occupancy gate while someone was still home, blocked, and
   never fired again.

   The condition is therefore written to be true of a state rather than of an
   edge: at least one opening open past the grace period, and every occupancy
   signal clear. That reads the same whichever trigger fired.

   Occupancy is two optional roles, presence sensors and tracked phones. Each
   contributes a state condition over its whole list only when it was picked, so
   picking either, both, or neither all render correctly. With neither the alert
   keeps its original behaviour and fires whenever an opening is left open.

   A tracked phone counts as away on any known state other than home, which
   includes named zones like Work. An unknown or unavailable tracker, a phone
   that is off or has lost GPS, does not count as away, so the recipe stays
   quiet rather than announcing an empty house it cannot vouch for.

   source is computed from the openings that are currently open rather than from
   trigger.to_state, which would name a phone on the departure edge.

   mode: queued so a second opening crossing the line close behind the first is
   handled rather than dropped. #}
{% set openings = roles.door_sensors + roles.window_sensors %}
{% set gated = roles.presence_sensors or roles.device_trackers %}
automation:
  - id: selora_recipe_{{ slug | replace('-', '_') }}_left_open
    alias: "Door or Window Left Open Alert"
    description: >-
      When a door or window is left open past the grace period and nobody is
      home, notify and, if configured, flash lights and announce it.
    mode: queued
    max: 10
    trigger:
{% for opening in openings %}
      - platform: state
        entity_id: {{ opening }}
        to: "on"
        for:
          minutes: {{ inputs.open_minutes }}
{% endfor %}
{% for sensor in roles.presence_sensors %}
      - platform: state
        entity_id: {{ sensor }}
        to: "off"
{% endfor %}
{% for tracker in roles.device_trackers %}
      # Leaving home, whatever they leave to. A phone that goes straight from
      # home to a named zone never passes through not_home, so keying on that
      # as the destination missed the departure edge entirely.
      - platform: state
        entity_id: {{ tracker }}
        from: "home"
{% endfor %}
    condition:
      - condition: or
        conditions:
{% for opening in openings %}
          - condition: state
            entity_id: {{ opening }}
            state: "on"
            for:
              minutes: {{ inputs.open_minutes }}
{% endfor %}
{% if roles.presence_sensors %}
      - condition: state
        state: "off"
        entity_id:
{% for sensor in roles.presence_sensors %}
          - {{ sensor }}
{% endfor %}
{% endif %}
{% if roles.device_trackers %}
      # Away is "not home", not the literal not_home state: a phone in a named
      # zone reads as that zone, so requiring not_home blocked every alert while
      # somebody sat at Work. unknown and unavailable are still rejected, so a
      # phone that is off or has lost GPS is never taken as evidence of an empty
      # house.
      - condition: template
        value_template: "{% raw %}{{ expand({% endraw %}{% for tracker in roles.device_trackers %}'{{ tracker }}'{{ ', ' if not loop.last else '' }}{% endfor %}{% raw %}) | selectattr('state', 'in', ['home', 'unknown', 'unavailable']) | list | count == 0 }}{% endraw %}"
{% endif %}
    action:
      - variables:
          # Only the openings actually past the grace period. Naming every
          # opening that happens to be on would report a window opened moments
          # ago, on purpose, as left open alongside the back door that genuinely
          # was. The cutoff mirrors the condition that let this run through.
          source: "{% raw %}{% set cutoff = now() - timedelta(minutes={% endraw %}{{ inputs.open_minutes }}{% raw %}) %}{{ expand({% endraw %}{% for opening in openings %}'{{ opening }}'{{ ', ' if not loop.last else '' }}{% endfor %}{% raw %}) | selectattr('state', 'eq', 'on') | selectattr('last_changed', 'lt', cutoff) | map(attribute='name') | list | join(', ') }}{% endraw %}"
      - service: notify.notify
        data:
{% if gated %}
          title: "{% raw %}Left open while you are out: {{ source }}{% endraw %}"
          message: "{% raw %}{{ source }} was left open and nobody is home. Check it before you get too far.{% endraw %}"
{% else %}
          title: "{% raw %}{{ source }} left open{% endraw %}"
          message: "{% raw %}{{ source }} has been open for a while. Check it is shut.{% endraw %}"
{% endif %}
{% if roles.alert_lights %}
      - service: light.turn_on
        target:
          entity_id:
{% for light in roles.alert_lights %}
            - {{ light }}
{% endfor %}
        data:
          flash: long
{% endif %}
{% if roles.announce_speakers and inputs.tts_engine %}
      - service: tts.speak
        target:
          entity_id: {{ inputs.tts_engine }}
        data:
          media_player_entity_id:
{% for player in roles.announce_speakers %}
            - {{ player }}
{% endfor %}
          message: "{% raw %}{{ source }} has been left open. Please check it.{% endraw %}"
{% endif %}

Type to search across

↑↓ navigate ↵ open
⌘K search