Differences with Apprise
mercurio aims to accept every Apprise URL and configuration file; sending
byte-for-byte the same requests is not a goal. Each entry says what Apprise
does and what mercurio does instead. Every difference on this page is
intentional: it stays that way. Differences of a single service are
on its page (see the service list), taken from the
# Differences with Apprise section of its module docs; those are
intentional too, unless marked todo.
General
- Markdown. Apprise renders with the Python
markdownpackage, while mercurio renders CommonMark plus GFM tables (pulldown-cmark); ordinary Markdown gives the same HTML, edge cases differ (headings with seven or more#, some entities, list continuation lines). - Request throttle. Apprise waits 5.5 s between requests to the same
service (
NotifyBase.request_rate_per_sec), while mercurio keeps only the explicit per-plugin throttles and drops the inherited default. - Basic auth without a password. Apprise’s
requestssendsuser:None, while mercurio sendsuser:. - URL parser. Apprise checks hosts in
parse_urland turns+in query values into a space for Office 365, while mercurio leaves host checks to the services that connect and never turns+into a space. ?tag=on a URL. Apprise ignores it, while mercurio tags the URL for--tag/TagFilter; in a configuration file the values are added to the entry’s tags.- Emoji names. Apprise expands
:shortcode:from its own table, while mercurio uses GitHub’s gemoji names (theemojiscrate). They agree on 1,843 of Apprise’s 1,855 names; 10 give another emoji (:grinning:is 😀, not 😄;:beetle:🪲, not 🐞),:blond_woman:and:information_deskperson:stay as text, and about 70 gemoji names that Apprise lacks are expanded. %2Fin a URL path. Apprise decodes it into a separator, sojson://,xml://andform://request another path (its issue #1799), while mercurio keeps it encoded for those three, soa%2Fbstays one path segment. Other services decode it as Apprise does (xmpp JIDs, signalgrid channels).?tz=. Validated as in Apprise (the UTC family, any case, or the location alone such asparis; an unknown zone rejects the URL). E-mailDate:and syslog timestamps use it, else a YAMLasset:timezone(ortz), else the machine’s zone.?optional=. Parsed by both; no effect in mercurio.- Overflow. Apprise lets each plugin tune overflow (
overflow_*attributes), numbers split pieces ([01/03]) and may repeat the title, while mercurio uses one policy: title on the first piece only, no counters,truncatekeeps the first piecesplitwould produce. - Malformed HTML (for example a malformed
<![section). Apprise fails the notification, while mercurio fails too when the core converts it, but the Pushover and Telegram reducers send it unreduced. - Upstream bugs. Where Apprise’s behaviour is clearly a bug mercurio
does the correct thing; see the service entries for e-mail, IFTTT, IRC,
Mastodon, Plivo, PushWard, Reddit, Remote Syslog, SMPP and Synology Chat.
Also fixed without
an entry of their own: target order in Bark, ntfy and MQTT, LaMetric
sound=thunder, Slack gov webhook query parameters, Mattermost webhook ports, Matrix!id:Noneroom ids andnullGET bodies, Office 365 re-sending an expired token on login, Burst SMScountry=US, Twittermode=DM, VoIP.ms stopping on one bad reply, Home Assistantsvc:a,b,clists andstorage prunedeleting Apprise’s*.psdatafiles. - Not available. Pickling a loaded notifier, internationalised
messages (
i18n). Apprise’sasync_notifyisAsyncMercurio::notify.
CLI
- Default input format. Apprise’s CLI assumes
text, while mercurio passes the body through when-iis not given;-i textgives Apprise’s behaviour. -e. Apprise runs its full escape decoder, while the CLI decodes\n,\r,\tand\\only (Asset::interpret_escapesin the library runs the full decoder).-gwith URLs on the command line. Apprise ignores it and notifies all of them, while mercurio filters them by their?tag=values.- No services (nothing given, nothing configured) exits with 2.
- Not implemented:
-P/--plugin-path(register custom services from Rust),-D/--debug(use-vvv). -ddoes not print storage URL ids; usemercurio storage.check,serve,servicesare mercurio subcommands.
Server
- Alertmanager and Grafana webhooks. apprise-api needs a field mapping
and still cannot build a body from a list of alerts, while mercurio reads
their webhook payloads directly on
/notify(see Alertmanager and Grafana webhooks). - Web UI. apprise-api serves a web UI (
/, login/logout,/qr,/cfg/@, themes,APPRISE_API_ONLY), while mercurio has none: those paths are404and the setting is ignored. - Non-ASCII header values (UTF-8 titles in
X-Apprise-Title). apprise-api gets them as WSGI Latin-1 strings, while mercurio decodes them as UTF-8, as Latin-1 only when they are not valid UTF-8. - Basic Auth credentials. apprise-api decodes them as UTF-8, so a non-ASCII password from Apprise Mobile (which sends Latin-1) fails, while mercurio decodes them as UTF-8, as Latin-1 only when they are not valid UTF-8.
- Translated messages. apprise-api translates its error and status
messages by
Accept-Languageor cookie and setsContent-Language, while mercurio answers in English only. - Returned logs. Both return the library’s own log lines for the
request’s level and stream them live while sending. mercurio’s lines
have their own wording, redact URLs, and add one INFO or ERROR line per
finished service;
APPRISE_WEBHOOK_URLgets INFO and up. - Persistent storage. Both prune old persistent storage and logins
without a configuration once at start-up and then daily
(
APPRISE_PRUNE_ENABLED,APPRISE_PRUNE_INTERVAL_SECONDS,APPRISE_PRUNE_TIMEOUT_SECONDS,APPRISE_STORAGE_PRUNE_DAYS,APPRISE_AUTH_PRUNE_SECONDS): apprise-api in a separate container process, mercurio inside the server. The storage layout differs andAPPRISE_STORAGE_UID_LENGTHworks the same. - Workers. apprise-api sizes gunicorn by
APPRISE_WORKER_COUNT(twice the CPUs plus one by default), gives a workerAPPRISE_WORKER_TIMEOUTseconds (300 by default) and recycles it afterAPPRISE_WORKER_MAX_REQUESTSrequests, while mercurio reads the first two with its own meaning and defaults. Each worker stands for 128 requests in progress (4 workers, so 512, by default), and the timeout is how long a request head, and each piece of a body, may take to arrive (30 s by default). It has no worker processes to recycle, soAPPRISE_WORKER_MAX_REQUESTSand its jitter are ignored. - Request log. apprise-api logs requests through nginx (
combinedformat) plusNOTIFY - ip - ...warnings, while mercurio logs one line per request with its key, services, failures and error. - Unknown paths. apprise-api answers
404with an error message, while mercurio answers an empty404, before authentication; its405Allowheader omitsOPTIONS. - Metrics. apprise-api exports the full
django_prometheusset, while mercurio exports its HTTP metrics under the same names (request and response counts, latency overall and by view, body sizes, streaming responses) and, on Linux,prometheus_client’sprocess_*metrics. Django’s model, database, cache and migration metrics and Python’sgcandinfometrics have no equivalent. - Other settings. Plugin paths are ignored.
- Logins and times. apprise-api checks logins with Django’s default
hashers; without
argon2-cffiandbcrypt(not in its requirements) those arepbkdf2_sha256,pbkdf2_sha1andscrypt, and it writespbkdf2_sha256. It reports times in theTZzone, while mercurio reads both PBKDF2 forms and writespbkdf2_sha256; ascryptdigest, which apprise-api never writes, is rejected. Times are UTC. - JSON
/addwithoutformat. apprise-api rejects it, while mercurio detects TEXT or YAML. - Stored configurations cannot include local files; attachment fields
only accept
http(s)://URLs (a local path is a400).
Config and YAML
- Results. The loader matches Apprise on 326 of 329 recorded configurations and 424 of 431 YAML documents; the rest are below.
${NAME}in an e-mail port. Apprise’s e-mail parser drops the host and the marker with it, so the port is ignored, while mercurio keeps the entry pending and rejects a non-numeric value when it is resolved.- Self-referencing YAML anchor. Apprise rejects only that entry, while mercurio rejects the whole document.
- YAML parser (
saphyr-parserplus a composer instead of PyYAML): several documents (---), custom!tagsand collection keys are rejected; explicit? keyentries are not supported; timestamps stay strings;1,1.0andtrueare three distinct keys; tabs are accepted as separators and trailing whitespace;=is a plain string; a quoted continuation line less indented than its block is an error. asset:keys.storage_pathis accepted in configuration files (a mercurio extension; Apprise takes it only as a constructor argument);interpret_emojisis rejected with a warning; keys mercurio has no use for (interpret_escapes,async_mode,encoding,image_path_*, …) are accepted without effect; the defaults name mercurio (app_id: Mercurio).
Storage
- File format. Apprise writes gzip
cache.psdata, while mercurio writes plain JSONcache.json; neither reads the other’s file (prune and clear remove both). - Modes.
auto,flushandmemory(-SM,APPRISE_STORAGE_MODE,Asset::storage_mode) as in Apprise;flushwrites before each I/O step of a send rather than on every change. - Entry checksum. Each
cache.jsonentry carries a 6-character SHA-256 checksum ("!"), and entries that fail it are dropped on load, as in Apprise. The hashed text differs (Apprise hashes its Pythonrepr), so the checksums are not interchangeable; the files are not shared anyway. Caches written by earlier mercurio versions have no checksum and are dropped once. - Size cap. Apprise refuses
write()calls that would push a namespace past 1 MiB; mercurio has nowrite()API for named files, and Apprise’s cache itself is uncapped, so there is no cap. mercurio storage listshows every configured URL, including services that never store anything (unused); Apprise omits them.${NAME}template targets are rebuilt per call, so their in-memory store does not outlive the call.- Rate-limit state (Bluesky, Mastodon, Reddit, Twitter) is kept for the life of a URL, across sends, as in Apprise.
- Remote stores. Store I/O is local only.
Attachments
?+Header=on an attachment URL. Apprise sends it as a header and a query parameter, while mercurio only as a header.- Downloads. Apprise streams to a temporary file and stops early, while mercurio reads into memory and also stops once the body passes
attachment_max_size. - Split messages. Apprise plugins can pick the piece that carries the
attachments (
_attachment_send_index), while mercurio always sends them with the first piece.
Retries
- Policy. Apprise retries every failure
?retry=times after a fixed?wait=and ignoresRetry-After, while mercurio retries only rate limits (429, 503 withRetry-After) and transient failures (408, 5xx, DNS and connect errors). It stops on authentication errors, other 4xx and failures after the request was sent. It honoursRetry-Afterup to 300 s. ?wait=. Apprise defaults to 0, while mercurio defaults to 0.5 s. Both cap it at 20 s.- Unclassified failures.
Error::Servicefailures (for example a JSON body with"ok": false) andregister_schemeclosures are not retried. - Service hints. Apprise reads neither Telegram’s
parameters.retry_afternor Matrix’sretry_after_ms, while mercurio feeds both into the retry loop as the 429’s wait (Matrix once its own back-off gives up).
Services
- Microsoft Teams (
msteams://). Apprise’s retired plugin is not provided; useworkflows://. - Discontinued services. Apprise still ships plugins for services that
no longer run, while mercurio does not provide them, and their URLs fail as an
unknown scheme.
- Growl (
growl://): the project was retired in November 2020. - Guilded (
guilded://): Roblox shut it down on 19 December 2025. - Kumulos (
kumulos://): acquired by Optimove in 2022; the API hostmessages.kumulos.comno longer resolves. - Pushed (
pushed://): the API hostapi.pushed.cono longer resolves. - Pushjet (
pjet://,pjets://): the project is abandoned and its domain redirects to an unrelated site.
- Growl (