Bidirectional task sync between Memos and CalDAV.
Write tasks as Markdown checkboxes in Memos, and they appear as VTODO items in any CalDAV client — Tasks.org, Thunderbird, GNOME To Do, or Apple Reminders. Check them off in your task app, and the checkbox updates in Memos. MemoDAV sits in the middle, reconciling both directions.
Why MemoDAV? — the problem, the gap, and the design principles.
┌──────────┐ webhook ┌──────────┐ CalDAV PUT ┌──────────┐ sync ┌──────────┐
│ │ ────────── │ │ ──────────── │ │ ────── │ │
│ Memos │ │ MemoDAV │ │ Radicale │ │ Tasks.org│
│ │ ────────── │ │ ──────────── │ │ ────── │ │
└──────────┘ API PATCH └──────────┘ hook POST └──────────┘ sync └──────────┘
Memos→CalDAV CalDAV→Memos
MemoDAV uses two event-driven triggers for near-instant sync:
- Memos webhook — Memos fires a POST when a memo changes. MemoDAV parses the tasks and PUTs them to CalDAV.
- Radicale storage hook — Radicale runs a shell script after every CalDAV write. The script POSTs to MemoDAV, which reads the CalDAV state and PATCHes Memos.
A periodic reconciliation loop (configurable, default 5 min) acts as a safety net for missed events.
Tasks are Markdown checkboxes with optional inline metadata, inspired by Todoist Quick Add:
- [ ] Buy groceries 2024-03-15 p1 @shopping #errands
- [x] Renew domain
- [ ] Fix laptop p2 @work| Token | Format | CalDAV mapping | Example |
|---|---|---|---|
| Due date | YYYY-MM-DD |
DUE property |
2024-03-15 |
| Priority | p1, p2, p3 |
PRIORITY (1, 5, 9) |
p1 = high |
| Labels | @word |
CATEGORIES |
@work @urgent |
| List | #word |
Target calendar | #personal |
A standalone #tag line at the top of a memo sets the default calendar for all tasks. Per-task #tags override it.
See docs/SYNTAX.md for the full mapping reference — priority mapping, date formats, escaping, calendar routing, and edge cases.
git clone https://github.com/avinal/memodav.git
cd memodav
cp config.example.yaml config.yaml
# Edit config.yaml with your Memos URL, API key, and CalDAV credentials
docker compose up -dgo build -o memodav ./cmd/memodav
./memodav -config config.yamlUse -debug for verbose logging without editing config.
Requires: Go 1.21+ (uses log/slog). No CGO needed.
See config.example.yaml for all options. Key settings:
| Setting | Description | Default |
|---|---|---|
log_level |
info or debug |
info |
webhook.listen |
HTTP listen address | :8887 |
caldav.url |
Radicale base URL | — |
caldav.default_list |
Fallback calendar name | tasks |
memos.url |
Memos server URL | — |
memos.apikey |
Memos API access token | — |
sync.reconciliation_interval |
Polling interval (0 to disable) | 5m |
Single Go binary. No CGO. Two runtime dependencies: a Memos instance and a CalDAV server (Radicale).
cmd/memodav/ Entry point, HTTP server, startup sequence
internal/
config/ YAML config loading
memos/ Memos REST API client
caldav/ CalDAV HTTP client (raw net/http, no library)
parser/ Markdown ↔ Task conversion
sync/ Bidirectional sync coordinator
db/ SQLite persistence (task mappings, sync state)
webhook/ Memos webhook HTTP handler
scripts/ Radicale storage hook script
docs/ Extended documentation
CalDAV uses raw HTTP instead of a library — we only need PUT, DELETE, REPORT, and MKCALENDAR, and Radicale has XML namespace quirks that trip up generic parsers.
Each task gets a deterministic UID: sha256(memoID + "|" + lineIndex). Reordering lines generates new UIDs — a known limitation with title-hash recovery planned.
See docs/DEPLOYMENT.md for complete setup instructions:
- Docker Compose — MemoDAV + Radicale in one command
- Standalone binary — build and run directly, with a systemd unit file
- Nginx reverse proxy — host both services on a single domain (e.g.
sync.example.com/memodav/andsync.example.com/caldav/) - Radicale configuration — storage hooks, authentication
An example nginx config is provided at docs/nginx.conf.
| Method | Path | Description |
|---|---|---|
| POST | /webhook |
Receives Memos webhook payloads |
| POST | /sync/caldav-changed |
Receives Radicale hook notifications |
| GET | /health |
Liveness probe |
See CONTRIBUTING.md for development setup, code style, and areas where help is welcome.
The back sync and documentation is written heavily using AI, please report if any discrepencies are found.
MIT — Avinal Kumar