Critical Notifications: Escalation, SMS, Repeats, Acknowledgment, and Fallback

Critical Notifications: Escalation, SMS, Repeats, Acknowledgment, and Fallback

Module 18 · Lesson 3

Critical Notifications: Escalation, SMS, Repeats, Acknowledgment, and Fallback

In Lesson 2 you built the push channel and planned a second SMS channel. Now you'll assemble an actual alarm system out of them: an urgent push, a repeat, human acknowledgment, a local household response, and optional SMS for when the internet is down and you're not home.

This is a lesson about escalation. You're still not installing flood or smoke sensors. You're building a response template that Lessons 4 through 9 will hang specific events off of. That way, every security scenario calls the same escalation scripts instead of duplicating repeat-and-fallback logic everywhere.

Plan on about 90 to 105 minutes. By the end you'll have a working alarm test from the dashboard, an acknowledgment button, a local siren or lights, and, if you have the hardware, a working SMS pattern inside the escalation.

The rule for this lesson
One notification isn't enough for flooding, smoke, or a break-in.
Notify, repeat, escalate, give the house a local signal, and consider a second SMS channel.

The module's rule for critical events
For routine events, push is enough. For critical ones: push plus escalation plus a local response, plus optionally SMS. None of these channels is a guarantee on its own; together they cut the risk of an important alarm getting lost.

A real-life problem: the push went out, nobody responded

Picture flooding under the washing machine at 10:30 PM. You're away from home. The home internet went down after a storm. Home Assistant is running locally and detected the water, but the push to your phone never went out. The automation "did its job": the notify action had no way to reach you outside the house. You don't know about the failure. The water is still running. This isn't a sensor failure. It's a missing escalation, a missing second channel, and a missing local response.

The same scenario looks different when you have a siren in the house, local SMS through an LTE router or a GSM gateway, and, from Lesson 4, an automatically closing water shutoff valve. The system doesn't have to wait until you read a push. It can limit the damage and reach you through another route.

In Lesson 2 you broke a notification down into five stages. This lesson closes stages 4 and 5: a household member has to notice the alert and confirm they're responding. If they don't, the system doesn't go quiet after a single push.

What's actually the risk

One push and nothing more: the household member is asleep, out of range, or ignoring notifications.

No acknowledgment: you don't know whether someone's already checking the situation, or whether the alarm is just hanging open.

Phone only, zero local signal: with no internet, push doesn't arrive, but HA can still trigger a siren in the house.

Mindless repeat spam: dozens of identical alerts every minute kill trust in the whole system.

Push only, with no internet: you're away, the router died, the alert never goes out, and the sensor has already detected flooding.

Treating SMS as a guarantee: no GSM signal, an expired SIM, or a modem with no UPS behave exactly like unreliable push.

Confusing app delivery with a human response: confirmation: true tells you the phone got the push, not that someone drove home.

What you need from Lesson 2

This lesson assumes you already have working notify_household and notify_household_urgent scripts, a notification test helper, and a test button on the dashboard. If the test from Lesson 2 doesn't reach your phone, go back and fix the channel before building escalation on top of it.

For a local response, at least one of the following will come in handy. You don't need all of them. A siren, or even one light in the hallway, is enough.

Device Example entity Role
Zigbee / Wi-Fi siren switch.security_siren An audible alarm in the house with no push needed
A light light.hallway A visual signal when someone's home
Speaker / media player media_player.living_room A spoken text-to-speech announcement

Don't have a siren yet?
To start, one light set to 100% brightness and red works fine. In the YAML examples, swap the entities for your own from Developer Tools. If you don't have text-to-speech, remove that action from the local script.

Which entities we're watching

All the escalation logic rests on two helpers. One says whether an alarm is active. The other says whether someone has acknowledged it. Everything else just reacts to those states.

Entity Meaning
input_boolean.security_alarm_active The alarm is ongoing, escalation can keep going
input_boolean.security_alarm_acknowledged Someone has consciously confirmed they're responding
input_boolean.security_alarm_test The escalation-test button on the dashboard
input_boolean.alarm_sms_enabled Turns on the second SMS channel during escalation
binary_sensor.internet_available Whether the house has internet access (a ping test)

The escalation pattern

The pattern below is the target flow. We'll build it step by step in YAML. Important: the repeats happen every few minutes, not every few seconds.

