Skip to content

Tag Routing & Retry

Apprise tags do more than just group services — they can drive delivery order, fallback chains, and retry behaviour without changing a line of application code.

This page explains how to configure those features in your configuration files and how to control them at call time via the Python library or CLI.


Every tag in a configuration file may carry a numeric priority prefix separated by a colon: N:tagname.

Lower numbers mean higher urgency — they are dispatched first.

# Priority 1 -- primary alert channels (highest urgency)
1:alerts=slack://tokenA/tokenB/tokenC
1:alerts=discord://webhook_id/webhook_token
# Priority 2 -- secondary channels (used only if priority 1 fails entirely)
2:alerts=telegram://bottoken/chatid
# Priority 5 -- last-resort backup
5:alerts=mailto://user:pass@example.com

Tags without a priority prefix default to priority 0 (highest possible urgency).


When you trigger a tag without specifying a priority prefix, Apprise uses the escalation chain:

  1. Services are grouped by their configured tag priority.
  2. The lowest-numbered group (highest urgency) runs first.
  3. If every service in that group succeeds, Apprise returns True immediately — the higher-numbered groups are never triggered.
  4. If any service in the group fails, Apprise escalates to the next priority level and runs that group.
# Fire all 'alerts' services using the escalation chain.
# Priority-1 entries run first. If they all succeed, priority-5 entries
# are never triggered.
apobj.notify(body="Disk usage above 90%", tag="alerts")
Terminal window
# CLI equivalent
apprise --config=config.yml --tag="alerts" --body="Disk usage above 90%"

This is useful for tiered alerting: send to your fast channel first; only page the on-call person if the fast channel is unavailable.

All services that share the same tag name and the same priority number are treated as a single group. The group is considered successful only if every service in it succeeds. If any one service fails, Apprise escalates to the next-higher-numbered group immediately.

Tags without a numeric prefix default to priority 0 — the highest urgency level. This means you can mix plain and prefixed versions of the same tag to create an escalation chain without touching existing URL entries:

# Priority 0 (default, highest urgency) -- these two form the first group
abc=slack://tokenA/tokenB/tokenC
abc=discord://webhook_id/webhook_token
# Priority 1 -- escalation fallback if the priority-0 group fails
1:abc=telegram://bottoken/chatid

When notify(tag="abc") is called against the above:

  1. The priority-0 group (Slack + Discord) runs first.
  2. If both succeed, True is returned — Telegram is never contacted.
  3. If either fails, Apprise escalates to the priority-1 group (Telegram).

Because tags without a prefix default to priority 0, the service definitions abc and 0:abc are fully equivalent. Both place the service in priority group 0, and a filter of either "abc" or "0:abc" will match services tagged with either form. If both forms appear in the same configuration file, those services will always be dispatched together as a single group.

When you specify a priority prefix in the filter, Apprise skips the escalation chain and notifies only the services whose matching tag carries exactly that priority.

# Trigger ONLY 'alerts' entries configured with priority 2.
# Priority-1 and priority-5 entries are untouched.
apobj.notify(body="Scheduled maintenance window", tag="2:alerts")
Terminal window
# CLI equivalent
apprise --config=config.yml --tag="2:alerts" --body="Scheduled maintenance window"
Filter valueBehaviour
"alerts"Escalation chain — highest priority (lowest number) first
"2:alerts"Exclusive — only priority-2 alerts entries
"0:alerts"Exclusive — only priority-0 entries; alerts and 0:alerts definitions are identical

When notify() runs multiple independent OR chains simultaneously, the default is to let every chain complete before returning — even if one chain has already failed. This ensures every configured service gets at least one attempt.

To instead stop all remaining escalation rounds as soon as any chain exhausts its priority groups without success, set abort_on_chain_failure=True on your AppriseAsset:

from apprise import Apprise, AppriseAsset
asset = AppriseAsset(abort_on_chain_failure=True)
apobj = Apprise(asset=asset)
# ...add services...
# If the 'ops' chain fails entirely, Apprise stops immediately and does
# not run any further escalation rounds for the 'security' chain.
apobj.notify(body="Critical alert", tag=["ops", "security"])
abort_on_chain_failureBehaviour when a chain fails
False (default)All chains complete; every service gets a chance
TrueRemaining escalation rounds are skipped; False is returned at once

Each service can be configured with its own retry count and inter-retry delay. These are independent of the escalation chain and apply whenever a delivery attempt fails.

  • retry — number of additional attempts after the first failure (0 = no retries).
  • wait — seconds to pause between attempts (decimals accepted; 0.0 = no pause).

Append ?retry=N and/or ?wait=S directly to the URL as query parameters.

# Retry up to 3 times, waiting 5 seconds between attempts
1:alerts=slack://tokenA/tokenB/tokenC?retry=3&wait=5
# Retry up to 2 times with a 1.5 second wait
1:alerts=discord://webhook_id/webhook_token?retry=2&wait=1.5
# Backup channel -- no retry needed (default behaviour)
5:alerts=mailto://user:pass@example.com
ParameterTypeConstraintsDefault
retryint0 to 100
waitfloat0.0 to 20.0; integers promoted to float0.5

