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 one of the most common complaints 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 that rarely gets mentioned.
- 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
You can’t see this one from the phone at all, which is what makes it interesting.
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, and so does every other one I’ve read.
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 DAVx⁵’s own mapping code,
which translates iCalendar into provider rows. It used to live in a separate
library, ical4android, then in its successor synctools, and both have since
been folded into the
DAVx⁵ repository itself. The mapping
is a single when block in
RemindersBuilder.buildReminder,
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 code’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 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 why 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 I know of 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 you never see when you set a
reminder in a web UI. 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. Both got fixed once someone reported them, so I wouldn’t read them as a reason to distrust the stack in general.
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. In my experience, exempting the calendar app from battery optimisation fixes more of these than anything else 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 with one call toareNotificationsEnabled(), 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, but it’s why 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 doesn’t lose you a reminder, only delays it.
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, like every other calendar app I’ve read, 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.
The linked issues were checked on 20 September 2026, and the mapping on 25
September 2026 against DAVx⁵’s main branch.
If one of them has moved on since, tell me and I’ll correct it.