1. Alarm starts: an urgent push (if the internet is up) plus a local siren / lights / TTS.

2. No internet: SMS goes out immediately through an LTE router or a local GSM gateway, if the SMS channel is enabled.

3. Wait 5 minutes: if nobody has acknowledged, send a repeat over whatever channel is available.

4. Repeat up to 3 times: push if the internet is working, or SMS if it isn't and the SMS channel is on.

5. Final escalation: an ESCALATION push plus SMS, if the alarm is still unacknowledged.

6. Acknowledgment: a household member taps I'M RESPONDING (or a helper): the repeats stop; closing the event is a separate decision.

Alarm helpers: separate event types

A single shared acknowledgment helper would mix flooding with smoke. That's why we split out at least three tracks: life, flooding, and intrusion. This is still a course-level simplification, not a professional alarm queue, but it doesn't hide concurrent threats from each other.

input_boolean:
  life_alarm_active:
    name: "Life alarm active"
    icon: mdi:fire-alert

  life_alarm_acknowledged:
    name: "Life alarm: I'm responding"
    icon: mdi:alarm-check

  flood_alarm_active:
    name: "Flood alarm active"
    icon: mdi:water-alert

  flood_alarm_acknowledged:
    name: "Flood alarm: I'm responding"
    icon: mdi:alarm-check

  intrusion_alarm_active:
    name: "Intrusion alarm active"
    icon: mdi:shield-alert

  intrusion_alarm_acknowledged:
    name: "Intrusion alarm: I'm responding"
    icon: mdi:alarm-check

  alarm_event_closed:
    name: "Close the event (cause checked)"
    icon: mdi:check-circle

  security_alarm_test:
    name: "Security alarm test"
    icon: mdi:alarm-bell

  alarm_sms_enabled:
    name: "Alarm SMS enabled"
    icon: mdi:message-alert
    initial: false

  alarm_sms_test:
    name: "Alarm SMS test"
    icon: mdi:message-alert

…_acknowledged means: "I saw it, I'm responding, stop the repeats." It does not mean the cause is gone. Clearing the alarm status is a separate decision, made through alarm_event_closed or a stop script once the cause has been checked.

The native alert integration has active / acknowledged / resolved states and repeats built in: it's worth considering later as an alternative to your own loops. To start, we're building transparent scripts.

The internet-availability sensor

A ping is a hint, not a gatekeeper for push. Cloudflare's 1.1.1.1 may not answer ICMP even with a working connection; Local Push at home can work without "internet on"; a brief unknown right after a restart shouldn't be allowed to stop the local response. Add the current Ping configuration through the UI (Settings → Devices & Services → Add Integration → Ping). The old-style YAML block binary_sensor: - platform: ping below is an illustration of the entity, not the only recommended method.

# Entity illustration only (prefer configuring Ping through the UI)
# binary_sensor.internet_available
# Default refresh is roughly 30s; also watch for packet loss.

The rule: always try push (continue_on_error: true). When the internet isn't a confident on, also try SMS. Once nobody has acknowledged, use every channel you have.

Script: the local household response

The local response starts before and independently of push. A notify failure must never stop the siren. For TTS: the target is a TTS entity, and you supply the player through media_player_entity_id. Limit siren runtime with a separate timer (say, 10 minutes) so it doesn't run forever.

script:
  security_alarm_local:
    alias: "Local alarm response"
    mode: parallel
    fields:
      tts_message:
        description: "A voice message matching the type of threat"
        default: "Attention. Security alarm at home."
    sequence:
      - action: switch.turn_on
        target:
          entity_id: switch.security_siren
      - action: light.turn_on
        target:
          entity_id: light.hallway
        data:
          brightness_pct: 100
          rgb_color: [255, 0, 0]
      - action: tts.speak
        continue_on_error: true
        target:
          entity_id: tts.piper
        data:
          media_player_entity_id: media_player.living_room
          message: "{{ tts_message }}"
      - delay:
          minutes: 10
      - action: switch.turn_off
        target:
          entity_id: switch.security_siren

TTS
Swap tts.piper for your own TTS entity. If you don't have TTS: remove the action. The siren and the light are enough on their own. The TTS message for smoke, CO, or gas has to be different from "check your phone": see Lesson 5.

Three independent escalation scripts