Invalid values (negative numbers, non-numeric strings, inf, nan) are rejected and fall back to the asset default (see Global Defaults below).


The optional flag marks a service as a “nice to have” endpoint. When optional=yes is set on a service, a delivery failure for that service is silently absorbed — the overall notify() call still returns True even if that endpoint was unreachable.

This is useful any time you want to send a notification opportunistically: deliver it if the endpoint is up, but do not let its absence signal a problem to the calling application.

Motivating example — home-screen displays

Section titled “Motivating example — home-screen displays”

Suppose your home automation system has four Kodi screens tagged media. You want to show a notification on whichever screens are currently on, but you do not want a delivery failure on a sleeping screen to wake you up at 3 am by paging your on-call channel.

# All four screens are optional -- their unavailability is never an error.
media=kodi://192.168.1.10
media=kodi://192.168.1.11
media=kodi://192.168.1.12
media=kodi://192.168.1.13

To mark them optional, append ?optional=yes to each URL:

media=kodi://192.168.1.10?optional=yes
media=kodi://192.168.1.11?optional=yes
media=kodi://192.168.1.12?optional=yes
media=kodi://192.168.1.13?optional=yes
# Even if every screen is off, this call returns True.
apobj.notify(body="Dinner is ready", tag="media")
Terminal window
apprise --config=config.yml --tag="media" --body="Dinner is ready"

How optional interacts with required services

Section titled “How optional interacts with required services”

The flag is per-service. You can freely mix optional and required endpoints within the same tag group. The aggregate result follows these rules:

Services in batchResult
All required, all succeedTrue
All required, one or more failFalse
All optional, all failTrue
All optional, all succeedTrue
Mix of required and optional; required all succeedTrue
Mix of required and optional; any required failsFalse

In short: optional failures are invisible to the caller. Only a required service failure can cause notify() to return False.

Setting optional=yes does not skip the service or bypass its retry logic. Delivery is still attempted, and if retry is greater than zero, Apprise will retry the configured number of times before giving up. Only after all retry attempts have been exhausted does Apprise check the optional flag and decide whether to absorb the failure.

# This service is attempted up to 4 times (initial + 3 retries) with a
# 2-second pause between attempts. If all four attempts fail, the failure
# is silently absorbed because optional=yes.
media=kodi://192.168.1.10?optional=yes&retry=3&wait=2
- kodi://192.168.1.10:
tag: media
optional: yes
retry: 3
wait: 2

You can set the flag directly on any loaded service object, or pass it as a constructor argument when building service objects manually:

import apprise
apobj = apprise.Apprise()
# Option 1 -- URL query parameter (works with all config loaders)
apobj.add("kodi://192.168.1.10?optional=yes")
# Option 2 -- set the attribute directly after loading
obj = apprise.Apprise.instantiate("kodi://192.168.1.10")
obj.optional = True
apobj.add(obj)
# Option 3 -- constructor keyword argument
from apprise.plugins.NotifyJSON import NotifyJSON
svc = NotifyJSON(host="192.168.1.10", optional=True)
apobj.add(svc)
# All three result in the same behaviour: failures are silently absorbed.
result = apobj.notify(body="Dinner is ready")
print(result) # True even if every endpoint was unreachable

Non-critical debug logger alongside a required alert channel:

# Required: the on-call engineer must always get paged.
alerts=slack://tokenA/tokenB/tokenC
# Optional: log to the internal diagnostics channel if it is up,
# but never treat its absence as a failure.
alerts=json://diagnostics.internal/api/events?optional=yes
# Returns True if Slack succeeded, regardless of the JSON logger state.
apobj.notify(body="Database unreachable", tag="alerts")

All-optional tag group — “best effort” broadcast:

# Notify all three displays if they are reachable; never fail if they are not.
displays=kodi://192.168.1.10?optional=yes
displays=kodi://192.168.1.11?optional=yes
displays=kodi://192.168.1.12?optional=yes
# Always returns True regardless of how many displays responded.
apobj.notify(body="Motion detected", tag="displays")

If you want every service in an Apprise session to share the same retry and wait defaults without editing each URL, set them on AppriseAsset:

from apprise import Apprise, AppriseAsset
asset = AppriseAsset(
default_service_retry=2, # retry up to 2 more times on failure before giving up
default_service_wait=3.0, # wait 3 seconds between retries
)
apobj = Apprise(asset=asset)
apobj.add("slack://tokenA/tokenB/tokenC")
apobj.add("discord://webhook_id/webhook_token")
apobj.notify(body="Default retry/wait applied to every service")

Per-URL values (?retry= / ?wait= or YAML keys) override the asset defaults for that specific service only. All other services still use the asset defaults.


A trailing :N on a tag filter value overrides the retry count for every matched service for that one call only. The service’s permanent configuration is not changed.

