2026-02-09 23:02:34 -05:00

7.0 KiB

Mail Calendar Sync for Nextcloud

Automatically apply calendar invitation responses (iMIP) received via email to your Nextcloud calendar events.

What it does

When someone responds to a calendar invitation you sent (Accept, Decline, Tentative), this app automatically updates the attendee's participation status in your calendar — no manual action needed.

Supported iMIP methods

Method Behavior
REPLY Updates attendee status (ACCEPTED/DECLINED/TENTATIVE) on an existing event in your calendar. The event must already exist (matched by UID).
REQUEST Updates an existing event with new details (time changes, etc). Optionally auto-adds new invitations to your calendar.
CANCEL Marks an existing event as CANCELLED when the organizer cancels it.

Key safety feature

The app always checks that the event UID from the email response already exists in your calendar before applying any changes. This prevents stale or spoofed responses from creating phantom events.

Requirements

  • Nextcloud 28 or later
  • Nextcloud Mail app installed and configured with at least one account
  • At least one writable calendar
  • Background jobs (cron) configured and running

Installation

  1. Copy the mail_calendar_sync folder to your Nextcloud apps/ directory:
cp -r mail_calendar_sync /var/www/nextcloud/apps/
  1. Set proper permissions:
chown -R www-data:www-data /var/www/nextcloud/apps/mail_calendar_sync
  1. Enable the app:
sudo -u www-data php occ app:enable mail_calendar_sync
  1. Run the database migration:
sudo -u www-data php occ migrations:migrate mail_calendar_sync

Configuration

Each user configures the app individually:

  1. Go to Settings → Groupware → Mail Calendar Sync
  2. Enable the sync
  3. Select which Mail account to scan for responses
  4. Select which Calendar to apply updates to
  5. Optionally enable auto-accept to automatically add new invitations
  6. Click Save

Manual sync

Click the "Sync now" button in settings to trigger an immediate scan instead of waiting for the background job.

How it works

┌─────────────────┐     ┌────────────────┐     ┌────────────────────┐
│  Background Job  │     │  Mail Service   │     │  Calendar Service   │
│  (every 10 min)  │────>│  (IMAP fetch)   │────>│  (CalDAV lookup)    │
└─────────────────┘     └────────────────┘     └────────────────────┘
        │                       │                        │
        │ 1. Load user config   │ 2. Find iMip messages  │ 3. Search by UID
        │                       │    in INBOX             │    in user calendars
        │                       │                        │
        │                       │ 4. Extract ICS from    │ 5. Verify event exists
        │                       │    MIME parts           │    before updating
        │                       │                        │
        │                       │                        │ 6. Update attendee
        │                       │                        │    PARTSTAT
        └───────────────────────┴────────────────────────┘
  1. A background job runs every 10 minutes
  2. For each user with sync enabled, it queries the Mail app's database for recent messages flagged as iMip
  3. For unprocessed messages, it connects via IMAP to fetch the actual email and extract text/calendar MIME parts
  4. It parses the ICS data using the sabre/vobject library
  5. Based on the METHOD (REPLY/REQUEST/CANCEL), it:
    • Searches the user's calendars for the event by UID
    • Only proceeds if the event already exists (for REPLY and CANCEL)
    • Updates the attendee participation status or event data
    • Writes the updated event back via ICreateFromString
  6. Each processed message is tracked to avoid re-processing

Architecture

mail_calendar_sync/
├── appinfo/
│   ├── info.xml              # App metadata & dependencies
│   └── routes.php            # API routes
├── lib/
│   ├── AppInfo/
│   │   └── Application.php   # App bootstrap
│   ├── BackgroundJob/
│   │   └── ProcessImipResponsesJob.php  # Cron job (every 10 min)
│   ├── Controller/
│   │   └── SettingsController.php       # REST API for settings UI
│   ├── Db/
│   │   ├── Config.php                   # User config entity
│   │   ├── ConfigMapper.php             # Config DB mapper
│   │   ├── LogEntry.php                 # Activity log entity
│   │   ├── LogMapper.php                # Log DB mapper
│   │   ├── ProcessedMessage.php         # Processed msg entity
│   │   └── ProcessedMessageMapper.php   # Processed msg DB mapper
│   ├── Migration/
│   │   └── Version1000Date20250209000000.php  # DB schema
│   ├── Service/
│   │   ├── CalendarService.php    # Calendar lookup & update
│   │   ├── MailService.php        # IMAP fetch & ICS extraction
│   │   └── SyncService.php        # Orchestrates the full flow
│   └── Settings/
│       └── PersonalSettings.php   # Settings page registration
├── templates/
│   └── personal-settings.php      # Settings page HTML
├── js/
│   └── personal-settings.js       # Settings page JavaScript
├── css/
│   └── personal-settings.css      # Settings page styles
├── composer.json
└── README.md

Database tables

Table Purpose
oc_mcs_config Per-user configuration (enabled, mail account, calendar, auto-accept)
oc_mcs_log Activity log of all processed responses (kept 30 days)
oc_mcs_processed Tracks which email messages have been processed (kept 90 days)

Troubleshooting

No messages being processed

  • Ensure the Mail app has synced recent messages (check Mail app directly)
  • Verify that incoming invitation responses have text/calendar MIME parts
  • Check that the Mail app flags messages with $imip (this happens during Mail sync)
  • Try clicking "Sync now" in the settings to trigger an immediate scan

Events not being updated

  • The event must already exist in the selected calendar (matched by UID)
  • The attendee email in the response must match an attendee in the existing event
  • Check the activity log in settings for "SKIPPED" entries with explanations

Check server logs

sudo -u www-data php occ log:tail --level=debug | grep -i "mail_calendar_sync\|MailCalendarSync"

License

AGPL-3.0-or-later