Instead of one mode: single script that rejects a second critical alarm, you have three scripts in mode: queued. Flooding doesn't block smoke. Smoke doesn't block an intrusion. The order inside each script: activate → local response → push (with continue_on_error) → optional SMS → repeats → final escalation.

script:
  life_alarm:
    alias: "Life alarm (smoke / CO / gas)"
    mode: queued
    max: 5
    fields:
      title:
        description: "Alarm title"
      message:
        description: "Alarm body"
    sequence:
      - action: input_boolean.turn_on
        target:
          entity_id: input_boolean.life_alarm_active
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.life_alarm_acknowledged

      - action: script.turn_on
        target:
          entity_id: script.security_alarm_local
        data:
          variables:
            tts_message: "Attention. Fire, carbon monoxide, or gas alarm. Leave the house following the plan."

      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "{{ title }}"
          message: "{{ message }}"

      - choose:
          - conditions:
              - condition: template
                value_template: >
                  {{ not is_state('binary_sensor.internet_available', 'on')
                     and is_state('input_boolean.alarm_sms_enabled', 'on') }}
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "HOME ALARM: {{ title }}"

      - repeat:
          count: 3
          sequence:
            - wait_template: "{{ is_state('input_boolean.life_alarm_acknowledged', 'on') }}"
              timeout: "00:05:00"
              continue_on_timeout: true
            - choose:
                - conditions:
                    - condition: state
                      entity_id: input_boolean.life_alarm_acknowledged
                      state: "on"
                  sequence:
                    - stop: "Alarm acknowledged"
                - conditions:
                    - condition: state
                      entity_id: input_boolean.life_alarm_active
                      state: "off"
                  sequence:
                    - stop: "Alarm canceled"
              default:
                - action: script.notify_household_urgent
                  continue_on_error: true
                  data:
                    title: "REPEAT: {{ title }}"
                    message: "{{ message }}: alarm not yet acknowledged."
                - choose:
                    - conditions:
                        - condition: template
                          value_template: >
                            {{ not is_state('binary_sensor.internet_available', 'on')
                               and is_state('input_boolean.alarm_sms_enabled', 'on') }}
                      sequence:
                        - action: script.alarm_sms
                          continue_on_error: true
                          data:
                            number: "+15550123456"
                            message: "REPEAT ALARM: {{ title }}"

      - condition: state
        entity_id: input_boolean.life_alarm_acknowledged
        state: "off"
      - condition: state
        entity_id: input_boolean.life_alarm_active
        state: "on"
      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "ESCALATION: {{ title }}"
          message: "{{ message }}: nobody has acknowledged."
      - choose:
          - conditions:
              - condition: state
                entity_id: input_boolean.alarm_sms_enabled
                state: "on"
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "ESCALATION ALARM: {{ title }}"
  flood_alarm:
    alias: "Flood alarm"
    mode: queued
    max: 5
    fields:
      title:
        description: "Alarm title"
      message:
        description: "Alarm body"
    sequence:
      - action: input_boolean.turn_on
        target:
          entity_id: input_boolean.flood_alarm_active
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.flood_alarm_acknowledged

      - action: script.turn_on
        target:
          entity_id: script.security_alarm_local
        data:
          variables:
            tts_message: "Attention. Flooding detected. Check the water and the shutoff valve."

      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "{{ title }}"
          message: "{{ message }}"

      - choose:
          - conditions:
              - condition: template
                value_template: >
                  {{ not is_state('binary_sensor.internet_available', 'on')
                     and is_state('input_boolean.alarm_sms_enabled', 'on') }}
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "HOME ALARM: {{ title }}"

      - repeat:
          count: 3
          sequence:
            - wait_template: "{{ is_state('input_boolean.flood_alarm_acknowledged', 'on') }}"
              timeout: "00:05:00"
              continue_on_timeout: true
            - choose:
                - conditions:
                    - condition: state
                      entity_id: input_boolean.flood_alarm_acknowledged
                      state: "on"
                  sequence:
                    - stop: "Alarm acknowledged"
                - conditions:
                    - condition: state
                      entity_id: input_boolean.flood_alarm_active
                      state: "off"
                  sequence:
                    - stop: "Alarm canceled"
              default:
                - action: script.notify_household_urgent
                  continue_on_error: true
                  data:
                    title: "REPEAT: {{ title }}"
                    message: "{{ message }}: alarm not yet acknowledged."
                - choose:
                    - conditions:
                        - condition: template
                          value_template: >
                            {{ not is_state('binary_sensor.internet_available', 'on')
                               and is_state('input_boolean.alarm_sms_enabled', 'on') }}
                      sequence:
                        - action: script.alarm_sms
                          continue_on_error: true
                          data:
                            number: "+15550123456"
                            message: "REPEAT ALARM: {{ title }}"

      - condition: state
        entity_id: input_boolean.flood_alarm_acknowledged
        state: "off"
      - condition: state
        entity_id: input_boolean.flood_alarm_active
        state: "on"
      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "ESCALATION: {{ title }}"
          message: "{{ message }}: nobody has acknowledged."
      - choose:
          - conditions:
              - condition: state
                entity_id: input_boolean.alarm_sms_enabled
                state: "on"
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "ESCALATION ALARM: {{ title }}"
  intrusion_alarm:
    alias: "Intrusion alarm"
    mode: queued
    max: 5
    fields:
      title:
        description: "Alarm title"
      message:
        description: "Alarm body"
    sequence:
      - action: input_boolean.turn_on
        target:
          entity_id: input_boolean.intrusion_alarm_active
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.intrusion_alarm_acknowledged

      - action: script.turn_on
        target:
          entity_id: script.security_alarm_local
        data:
          variables:
            tts_message: "Attention. Intrusion alarm. Check the entry points per the plan."

      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "{{ title }}"
          message: "{{ message }}"

      - choose:
          - conditions:
              - condition: template
                value_template: >
                  {{ not is_state('binary_sensor.internet_available', 'on')
                     and is_state('input_boolean.alarm_sms_enabled', 'on') }}
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "HOME ALARM: {{ title }}"

      - repeat:
          count: 3
          sequence:
            - wait_template: "{{ is_state('input_boolean.intrusion_alarm_acknowledged', 'on') }}"
              timeout: "00:05:00"
              continue_on_timeout: true
            - choose:
                - conditions:
                    - condition: state
                      entity_id: input_boolean.intrusion_alarm_acknowledged
                      state: "on"
                  sequence:
                    - stop: "Alarm acknowledged"
                - conditions:
                    - condition: state
                      entity_id: input_boolean.intrusion_alarm_active
                      state: "off"
                  sequence:
                    - stop: "Alarm canceled"
              default:
                - action: script.notify_household_urgent
                  continue_on_error: true
                  data:
                    title: "REPEAT: {{ title }}"
                    message: "{{ message }}: alarm not yet acknowledged."
                - choose:
                    - conditions:
                        - condition: template
                          value_template: >
                            {{ not is_state('binary_sensor.internet_available', 'on')
                               and is_state('input_boolean.alarm_sms_enabled', 'on') }}
                      sequence:
                        - action: script.alarm_sms
                          continue_on_error: true
                          data:
                            number: "+15550123456"
                            message: "REPEAT ALARM: {{ title }}"

      - condition: state
        entity_id: input_boolean.intrusion_alarm_acknowledged
        state: "off"
      - condition: state
        entity_id: input_boolean.intrusion_alarm_active
        state: "on"
      - action: script.notify_household_urgent
        continue_on_error: true
        data:
          title: "ESCALATION: {{ title }}"
          message: "{{ message }}: nobody has acknowledged."
      - choose:
          - conditions:
              - condition: state
                entity_id: input_boolean.alarm_sms_enabled
                state: "on"
            sequence:
              - action: script.alarm_sms
                continue_on_error: true
                data:
                  number: "+15550123456"
                  message: "ESCALATION ALARM: {{ title }}"

