Automation cookbook¶
Every automation on this page is pulled straight from the Parcel Aggregator's
examples/automations/ folder when this site is
built, so it always matches the version that is actually shipped. Looking for
dashboard cards? They have their own page.
They are carrier-agnostic on purpose: they trigger on canonical
ParcelStatus values and the unified
parcel_aggregator_* events, so the same automation covers a carrier you install
next year without a single edit.
Not running the aggregator?
These work per carrier too. Swap parcel_aggregator_ for the carrier's own
domain (postnl_parcel_status_changed) and the aggregator's sensors for that
carrier's own. The parcel data inside is identical — see the
contract.
Where these go
Settings → Automations & scenes → Create automation → Create new automation, then ⋮ → Edit in YAML and paste over what is there.
Automations¶
Parcels: add delivery to my calendar
Add every expected delivery — from any installed carrier — to a calendar of your choice.
# Add every expected delivery — from any installed carrier — to a calendar
# of your choice.
#
# The aggregator already ships a read-only combined "Deliveries" calendar
# entity that needs no maintenance — use that if you just want to SEE all
# carriers' deliveries together in Home Assistant. This automation is for the
# other case: writing each delivery into your OWN calendar (Google, CalDAV,
# or HA's Local Calendar) so it also shows on your phone.
#
# Pick a writable calendar below (replace `calendar.your_calendar`).
#
# Caveat: `calendar.create_event` always creates a NEW event, so if a carrier
# shifts the ETA you'll get a second entry — delete the old one by hand, or
# just rely on the built-in combined Deliveries calendar instead.
alias: "Parcels: add delivery to my calendar"
description: Write every carrier's expected delivery window into a calendar you choose.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_delivery_time_changed
conditions:
# Only act when there is a real start moment to put on the calendar.
- condition: template
value_template: "{{ trigger.event.data.new_planned_from is not none }}"
actions:
- action: calendar.create_event
target:
entity_id: calendar.your_calendar
data:
summary: >-
{{ trigger.event.data.carrier }}:
{{ trigger.event.data.sender or trigger.event.data.barcode }}
description: >-
Status: {{ trigger.event.data.status }}
{{ trigger.event.data.url }}
start_date_time: "{{ trigger.event.data.new_planned_from }}"
end_date_time: >-
{{ trigger.event.data.new_planned_to
if trigger.event.data.new_planned_to
else (as_datetime(trigger.event.data.new_planned_from) + timedelta(hours=1)).isoformat() }}
Parcel: announce next delivery (any carrier)
Speak the expected delivery window for the next active parcel across every carrier, one hour before the planned_from timestamp. Uses the aggregator's Next delivery sensor directly instead of an event.
# Speak the expected delivery window for the next active parcel across
# every carrier, one hour before the planned_from timestamp. Uses the
# aggregator's Next delivery sensor directly instead of an event.
alias: "Parcel: announce next delivery (any carrier)"
description: Speak the next aggregated delivery window an hour before it opens.
mode: single
triggers:
- trigger: template
value_template: >-
{% set when = states('sensor.parcel_aggregator_next_delivery') %}
{% if when not in ('unknown', 'unavailable') %}
{{ (as_datetime(when) - now()).total_seconds() | int == 3600 }}
{% endif %}
actions:
- action: tts.cloud_say
data:
entity_id: media_player.living_room
message: >-
Parcel on its way, expected around
{{ as_datetime(states('sensor.parcel_aggregator_next_delivery'))
.strftime('%H:%M') }}.
Parcel: daily morning summary
Daily 08:00 push notification summarising what's expected today across every installed carrier. Uses the aggregator's by_carrier count attribute for the headline and the parcels attribute for the sender list per carrier.
# Daily 08:00 push notification summarising what's expected today
# across every installed carrier. Uses the aggregator's `by_carrier`
# count attribute for the headline and the `parcels` attribute for
# the sender list per carrier.
#
# Set the time to whatever suits your routine — the trigger is a fixed
# time-of-day, not an event.
alias: "Parcel: daily morning summary"
description: Push the day's expected parcels grouped by carrier.
mode: single
triggers:
- trigger: time
at: "08:00:00"
conditions:
- condition: numeric_state
entity: sensor.parcel_aggregator_incoming_parcels
above: 0
actions:
- action: notify.mobile_app
data:
title: "Parcels today"
message: >-
{% set by_carrier = state_attr('sensor.parcel_aggregator_incoming_parcels', 'by_carrier') or {} %}
{% set parcels = state_attr('sensor.parcel_aggregator_incoming_parcels', 'parcels') or [] %}
{%- for carrier, count in by_carrier.items() %}
{{ carrier }}: {{ count }}
{%- set senders = parcels | selectattr('carrier', 'eq', carrier)
| map(attribute='sender')
| reject('none')
| list %}
{%- if senders %}
• {{ senders | join('
• ') }}
{%- endif %}
{%- endfor %}
Parcel: out for delivery (per-carrier styling)
Notify when any parcel goes out for delivery, with a carrier-specific emoji and title. Demonstrates how to branch on the carrier field inside a single carrier-agnostic automation rather than maintaining one automation per carrier.
# Notify when any parcel goes out for delivery, with a carrier-specific
# emoji and title. Demonstrates how to branch on the `carrier` field
# inside a single carrier-agnostic automation rather than maintaining
# one automation per carrier.
alias: "Parcel: out for delivery (per-carrier styling)"
description: Notify when any parcel is on the truck today; style the message per carrier.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_status_changed
event_data:
new_status: out_for_delivery
variables:
carrier_styles:
DHL: { emoji: "🟡", color: "yellow" }
DPD: { emoji: "🔴", color: "red" }
PostNL: { emoji: "🟠", color: "orange" }
actions:
- action: notify.mobile_app
data:
title: "{{ carrier_styles[trigger.event.data.carrier].emoji }} {{ trigger.event.data.carrier }} on the way"
message: >-
{{ trigger.event.data.sender or 'Unknown sender' }} – {{ trigger.event.data.barcode }}
data:
color: "{{ carrier_styles[trigger.event.data.carrier].color }}"
clickAction: "{{ trigger.event.data.url }}"
Parcel: DHL parcels only
Only notify for DHL parcels — handy when you actively track one carrier and don't want noise from the others. Demonstrates the inverse of the carrier-agnostic default: a unified event listener with a carrier filter in the condition block.
# Only notify for DHL parcels — handy when you actively track one carrier
# and don't want noise from the others. Demonstrates the inverse of the
# carrier-agnostic default: a unified event listener with a carrier
# filter in the condition block.
#
# Swap "DHL" for "DPD" or "PostNL" as needed, or use `not in [...]` to
# silence specific carriers.
alias: "Parcel: DHL parcels only"
description: Notify only for new DHL parcels, ignore other carriers.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_registered
conditions:
- condition: template
value_template: "{{ trigger.event.data.carrier == 'DHL' }}"
actions:
- action: notify.mobile_app
data:
title: "DHL parcel on the way"
message: >-
{{ trigger.event.data.sender or 'Unknown sender' }} – expected
{% if trigger.event.data.planned_from %}
{{ as_timestamp(trigger.event.data.planned_from)
| timestamp_custom('%a %-d %b') }}
{% else %}
soon
{% endif %}
data:
clickAction: "{{ trigger.event.data.url }}"
Parcel: ready at pickup point (any carrier)
Notify when a parcel arrives at any pickup point — DHL ServicePoint, PostNL Point, or DPD ParcelShop — ready for collection.
# Notify when a parcel arrives at any pickup point — DHL ServicePoint,
# PostNL Point, or DPD ParcelShop — ready for collection.
#
# Triggers on the canonical AT_PICKUP_POINT status. Each carrier maps
# its own raw "ready to collect" status onto this enum value, so one
# automation handles all three.
alias: "Parcel: ready at pickup point (any carrier)"
description: Notify when any parcel arrives at a pickup point for collection.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_status_changed
event_data:
new_status: at_pickup_point
actions:
- action: notify.mobile_app
data:
title: "Parcel ready for collection ({{ trigger.event.data.carrier }})"
message: >-
{{ trigger.event.data.sender }} is waiting for you at
{{ trigger.event.data.pickup_point or 'the pickup point' }}.
data:
clickAction: "{{ trigger.event.data.url }}"
Parcel: delivery time updated (any carrier)
Notify when any carrier updates the expected delivery time for an active parcel — either a brand-new ETA appears, or an existing one shifts. value -> null transitions (carrier dropped the ETA) are intentionally silent at the integration level, so they will not trigger this automation.
# Notify when any carrier updates the expected delivery time for an
# active parcel — either a brand-new ETA appears, or an existing one
# shifts. value -> null transitions (carrier dropped the ETA) are
# intentionally silent at the integration level, so they will not
# trigger this automation.
alias: "Parcel: delivery time updated (any carrier)"
description: Notify when any carrier updates the expected delivery time for an active parcel.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_delivery_time_changed
actions:
- action: notify.mobile_app
data:
title: "New delivery time ({{ trigger.event.data.carrier }})"
message: >-
{{ trigger.event.data.sender }} – {{ trigger.event.data.barcode }}:
expected
{{ as_timestamp(trigger.event.data.new_planned_from) | timestamp_custom('%a %d %b %H:%M', true)
if trigger.event.data.new_planned_from else 'unknown' }}
data:
clickAction: "{{ trigger.event.data.url }}"
Parcel: out for delivery (any carrier)
Notify exactly once per parcel when any carrier says it's on the delivery vehicle today. Filters on the canonical ParcelStatus value out_for_delivery, so this is independent of carrier-specific raw status strings.
# Notify exactly once per parcel when any carrier says it's on the
# delivery vehicle today. Filters on the canonical ParcelStatus value
# `out_for_delivery`, so this is independent of carrier-specific raw
# status strings.
alias: "Parcel: out for delivery (any carrier)"
description: Notify when any parcel goes out for delivery today.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_status_changed
event_data:
new_status: out_for_delivery
actions:
- action: notify.mobile_app
data:
title: "Out for delivery today ({{ trigger.event.data.carrier }})"
message: >-
{{ trigger.event.data.sender }} – {{ trigger.event.data.barcode }}
data:
clickAction: "{{ trigger.event.data.url }}"
Parcels: any outgoing parcel delivered
Notify when an outgoing parcel from ANY carrier (something you sent, e.g. a webshop return) reaches the recipient, so you can watch for the refund. One automation covers every carrier via the aggregator's unified event.
# Notify when an outgoing parcel from ANY carrier (something you sent, e.g.
# a webshop return) reaches the recipient, so you can watch for the refund.
# One automation covers every carrier via the aggregator's unified event.
alias: "Parcels: any outgoing parcel delivered"
description: Notify when an outgoing/return parcel from any carrier is delivered.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_outgoing_parcel_delivered
actions:
- action: notify.mobile_app
data:
title: "Outgoing parcel delivered ({{ trigger.event.data.carrier }})"
message: >-
{{ trigger.event.data.receiver or trigger.event.data.barcode }} has
arrived.
data:
clickAction: "{{ trigger.event.data.url }}"
Parcel: new parcel registered (any carrier)
Notify when any carrier announces a new parcel.
# Notify when any carrier announces a new parcel.
#
# Uses the unified parcel_aggregator_parcel_registered event, so this one
# automation covers every carrier the aggregator knows about — no need
# for separate per-carrier copies.
alias: "Parcel: new parcel registered (any carrier)"
description: Notify when any carrier announces a new parcel.
mode: queued
max: 10
triggers:
- trigger: event
event_type: parcel_aggregator_parcel_registered
actions:
- action: notify.mobile_app
data:
title: "New parcel on the way ({{ trigger.event.data.carrier }})"
message: >-
{{ trigger.event.data.sender }} – expected
{% if trigger.event.data.planned_from %}
{{ as_timestamp(trigger.event.data.planned_from)
| timestamp_custom('%a %-d %b') }}
{% else %}
soon
{% endif %}
data:
clickAction: "{{ trigger.event.data.url }}"
Carrier-specific events¶
Every carrier fires its own events, whether or not the aggregator is installed: <domain>_parcel_registered, <domain>_parcel_status_changed, <domain>_parcel_delivered, <domain>_parcel_delivery_time_changed. Use these when you run a single carrier, when you want one carrier to behave differently from the rest, or when you need the raw carrier payload the aggregator strips. See the parcel contract for the payload.