# Retry each matched service up to 5 times for this call only
apobj.notify(body="Critical alert", tag="alerts:5")
# Exclusive priority-2 match AND up to 3 retries per service
apobj.notify(body="Priority 2 only", tag="2:alerts:3")
Terminal window
# CLI equivalents
apprise --config=config.yml --tag="alerts:5" --body="Critical alert"
apprise --config=config.yml --tag="2:alerts:3" --body="Priority 2 only"

The :N suffix follows the same validation rules as the retry URL parameter. Specifying it does not affect wait — each service still uses its own configured wait value between retries.

Apprise supports two ways to combine tag names in a single notify call.

PatternMeaningExample
OR — multiple separate tag valuesMatch services that carry any of the listed tagsMultiple --tag flags in the CLI; a list of tag strings in Python
AND — multiple tag names in one filter entryMatch only services that carry all of the listed tagsA single --tag "a, b" flag in the CLI; a nested list in Python

When using OR, each tag name forms an independent escalation chain. Every chain must find a fully-successful priority group before notify() returns True. A :N retry suffix on any token applies only to services matched by that token; other tokens are unaffected.

# OR -- devops services get retry=3; management services get retry=2.
# Each tag's escalation chain runs independently.
apobj.notify(body="Team alert", tag=["devops:3", "management:2"])
Terminal window
# OR -- multiple --tag flags; each is an independent chain.
apprise --config=config.yml \
--tag="devops:3" \
--tag="management:2" \
--body="Team alert"

If devops-tagged services all succeed at their lowest priority group, Apprise still dispatches the management chain — the two chains are independent of each other.

When using AND, services must carry every tag in the group to be selected. All AND-matched services share one escalation chain.

# AND -- service must carry BOTH "devops" AND "management" to match.
apobj.notify(body="Combined alert", tag=[["devops", "management"]])
Terminal window
# AND -- comma-separated values inside a single --tag flag.
apprise --config=config.yml --tag="devops, management" --body="Combined alert"

The following example combines all three features: priority-based escalation, per-service retry/wait, and a per-call retry override.

# --- Primary channels (priority 1) ---
# Retry 2 times, 2 second wait between attempts
1:alerts=slack://tokenA/tokenB/tokenC?retry=2&wait=2
1:alerts=discord://webhook_id/webhook_token?retry=2&wait=2
# --- Secondary channel (priority 5 -- fallback) ---
# Single attempt is enough here; the primary channels already retried
5:alerts=mailto://user:pass@pagerduty.example.com

What happens step by step for the normal alert:

  1. Apprise finds all services tagged alerts and groups them by priority.
  2. Priority 1 group (Slack + Discord) dispatches first.
    • Each service makes up to 3 attempts (initial + 2 retries), pausing 2 seconds between each.
  3. If both succeed — True is returned; priority 5 is never triggered.
  4. If either fails after all retries — Apprise escalates to priority 5.
  5. Priority 5 group (PagerDuty email) runs as a fallback.

The examples below show common notification patterns using the features described on this page.

Single tag with two escalation levels — priority values 2 and 50 are used to show that the numbers are arbitrary; only the ordering matters.

# First to try (lower number = higher urgency)
2:alerts=slack://tokenA/tokenB/tokenC
# Fallback; only runs if priority 2 fails
50:alerts=mailto://user:pass@example.com
# Escalation chain: Slack first, email only on failure
apobj.notify(body="Alert", tag="alerts")
Terminal window
apprise --config=config.yml --tag="alerts" --body="Alert"

FeatureWhere it is setScope
Tag priorityN:tagname in config file (TEXT or YAML)Per service definition
Per-service retry count?retry=N URL param or YAML retry: keyPer service definition
Per-service retry wait?wait=S URL param or YAML wait: keyPer service definition
Optional service flag?optional=yes URL param or YAML optional: yes keyPer service definition
Global retry/wait defaultsAppriseAsset(default_service_retry=N, default_service_wait=S)All services in the session
Escalation dispatchnotify(tag="tagname") or --tag tagname (no priority prefix)One notify() / CLI call
Exclusive dispatchnotify(tag="N:tagname") or --tag N:tagnameOne notify() / CLI call
Per-call retry overridenotify(tag="tagname:N") or --tag tagname:NOne notify() / CLI call
Multi-tag OR dispatchnotify(tag=["tagA", "tagB"]) or --tag tagA --tag tagBOne notify() / CLI call
Multi-tag AND dispatchnotify(tag=[["tagA", "tagB"]]) or --tag "tagA, tagB"One notify() / CLI call
Multi-tag OR with per-tag retrynotify(tag=["tagA:N", "tagB:M"]) or --tag tagA:N --tag tagB:MOne notify() / CLI call
Early-abort on chain failureAppriseAsset(abort_on_chain_failure=True)All services in the session
Questions or Feedback?

Documentation

Notice a typo or an error? Report it or contribute a fix .

Technical Issues

Having trouble with the code? Open an issue on GitHub:

Made with love from Canada