In Lessons 4 through 6 and 9 you'll call the right script directly. The old name script.security_alarm is no longer the only one: treat it as legacy. For tests, use script.turn_on, so the automation doesn't sit and wait 15 minutes for the escalation to finish.

script.turn_on versus calling it directly
action: script.flood_alarm waits for the whole sequence to finish (including three × 5-minute waits). That's why, for tests and whenever you want the helper reset right away, use action: script.turn_on with variables.

Buttons in the notification (recommended)

A household member shouldn't have to go hunting for a helper on the dashboard. The Companion App supports actionable notifications with buttons. The helper on the panel stays as your Plan B.

# Example of a critical push with buttons (Android + iOS)
# Use the device-specific action or notify data per the Companion documentation
action: notify.mobile_app_marek_phone
continue_on_error: true
data:
  title: "{{ title }}"
  message: "{{ message }}"
  data:
    actions:
      - action: "ALARM_ACK"
        title: "I'M RESPONDING"
      - action: "URI"
        title: "OPEN DASHBOARD"
        uri: "/lovelace/security"
    # Android: channel / importance; iOS: push.sound critical: per Companion docs
automation:
  - alias: "Security: I'M RESPONDING button in a notification"
    mode: queued
    triggers:
      - trigger: event
        event_type: mobile_app_notification_action
        event_data:
          action: "ALARM_ACK"
    actions:
      - choose:
          - conditions:
              - condition: state
                entity_id: input_boolean.life_alarm_active
                state: "on"
            sequence:
              - action: input_boolean.turn_on
                target:
                  entity_id: input_boolean.life_alarm_acknowledged
          - conditions:
              - condition: state
                entity_id: input_boolean.flood_alarm_active
                state: "on"
            sequence:
              - action: input_boolean.turn_on
                target:
                  entity_id: input_boolean.flood_alarm_acknowledged
        default:
          - action: input_boolean.turn_on
            target:
              entity_id: input_boolean.intrusion_alarm_acknowledged

