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

170 lines
7.0 KiB
Markdown

# 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](https://apps.nextcloud.com/apps/mail) 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:
```bash
cp -r mail_calendar_sync /var/www/nextcloud/apps/
```
2. Set proper permissions:
```bash
chown -R www-data:www-data /var/www/nextcloud/apps/mail_calendar_sync
```
3. Enable the app:
```bash
sudo -u www-data php occ app:enable mail_calendar_sync
```
4. Run the database migration:
```bash
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
```bash
sudo -u www-data php occ log:tail --level=debug | grep -i "mail_calendar_sync\|MailCalendarSync"
```
## License
AGPL-3.0-or-later