RFC 0009: Notification IDs and Linking to Home Assistant¶
Created: 2026-10-17 Author: Jey Burrows Status: draft
Summary¶
Each notification has an id, a uuid1, used for its archive file, its media files and the logbook event. It does that job, but people can't use it. It's too long to read out or type, it looks like noise, and nothing in Home Assistant's Activity or History views refers to it.
This RFC sets out what the id should do. It should let a person get from a row in the Activity or History view to the archived notification, and from the archive back to the automation run, logbook and history that go with it. It compares the ways of doing that and proposes separating the id people use from the id the code uses, because the two have different constraints.
Goals¶
- From a row in the Activity (logbook) view, or the History view, find the archived notification it belongs to
- From an archived notification, get to the automation or script run that sent it, and the logbook and history around it
- Keep the full JSON out of the Home Assistant database. The recorder only gets small events and entity states
- Work on an install where nobody has configured anything: no extra integrations, settings or YAML
- Give people a reference short enough to read out, type, or quote in an issue, such as "17 Oct #7"
- Keep the technical id unique, never reused, sortable by time, and safe in filenames and MQTT topics
Current State¶
| Where | What it holds now |
|---|---|
Notification.id | str(uuid.uuid1()), e.g. 642c73fc-ca23-11f1-97c3-a203a37618e8 |
| Archive file | <local time to the minute>_<id>.json, e.g. 2026-10-17T13-07_642c73fc-….json. _filename_period() parses the first 16 characters to skip files outside a time range |
| MQTT archive | <topic>/<same base filename> |
| Media | <id>.<ext> and <id>_<key>.<ext> |
Envelope.id (<notification id>_<delivery>) as the idstring of make_msgid(), which adds its own time, random part and domain | |
| Logbook | supernotify_notification event with notification_id, a 100 character summary, outcome and delivery names, fired with the notification's context |
| Recipient notify entities | notify.recipient_<name> state is set once per notification for each recipient reached, with the notification's context, so each delivery to a person is in the History and Activity views |
| Archive linkage | original_context (context id, parent_id, user_id, and the resolved user). From v2.15.1, original_trace (trace key and run_id) |
| Lookup | ArchiveDirectory.read_entry() returns the first file whose name contains the given string, used by the archive enquiry action |
What Home Assistant Allows¶
Checked against HA 2026.10.1 and 2026.10.2, its frontend, and a test container.
Activity view¶
- A logbook entry has only
name,message,entity_id,icon,domain,source,state,whenand thecontext_*fields. There's no URL field - The message is plain text. The frontend swaps the first word that is an entity id for that entity's friendly name, but doesn't make it a link
- A row is linked through its context: "triggered by automation X" opens that automation's trace, and an entry with an
entity_idopens that entity's more-info dialog - The URL
/logbook?entity_id=…&start_date=…&end_date=…filters by entity and time only. Thelogbook/get_eventswebsocket command also takes acontext_id
So a row can't link to one particular notification. It can show a reference as text, and it already shares a context with the archive entry.
History view¶
/history?entity_id=…&start_date=…&end_date=…shows the state of entities over a time range- Each delivery to a recipient is a state change of their
notify.recipient_<name>entity, with the notification's context
Traces¶
- While an automation or script runs,
homeassistant.helpers.trace.trace_id_get()returns("<domain>.<config id>", run_id). The context var reaches thesupernotify.notifyandnotify.supernotifyhandlers - It's set on every install, since
automationandscriptboth depend ontrace - It's missing for calls from the developer tools, the REST API, Assist, or another integration's own task
- An automation without an
idgetsautomation.Noneand no stored trace - Only the last 5 runs of each automation or script are kept, by default
- Context vars are copied into tasks and event listeners, and HA never resets the trace id when a run ends. An integration reacting to an event an automation fired inherits that automation's trace id
trace/getreturns the run'scontext, which can be checked againstoriginal_context.trace/listdoesn't include it.hass.data[DATA_TRACE]is internal, and its layout changed between 2026.10.1 and 2026.10.2- One automation run can send several notifications. On the test container, two
supernotify.notifycalls in one run were archived with the samerun_idand the same context id
Linking keys¶
The context id is already the key shared by the logbook event, the recipient entity state changes, the automation trace and the archive entry. It links one run to many notifications, not to just one, but it's the one key that every view already has.
Options for the Technical Id¶
| Option | Unique | Sortable | Human friendly | Notes |
|---|---|---|---|---|
| A. Keep uuid1 | Yes | No | No | No change. Its first 8 characters are the low bits of the time, which repeat about every 7 minutes, so a short prefix isn't a safe reference |
B. Local timestamp to the second + in-memory counter, e.g. 2026-10-17T13-07-57_3 | Yes, if the counter never resets while running | Yes | Partly | A restart can't happen within one second. The hour repeated when the clocks go back only clashes if the second and the count both repeat |
| C. Timestamp + persisted counter that never resets | Yes | Yes | No, eventually | At 500 a day, the counter passes 100,000 in under a year |
| D. Timestamp + persisted daily counter | Yes, with the timestamp | Yes | Yes, as date + number | Needs a Store file. A crash or backup restore can reuse a number on the same day |
E. Trace run_id | No | No | No | Shared by every notification in a run, missing for calls from outside an automation or script, and 32 hex characters. Useful for linking, already kept as original_trace |
| F. HA context id (a ULID) | No | Yes | No | Shared by every notification in a run, like the run_id. Useful for linking, already kept as original_context |
G. ULID (homeassistant.util.ulid) or uuid.uuid7() | Yes | Yes | No | Standard, time-ordered and unique without any state. A ULID matches HA's own context ids. Python 3.14, which Supernotify requires, has uuid7() |
E and F are good keys for linking, but they can't identify one notification. B, C and D try to make one value do two jobs, which is where their weaknesses come from.
Proposal: Separate the Human Reference from the Technical Id¶
The two have different constraints:
| Technical id | Human reference | |
|---|---|---|
| Used by | Filenames, MQTT topics, media, events, code | People reading the Activity view, a notification, an issue or the archive viewer |
| Must be | Unique, never reused, safe in filenames | Short, easy to read out and type |
| Unique within | Everything, forever | One day, and only in practice |
| Survives a crash or restore | Yes, with no stored state | A rare repeat is acceptable |
| Format can change | Rarely, it ends up in stored files | Freely, it's only for display |
Technical id¶
Option G: a ULID or uuid7. It's unique without stored state, sorts by time, and needs no counter. A ULID is the same kind of id as HA's context ids, so both read alike in the archive. Archive filenames keep the local time prefix, so _filename_period() and file listings are unchanged.
Option A, keeping uuid1, is also acceptable. Once the human reference is separate, the main reason left to change the technical id is sorting.
Human reference¶
Option D's daily counter, used only as the reference: ref: 7, shown with the date as "17 Oct #7".
- Kept in a
Storefile, e.g..storage/supernotify.sequenceholding{"date": "2026-10-17", "count": 7}. The first notification after local midnight starts again at 1 - Taken when the notification is created, for every notification, whether delivered, suppressed, failed or a duplicate, so every archived notification has one
- A dry run (
dry_run: simulate) takes a number from its own daily count instead, with a marker that can't be mistaken for a real one, e.g.#D3. The real numbering has no gaps, and a dry run's reference in its response, the logs or an AI agent's reply is plainly not a notification that was sent. Dry runs aren't archived or logged in the Activity view, so this count doesn't need to survive a restart - Saved with
async_delay_save()on a short delay.Storealready does a final write when HA stops, so only a hard crash within the delay can reuse a number. Restoring a backup can too. Neither breaks anything, because the technical id stays unique - The store file is removed when the config entry is removed (
async_remove_entry) - The existing notifications sensor isn't used. It only counts delivered notifications, and as an entity it can be disabled or deleted
Where the reference appears:
- In the archive, as
ref, next toid - In the logbook message, e.g.
sent 'Oven started' via alexa, mobile_push #7. The row's own date makes it unique - In the logbook event data, next to
notification_id, so the archive viewer can find the entry from an event - Optionally in the notification itself, such as an email footer, so a recipient can quote it
Lookup¶
read_entry() changes from "the first file whose name contains this string" to exact matches:
- The full technical id
- A date and reference, e.g.
2026-10-17 #7. Only that day's files are opened, using the time in their names - An old uuid1 id, as now
Linking¶
| From | To | How |
|---|---|---|
| Activity row | Archive | The #7 in the message with the row's date. The archive viewer could also look up by context id |
| History (recipient entity) | Archive | The state change's context id, or its time |
| Archive | Automation trace | original_trace, as /config/automation/trace/<id>?run_id=<run_id>. The viewer checks the trace's context with trace/get before showing it as the sender |
| Archive | Activity | /logbook?start_date=…&end_date=… around the notification, narrowed to the sender entity when known. The viewer can list related entries itself with logbook/get_events and context_id |
| Archive | History | /history?entity_id=notify.recipient_…&start_date=… for the recipients reached |
The archive viewer itself, a bundled panel, is on the roadmap. The human reference and the exact lookup are useful without it, through the archive enquiry action.
Compatibility¶
- Archive files already written keep their names and stay readable.
read_entry()still finds them by uuid1 - Anything outside Supernotify that stores or parses
notification_idsees a new format if the technical id changes. Supernotify Cards is the one to check Envelope's own uuid1, used only when there's no notification, as in scenario tests, can stay
Open Questions¶
- [ ] ULID or uuid7? A ULID matches HA's context ids and is shorter (26 characters against 36).
uuid7()is in the standard library and is still a uuid - [ ] Change the technical id at all? With a separate reference, uuid1 still works. Sorting by time is the main gain
- [ ] Reference in the filename?
2026-10-17T13-07_7_<id>.jsonwould let a lookup by reference skip opening files, at the cost of a longer name - [ ] Reference format.
#7alone, or with a date prefix where there's no date beside it, e.g.1017-7? - [x] Count dry runs? Yes, from their own count with a marker, e.g.
#D3, so the real numbering has no gaps - [ ] Dry run marker. A
Dprefix, or a suffix such as#3-dry? It has to read clearly in the dry run response and in an AI agent's reply - [ ] Look up by context id. A context id can match several notifications, so the lookup returns a list. Is that worth adding to the archive enquiry action before the viewer exists?
Testing¶
- The reference survives a restart on the same day, and starts at 1 on the next local day
- Every archived notification has a
ref, whatever its outcome - A dry run gets a marked reference from its own count, and doesn't move the real count on
- Lookup by full id, by date and reference, and by an old uuid1 id. A partial number doesn't match a longer one
- The logbook message and event data include the reference
- Two notifications from one automation run get different ids and references, and the same
original_traceandoriginal_context