A second alarm channel: SMS

Push from the Home Assistant app is fast and convenient, but it shouldn't be the only channel for critical alarms. SMS can go out through an LTE router with an HA integration, a local GSM gateway, or an external API. For real emergency use, a local setup makes the most sense: an LTE router or GSM gateway with its own SIM card, independent of the home internet.

SMS can fail too
No GSM signal, no prepaid balance, an expired SIM, a modem stuck after months without a test, no power to HA or the router, a message that's too long, or an internet-based SMS gateway during a connectivity outage. SMS reduces the risk; it doesn't eliminate it.

Option A: an LTE router with an HA integration

If you have a compatible Huawei LTE or Netgear LTE router, the integration can create a notify.* service. Check the exact name in Developer Tools → Actions. Keep alarm SMS short: no emoji, no long sentences, and ideally plain ASCII. Netgear LTE has message-length limits.

action: notify.huawei_lte
data:
  message: "HOME ALARM: flooding detected under the washer."

Option B: a local SMS gateway over REST (recommended pattern)

The GSM modem and Gammu live outside the main Home Assistant instance, on a separate device, add-on, or container running an HTTP gateway (for example sms-gammu-gateway). HA only calls the local REST API through rest_command. That way, swapping the modem doesn't require rewriting your security automations.

The URL, method, and payload depend on your own gateway. The example below assumes a local API that accepts JSON with number and message fields. Adapt it to your device's documentation.

rest_command:
  sms_gateway_send:
    url: "http://192.168.1.80:5000/api/sms"
    method: post
    content_type: "application/json"
    username: !secret sms_gateway_user
    password: !secret sms_gateway_password
    authentication: basic
    payload: >
      {
        "number": "{{ number }}",
        "message": "{{ message }}"
      }

secrets.yaml

sms_gateway_user: admin
sms_gateway_password: a_very_strong_password
script:
  alarm_sms:
    alias: "Alarm SMS"
    mode: queued
    fields:
      number:
        description: "Recipient's phone number, in +1... format"
      message:
        description: "Short SMS body"
    sequence:
      - action: rest_command.sms_gateway_send
        data:
          number: "{{ number }}"
          message: "{{ message }}"

Watch out for HiLink-style USB modems
Many 4G dongles act as a network adapter or USB router rather than a classic AT modem exposed as a serial port. Gammu generally needs a modem in AT mode. Check your modem's documentation before buying.

An SMS test from the dashboard

automation:
  - alias: "Security: alarm SMS test"
    description: "Sends a test SMS through the GSM gateway"
    mode: single
    triggers:
      - trigger: state
        entity_id: input_boolean.alarm_sms_test
        to: "on"
    actions:
      - action: script.alarm_sms
        data:
          number: "+15550123456"
          message: "HA TEST: the SMS channel is working."
      - delay:
          seconds: 2
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.alarm_sms_test

If you're using an LTE router instead of a REST gateway, swap rest_command in the alarm_sms script for a notify.huawei_lte or notify.netgear_lte action. What matters is that the whole escalation calls one single script, not three different places holding phone numbers.

Stop / acknowledge / close the event

