Why your CalDAV calendar reminders never fire on Android
The events are there. You can see them in the calendar app, the right ones on the right days, with “10 minutes before” printed on the detail screen. And nothing ever goes off.
This is the most common complaint about self-hosted calendars on Android, and it has at least four different causes that all look identical from the phone. Two of them you cannot fix from the calendar app, whatever it is. Here they are in the order worth checking, with how to tell them apart.
Three jobs, not two
I’ve written before that Android splits calendar work into two jobs: something syncs, something displays. Reminders are where the split has a third part nobody mentions.
- Sync — a sync adapter like DAVx⁵ talks CalDAV
to your server and writes events into Android’s calendar provider. It also
writes each event’s alarms into the provider’s
Reminderstable. - Display — a calendar app reads the provider and draws it.
- Fire — a calendar app also has to turn those
Remindersrows into notifications at the right moment.
DAVx⁵ does the first job only, and says so. The provider stores reminder rows but does not, on its own, put a notification on your screen. So a phone with DAVx⁵ and no calendar app has a complete, correctly synced calendar that is incapable of reminding you of anything.
That is cause zero, and it is the easy one: install a calendar app. The other three are what people hit after they already did that.
Cause 1 — the alarm arrived, flagged as one that doesn’t ring
This is the interesting one, because there is no way to see it from the phone.
Android’s Reminders table has a METHOD column with five values:
METHOD_DEFAULT, METHOD_ALERT, METHOD_EMAIL, METHOD_SMS and
METHOD_ALARM. In practice only METHOD_ALERT produces a notification.
Calendar apps query that value and ignore the rest — AOSP’s does, Calendula
does, every one I’ve read does.
Here is the part that took me a while to believe. CalendarContract’s own
documentation says something different:
the device will only process
METHOD_DEFAULTandMETHOD_ALERTreminders (the other types are simply stored so we can send the same reminder info back to the server when we make changes)
So the platform documents METHOD_DEFAULT as a value that rings, and then the
platform’s own provider doesn’t honour it: CalendarAlarmManager filters on
METHOD = METHOD_ALERT and METHOD_DEFAULT appears nowhere in that file. The
contract and the implementation disagree, the implementation wins, and
everything downstream has been written against the implementation.
Which value a synced alarm lands under is decided by
ical4android, the library DAVx⁵
uses to translate iCalendar into provider rows. The mapping is a single when
block in AndroidEvent.insertReminder, and it reads the VALARM’s ACTION
property:
ACTION in the VALARM |
Provider METHOD |
Rings? |
|---|---|---|
DISPLAY |
METHOD_ALERT |
yes |
AUDIO |
METHOD_ALERT |
yes |
EMAIL |
METHOD_EMAIL |
no — the server’s job |
| anything else, or absent | METHOD_DEFAULT |
no |
The library’s own comment on that last branch says it: won’t trigger an alarm on the Android device.
So an alarm written by something that left ACTION off — or set it to
NONE, or to PROCEDURE — syncs through, appears on the event detail screen
as a reminder, and is stored in a way that every calendar app on the device
reads as silent. There is no error anywhere. (RFC 5545 does require a VALARM
to carry an ACTION, so an alarm missing one is already malformed; it just
happens to be malformed in a way the parser tolerates instead of rejecting,
which is the whole reason it gets this far.) DAVx⁵ hit exactly
this with its own default-reminder setting years ago, and the maintainer’s
diagnosis in
discussion #491 is
the same paragraph I just wrote: the alarms were being created without an
action, ical4android mapped them to METHOD_DEFAULT, and Android ignored
them.
The fix belongs on whichever side wrote the alarm. A calendar app could in
principle fire METHOD_DEFAULT too — the documentation above says it should —
but none does, so writing an app that did would mean ringing for rows every
other calendar app on the device treats as silent. The row is dead in
practice whatever the contract says.
How to check: open the event on your server’s web UI and look at what kind
of reminder it is. In Nextcloud, a reminder set to Email is an
ACTION:EMAIL alarm — it will never make your phone buzz, by design, and the
mail comes from the server instead. If it is set to Notification and still
does nothing, you are probably in cause 2.
Cause 2 — the alarm never arrived, because it was invalid
The alarm can also be rejected on the way in, which looks the same from the phone and different in the logs.
RFC 5545 puts requirements on a VALARM that most people setting a reminder
in a web UI never see. An ACTION:DISPLAY alarm must carry a DESCRIPTION.
Durations cannot mix weeks with hours. Both of those have bitten Nextcloud
inside the last few years:
- Nextcloud Calendar wrote display alarms with no
DESCRIPTION, which is invalid, and the reporter had DAVx⁵ throwing a null pointer on it while syncing (nextcloud/calendar#3040 — fixed;mainnow adds the property when it is missing). - An eight-day reminder came out as
TRIGGER;RELATED=START:-P1WT15H, which RFC 5545 does not allow: a duration in weeks cannot carry a time part. Nothing subtle happened here — DAVx⁵ rejected the whole event, posted a notification saying invalid resources were ignored, and the event never appeared in the calendar at all (davx5-ose#1734, fixed on the Nextcloud side in nextcloud/calendar#7526).
Both of those are server-side bugs that a phone cannot work around. Neither is a reason to distrust the stack in general — they are the kind of thing that gets fixed once someone reports it, and both were.
How to check: the quickest tell needs no tools. If the reminder never made it into the provider, the calendar app isn’t showing it on the event detail screen either — a reminder you can see in the app arrived, one you can’t, didn’t. And if the event itself is missing, that’s the same cause one level up, which is what both bugs above actually looked like.
For the detail, DAVx⁵’s logs are off by default: Settings → turn on Verbose logging, force a sync, then open Show debug info → View logs. A rejected resource also arrives as its own notification, which opens the same place.
Cause 3 — the alarm is fine and Android sat on it
If the reminder is visible on the phone, was set as a notification, and still doesn’t fire, the remaining suspects are all local. In rough order of how often they’re the answer:
- Battery optimisation. This is the big one, and on some OEM builds it is brutal. A calendar app that has been dozed cannot post a notification at the moment it planned to. Exempting the calendar app from battery optimisation is the single most effective fix on this list.
- Notification permission. Android 13 and up requires
POST_NOTIFICATIONS, granted at runtime. Decline it once and the app is posting into a void. It can find out —areNotificationsEnabled()is one call — but plenty of apps never ask, so you get silence instead of a warning. - A reboot. Pending alarms do not survive a restart. An app has to listen
for
BOOT_COMPLETEDand re-arm them; not all of them do, and reminders that work until the first reboot and never again are the signature. - Exact alarm permission. On Android 12 and 12L, a precisely-timed alarm
needs
SCHEDULE_EXACT_ALARM, which you can revoke; without it the reminder arrives when the system feels like it. From Android 13 a calendar app can declareUSE_EXACT_ALARMinstead, which is granted automatically — so this bites the apps that never adopted it, and on Android 14 it bites harder, becauseSCHEDULE_EXACT_ALARMis no longer granted on install.
How to check: set an event five minutes out with a one-minute reminder, lock the phone, and leave it alone. If it fires there but not overnight, it’s battery optimisation.
What Calendula does about it
I write Calendula, so treat this section as interested. What it can honestly claim is limited to the local half:
It delivers reminders itself rather than reacting to the calendar provider’s
broadcast. That sounds like an implementation detail and it is the reason the
whole pipeline got rewritten: the provider’s EVENT_REMINDER broadcast holds
on stock Android and demonstrably not everywhere. AOSP’s own unbundled
calendar accumulated three separate workarounds in this area — two for OEM
providers that retargeted the broadcast or wrote the alert row late, one for
stale notifications on HTC — and only one of the three survives in the current
tree. An app that only reacts cannot tell “nothing was due” from “the
broadcast never came”. So Calendula reads the reminder offsets as data, works
out when each occurrence is due, and holds a single alarm for the next one —
exact where the OS allows it, inexact where the permission above was
withheld. A scan that runs late still posts what the missed
alarm owed, so a reboot, an app update or a doze window costs you nothing.
There is a Reliable delivery setting that opens the system battery optimisation dialog directly, because cause 3 is a real cause and burying the fix three menus deep in Android’s settings helps nobody.
What it cannot do is cause 1 or cause 2. Calendula queries METHOD_ALERT and
nothing else, exactly like every other calendar app, and an alarm that never
synced isn’t on the device to fire. If someone tells you their calendar app
fixes those, ask them which column they’re reading.
The short version
- Is a calendar app installed at all? DAVx⁵ is not one.
- Can you see the reminder on the event in the app? If not, it never synced — check the server and the sync log.
- Is the reminder a notification on the server, or an email? Email reminders are the server’s job and will never ring the phone.
- Everything else is battery optimisation until proven otherwise.
Checked on 20 September 2026 against ical4android main and the linked issues.
If one of them has moved on since, tell me and I’ll correct it.