calendar: remove self-notification emails (CalDAV sync replaces them)

Remove three redundant email flows since all devices sync via CalDAV:
- Auto-adding mail@luyx.org as attendee on outgoing invites
- Forwarding invites to mail@luyx.org on accept/tentative
- Emailing todos to mail@luyx.org on todo add
This commit is contained in:
Yanxin Lu
2026-03-23 14:27:28 -07:00
parent 48e179c8de
commit ab9f7da592
5 changed files with 12 additions and 66 deletions

View File

@@ -5,7 +5,7 @@ _这份文件记录持续性项目和重要状态跨会话保留。_
## 📝 重要规则
### 邮件发送规则v2
- **youlu@luyanxin.com → mail@luyx.org**: 直接发送,无需确认(用户 SimpleLogin 别名,日历邀请自动抄送
- **youlu@luyanxin.com → mail@luyx.org**: 直接发送,无需确认(用户 SimpleLogin 别名)
- 其他所有对外邮件: 仍需确认
### 代码审查规则
@@ -88,8 +88,8 @@ _这份文件记录持续性项目和重要状态跨会话保留。_
- 运行方式: `uv run`(依赖 `icalendar` 库)
**功能**:
- 发送日历邀请(自动添加 mail@luyx.org 为参与者)
- 接受/拒绝/暂定回复邀请(自动转发给 mail@luyx.org
- 发送日历邀请给外部收件人
- 接受/拒绝/暂定回复邀请(回复给 organizer
- VTODO 待办管理add/list/complete/delete/check
- 发送/回复/待办操作后自动 `vdirsyncer sync` 同步到 CalDAV
- 心跳定期同步日历

View File

@@ -154,8 +154,7 @@ khal list today 7d
**支持操作**: 发送邀请 (`send`)、接受/拒绝/暂定 (`reply`)、待办管理 (`todo add/list/complete/delete/check`)
**依赖**: himalaya邮件、vdirsyncerCalDAV 同步、khal查看日历、todoman待办管理
**同步**: 发送/回复后自动 `vdirsyncer sync`,心跳也会定期同步
**自动抄送**: mail@luyx.org用户别名自动加入所有邀请
**注意**: 发送日历邀请属于对外邮件,除 mail@luyx.org 外需先确认
**注意**: 发送日历邀请属于对外邮件,需先确认
---

View File

@@ -26,12 +26,6 @@ Calendar invites are outbound emails. Follow the workspace email rules:
- **youlu@luyanxin.com -> mail@luyx.org**: send directly, no confirmation needed
- **All other recipients**: confirm with user before sending
## Owner Auto-Attendee
When sending invites, `mail@luyx.org` (owner's SimpleLogin alias) is **always added as an attendee automatically**. All invites include a **1-day reminder** (VALARM) by default. This ensures the owner receives every invite and can Accept/Decline from their own email client. No need to include it in `--to` — it's added by the script.
When accepting or tentatively accepting a received invite, the original invite is **automatically forwarded to `mail@luyx.org`** so the event lands on the owner's calendar too.
## Usage
All commands go through the wrapper script:
@@ -167,7 +161,7 @@ Manage tasks as RFC 5545 VTODO components, stored in `~/.openclaw/workspace/cale
### Sync Model
The agent's local CalDAV is the **source of truth** (no two-way sync). When a todo is created, it's saved locally and emailed to `mail@luyx.org` as a delivery copy. When the user completes a task, they tell the agent, and the agent runs `todo complete`. The daily `todo check` cron reads from local files.
The agent's local CalDAV is the **source of truth**. When a todo is created, it's saved locally and synced to Migadu CalDAV via vdirsyncer — all connected devices (DAVx5, etc.) pick it up automatically. When the user completes a task, they tell the agent, and the agent runs `todo complete`. The daily `todo check` cron reads from local files.
### Priority Mapping (RFC 5545)
@@ -195,7 +189,7 @@ $SKILL_DIR/scripts/calendar.sh todo add \
| `--priority` | No | `high`, `medium`, or `low` (default: `medium`) |
| `--description` | No | Notes / description |
| `--alarm` | No | Reminder trigger: `1d`, `2h`, `30m` (default: `1d`) |
| `--dry-run` | No | Preview ICS + email without saving |
| `--dry-run` | No | Preview ICS without saving |
### `todo list` — List Todos
@@ -246,7 +240,7 @@ Same as `todo list` but only NEEDS-ACTION items. Exits silently when no pending
6. Runs `vdirsyncer sync` to push changes to Migadu CalDAV
**Managing todos:**
1. `todo add`: Creates a VTODO ICS file (via `icalendar` library), saves to `calendars/tasks/`, emails to owner, syncs
1. `todo add`: Creates a VTODO ICS file (via `icalendar` library), saves to `calendars/tasks/`, syncs to CalDAV
2. `todo list/complete/delete/check`: Delegates to `todoman` CLI for robust RFC 5545 VTODO parsing
3. Runs `vdirsyncer sync` after mutations to push changes to Migadu CalDAV

View File

@@ -16,7 +16,7 @@ TEST_DATE=$(date -d "+3 days" +%Y-%m-%d)
Generates the ICS and MIME email without sending. Check that:
- ICS has `METHOD:REQUEST`
- MIME has `Content-Type: text/calendar; method=REQUEST`
- `mail@luyx.org` appears as attendee (auto-added)
- Only `--to` recipients appear as attendees
- Times and timezone look correct
```bash
@@ -89,8 +89,7 @@ $SKILL_DIR/scripts/calendar.sh reply \
**Verify:**
- [ ] Reply sent to organizer (youlu@luyanxin.com, i.e. ourselves)
- [ ] Original invite forwarded to `mail@luyx.org`
- [ ] Event still in `~/.openclaw/workspace/calendars/home/`
- [ ] Event saved to `~/.openclaw/workspace/calendars/home/`
- [ ] `vdirsyncer sync` ran
- [ ] `khal list "$TEST_DATE"` still shows the event
@@ -119,7 +118,6 @@ $SKILL_DIR/scripts/calendar.sh reply \
**Verify:**
- [ ] Reply sent to organizer with comment
- [ ] Event NOT forwarded to `mail@luyx.org`
- [ ] Event removed from local calendar
- [ ] `khal list "$TEST_DATE"` does NOT show "Decline Test Event"
@@ -142,12 +140,11 @@ khal list today 7d
## 7. Dry Run: Add Todo
Generates the VTODO ICS and MIME email without saving. Check that:
Generates the VTODO ICS without saving. Check that:
- ICS has `BEGIN:VTODO`
- ICS has correct `PRIORITY` value (1 for high)
- ICS has `STATUS:NEEDS-ACTION`
- ICS has `BEGIN:VALARM`
- MIME has `Content-Type: text/calendar`
```bash
$SKILL_DIR/scripts/calendar.sh todo add \
@@ -171,7 +168,6 @@ $SKILL_DIR/scripts/calendar.sh todo add \
- [ ] Script exits without error
- [ ] `.ics` file created in `~/.openclaw/workspace/calendars/tasks/`
- [ ] `todo list` (todoman directly) shows "Test Todo"
- [ ] Email arrives at `mail@luyx.org` with .ics attachment
- [ ] `vdirsyncer sync` ran
## 9. List Todos

View File

@@ -34,7 +34,6 @@ from icalendar import Alarm, Calendar, Event, Todo, vCalAddress, vText
DEFAULT_TIMEZONE = "America/Los_Angeles"
DEFAULT_FROM = "youlu@luyanxin.com"
DEFAULT_OWNER_EMAIL = "mail@luyx.org" # Always added as attendee
CALENDAR_DIR = Path.home() / ".openclaw" / "workspace" / "calendars" / "home"
TASKS_DIR = Path.home() / ".openclaw" / "workspace" / "calendars" / "tasks"
PRODID = "-//OpenClaw//Calendar//EN"
@@ -148,12 +147,7 @@ def cmd_send(args):
recipients = [addr.strip() for addr in args.to.split(",")]
# Always include owner as attendee
all_attendees = list(recipients)
if DEFAULT_OWNER_EMAIL not in all_attendees:
all_attendees.append(DEFAULT_OWNER_EMAIL)
for addr in all_attendees:
for addr in recipients:
event.add("attendee", f"mailto:{addr}", parameters={
"ROLE": "REQ-PARTICIPANT",
"RSVP": "TRUE",
@@ -176,11 +170,8 @@ def cmd_send(args):
if args.description:
body += f"\n\n{args.description}"
# Email goes to all attendees (including owner)
all_to = ", ".join(all_attendees)
# Build MIME email
email_str = _build_calendar_email(args.sender, all_to, args.subject, body, ics_bytes, method="REQUEST")
email_str = _build_calendar_email(args.sender, ", ".join(recipients), args.subject, body, ics_bytes, method="REQUEST")
if args.dry_run:
print("=== ICS Content ===")
@@ -352,20 +343,6 @@ def cmd_reply(args):
_send_email(email_str, args.account)
print(f"Calendar invite {partstat.lower()}: {summary} (replied to {organizer_email})")
# Forward invite to owner on accept/tentative
if partstat in ("ACCEPTED", "TENTATIVE"):
fwd_body = f"{prefix}: {summary}"
fwd_email = _build_calendar_email(
args.sender, DEFAULT_OWNER_EMAIL,
f"{prefix}: {summary}", fwd_body,
ics_path.read_bytes(), method="REQUEST",
)
try:
_send_email(fwd_email, args.account)
print(f"Forwarded invite to {DEFAULT_OWNER_EMAIL}")
except subprocess.CalledProcessError:
print(f"Warning: Failed to forward invite to {DEFAULT_OWNER_EMAIL}")
# Save to / remove from local calendar
if CALENDAR_DIR.is_dir():
dest = CALENDAR_DIR / f"{uid}.ics"
@@ -526,24 +503,11 @@ def cmd_todo_add(args):
cal.add_component(todo)
ics_bytes = cal.to_ical()
# Build email body
prio_label = PRIORITY_LABELS.get(priority, "")
body = f"待办事项: {args.summary}\n截止日期: {due_date}\n优先级: {prio_label}"
if args.description:
body += f"\n\n{args.description}"
# Build MIME email
email_str = _build_calendar_email(
DEFAULT_FROM, DEFAULT_OWNER_EMAIL,
f"📋 待办: {args.summary}",
body, ics_bytes, method="REQUEST",
)
if args.dry_run:
print("=== ICS Content ===")
print(ics_bytes.decode())
print("=== Email Message ===")
print(email_str)
return
# Save to TASKS_DIR (without METHOD for CalDAV)
@@ -555,13 +519,6 @@ def cmd_todo_add(args):
# Sync
_sync_calendar()
# Email the VTODO to owner
try:
_send_email(email_str)
print(f"Emailed todo to {DEFAULT_OWNER_EMAIL}")
except subprocess.CalledProcessError:
print(f"Warning: Failed to email todo to {DEFAULT_OWNER_EMAIL}")
def _format_todo_digest(todos):
"""Format todos into the Chinese priority-grouped digest. Returns string or None."""