script:
  security_alarm_stop:
    alias: "Stop the local response (siren / lights)"
    mode: single
    sequence:
      - action: switch.turn_off
        target:
          entity_id: switch.security_siren
      - action: light.turn_off
        target:
          entity_id: light.hallway

automation:
  - alias: "Security: acknowledging life alarm stops the repeats"
    mode: queued
    triggers:
      - trigger: state
        entity_id: input_boolean.life_alarm_acknowledged
        to: "on"
    actions:
      - action: script.security_alarm_stop
      # life_alarm_active does NOT have to clear here: closing the event is a separate, deliberate decision

  - alias: "Security: flood alarm acknowledged"
    mode: queued
    triggers:
      - trigger: state
        entity_id: input_boolean.flood_alarm_acknowledged
        to: "on"
    actions:
      - action: script.security_alarm_stop

  - alias: "Security: intrusion alarm acknowledged"
    mode: queued
    triggers:
      - trigger: state
        entity_id: input_boolean.intrusion_alarm_acknowledged
        to: "on"
    actions:
      - action: script.security_alarm_stop

  - alias: "Security: closing the event"
    mode: single
    triggers:
      - trigger: state
        entity_id: input_boolean.alarm_event_closed
        to: "on"
    actions:
      - action: input_boolean.turn_off
        target:
          entity_id:
            - input_boolean.life_alarm_active
            - input_boolean.flood_alarm_active
            - input_boolean.intrusion_alarm_active
            - input_boolean.alarm_event_closed
      - action: script.security_alarm_stop

Acknowledging delivery is not the same as closing the event. Don't mix up these two decisions.

Automation: an alarm test from the dashboard

automation:
  - alias: "Security: alarm test from the dashboard"
    description: "Runs a full escalation test without blocking the helper reset"
    mode: single
    triggers:
      - trigger: state
        entity_id: input_boolean.security_alarm_test
        to: "on"
    actions:
      - action: script.turn_on
        target:
          entity_id: script.intrusion_alarm
        data:
          variables:
            title: "TEST: security alarm"
            message: "This is a test of the full alarm escalation."
      - delay:
          seconds: 2
      - action: input_boolean.turn_off
        target:
          entity_id: input_boolean.security_alarm_test

The test is a real escalation
Push, SMS (if enabled), and the siren all fire. Test during the day, and give the household a heads-up. The helper turns itself off after 2 seconds thanks to script.turn_on.

Critical scenario: flooding with no internet

You're away, the internet is down, and a sensor has detected water
If you only had push, you might never find out. That's why, for flooding, we don't rely solely on a notification. The system should trigger a siren and lights locally, and send SMS as a second channel through an LTE router or a local GSM gateway. In Lesson 4 you'll add automatically closing a water shutoff valve: that's the first response that actually limits damage, and it matters more than the push itself.

