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, found via the carriers page. 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¶
Parcel: add delivery to my calendar¶
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.
Show the YAML
alias: "Parcel: 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.
Show the YAML
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.
Set the time to whatever suits your routine — the trigger is a fixed time-of-day, not an event.
Show the YAML
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: 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.
Swap "DHL" for "DPD" or "PostNL" as needed, or use not in [...] to silence specific carriers.
Show the YAML
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.
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.
Show the YAML
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.
Show the YAML
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.
Want a different emoji or colour per carrier? Uncomment the lines marked "per-carrier" and list only the carriers you have installed. Any carrier that is not in the list falls back to default_style.
Show the YAML
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
# per-carrier: styling
# variables:
# carrier_styles:
# DHL: { emoji: "🟡", color: "yellow" }
# DPD: { emoji: "🔴", color: "red" }
# PostNL: { emoji: "🟠", color: "orange" }
# default_style: { emoji: "📦", color: "#888888" }
# style: "{{ carrier_styles.get(trigger.event.data.carrier, default_style) }}"
actions:
- action: notify.mobile_app
data:
title: "Out for delivery today ({{ trigger.event.data.carrier }})"
# per-carrier: replace the title above with
# title: "{{ style.emoji }} {{ trigger.event.data.carrier }} on the way"
message: >-
{{ trigger.event.data.sender }} – {{ trigger.event.data.barcode }}
data:
# per-carrier: add
# color: "{{ style.color }}"
clickAction: "{{ trigger.event.data.url }}"
Parcel: 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.
Show the YAML
alias: "Parcel: 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.
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.
Show the YAML
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.