The target architecture for flooding looks like this: sensor → HA locally → water shutoff valve (Lesson 4) → siren and lights → push (if the internet is up) → SMS (if the internet is down or there's no acknowledgment). Without the shutoff valve, the system only informs you. With it, it can limit the damage even without you at home.

A silenced phone, no internet, and a UPS

Push depends on your phone, your carrier, and Apple's or Google's services. SMS depends on GSM signal and the modem. A local siren, light, and TTS work on the home network even when the internet is down, as long as Home Assistant and the devices have power.

A UPS matters
If you expect a response during an internet or power outage, put Home Assistant, whatever router or switch you need for communication, the Zigbee coordinator, and the LTE router or GSM gateway all on backup power. Otherwise, a sensor might detect water, but the system will have no way to close the valve or send an SMS.

Scenario Push SMS Local response
You're home, internet's up Should arrive Optional, during escalation Siren and lights as a bonus
You're away, internet's down Probably won't arrive May get through via GSM/LTE Siren alerts anyone home
You're away, phone's on silent May go unnoticed SMS may be noticed later Won't help you while you're away
Power's out at home HA doesn't run without a UPS The modem doesn't either, without a UPS Doesn't work without power either

This setup doesn't guarantee you'll always get the message. It cuts the risk, because it responds locally, doesn't rely solely on push, has a second SMS channel, and requires acknowledgment. For threats to life, a certified detector with its own siren still matters most, not just Home Assistant.

A dashboard card: alarm and acknowledgment

Add an alarm section alongside the notifications card from Lesson 2. The acknowledgment button should be visible whenever an alarm is active.

type: entities
title: Security alarm
entities:
  - input_boolean.security_alarm_test
  - input_boolean.security_alarm_active
  - input_boolean.security_alarm_acknowledged
  - input_boolean.alarm_sms_enabled
  - input_boolean.alarm_sms_test
  - binary_sensor.internet_available
  - script.security_alarm
  - script.security_alarm_stop
  - script.alarm_sms

In the mobile app, you can also add a shortcut to the acknowledgment helper, or use a view with one large button. What matters is that acknowledging is faster than hunting for an automation in settings.

How to test, step by step

1. Let the household know you're about to test the alarm.

2. Turn on security_alarm_test on the dashboard.

3. Check the urgent push, the siren, and the lights. intrusion_alarm_active should be on.

4. Within 5 minutes, turn on intrusion_alarm_acknowledged. The alarm should stop.

5. Repeat the test, but this time don't acknowledge right away. Wait for the push with the REPEAT prefix.

6. If you have an SMS gateway, turn on alarm_sms_test and check that the SMS reaches your phone.

7. Safely simulate losing the internet: unplug the WAN cable on the router or turn off Wi-Fi on a test phone, don't power off the entire router or the switch you use to reach Home Assistant. Run the alarm test and check that the escalation falls back to SMS instead of push.

8. Finally, run script.security_alarm_stop manually if anything got left running.

Common mistakes

Escalating every minute: the household will disable HA notifications permanently.

No single mode on the alarm script: two events launch two parallel escalations.

Acknowledgment doesn't silence the siren: the test ends, but the house keeps shouting.

Confusing push delivery with acknowledgment: an app event isn't the same as the acknowledgment helper.

Push only, no local response: with the router offline, you have no signal in the house.

Testing the alarm at night with no warning: you wake the house with a false production alarm.

SMS never tested: the SIM card expired, and you find out during a real flood.

An internet-based SMS gateway as your only backup channel: it won't help during a connectivity outage.

What not to do

Don't build escalation on top of a notify setup from Lesson 2 that isn't already working.

Don't use the alarm test as your weekly reminder: it's a full escalation, not a routine ping.

Don't duplicate the repeat logic in every sensor automation: keep it inside the alarm scripts.

Don't promise yourself that acknowledgment in HA replaces a physical response: the helper only means someone tapped "I'm responding."

Don't promise yourself SMS will always arrive: it's a second channel, not a guarantee.

Assignment: the alarm escalation system

To do

☐ Add the security_alarm_active, security_alarm_acknowledged, and security_alarm_test helpers.

☐ Create the security_alarm_local script with your own siren, light, or TTS entities.

☐ Create the escalation scripts with repeats every 5 minutes for life, flood, and intrusion alarms.

☐ Create the security_alarm_stop script and the acknowledgment automations.

☐ Add the alarm-test automation triggered from the dashboard.

☐ Place the alarm card on the security dashboard next to the notifications section.

☐ Test the scenario where you acknowledge within 5 minutes.

☐ Test the scenario with no acknowledgment and check for the push with the REPEAT prefix.

☐ If you have an LTE router or a GSM gateway: configure script.alarm_sms and test SMS from the dashboard.

☐ Safely simulate losing the internet (for example, unplug the WAN, not the whole router) and check that escalation falls back to SMS.

☐ Note down: who gets the push, who gets the SMS, and who acknowledges the alarm.

☐ Write down whether your GSM modem / LTE router is on a UPS.

Key takeaways

One notification isn't enough for flooding, smoke, or a break-in.

The escalation scripts centralize this pattern for the whole module.

Acknowledgment is a human decision, not just the phone receiving a push.

A local siren and lights still work when push fails inside the house.

SMS is a second channel, not a replacement for push or a delivery guarantee.

With no internet, the local response and SMS matter more than push on its own.

Run the alarm test and the SMS test deliberately, every so often, not every day.

What's next

In Lesson 4 you'll wire up the first real scenario: flood sensors in the kitchen, bathroom, near the washing machine, and near the dishwasher. The flooding automation will call the flood alarm script and, if you have a valve, close the water supply locally before push or SMS ever reach you away from home.

In Lesson 10 we'll come back to SMS and push during internet outages, power failures, and backup-channel tests. If your alarm test ends with an acknowledgment and the siren going quiet, you have the module's response engine in place.

Finished this lesson?