Files
youlu-openclaw-workspace/skills/calendar-invite/SKILL.md
Yanxin Lu b8ba4adec5 calendar-invite: add VALARM reminder, fix terminology, remove dead code
- Add 1-day reminder (VALARM) to all sent invites
- Fix datetime.utcnow() deprecation → datetime.now(timezone.utc)
- Rename "message ID" → "envelope ID" in SKILL.md for consistency
- Remove unused _himalaya() and _himalaya_with_account() helpers
2026-03-18 14:44:08 -07:00

8.5 KiB

name, description, metadata
name description metadata
calendar-invite Send, accept, and decline calendar invite emails (ICS/iCalendar) via himalaya. Syncs events to CalDAV (Migadu) via vdirsyncer.
clawdbot
emoji requires
📅
bins skills
himalaya
vdirsyncer
himalaya

Calendar Invite

Send, accept, and decline calendar invitations via email using himalaya. Events are saved to local calendar and synced to CalDAV (Migadu) via vdirsyncer.

Testing

See TESTING.md for dry-run and live test steps, verification checklists, and troubleshooting.

Prerequisites

  • himalaya configured and working (see the himalaya skill)
  • vdirsyncer configured and syncing to ~/.openclaw/workspace/calendars/
  • khal for reading calendar (optional but recommended)
  • Runs via uv run (dependencies managed in pyproject.toml)

Important: Email Sending Rules

Calendar invites are outbound emails. Follow the workspace email rules:

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:

SKILL_DIR=~/.openclaw/workspace/skills/calendar-invite

# Send an invite
$SKILL_DIR/scripts/calendar-invite.sh send [options]

# Reply to an invite
$SKILL_DIR/scripts/calendar-invite.sh reply [options]

Sending Invites

$SKILL_DIR/scripts/calendar-invite.sh send \
  --to "friend@example.com" \
  --subject "Lunch on Friday" \
  --summary "Lunch at Tartine" \
  --start "2026-03-20T12:00:00" \
  --end "2026-03-20T13:00:00" \
  --location "Tartine Bakery, SF"

Send Options

Flag Required Description
--to Yes Recipient(s), comma-separated
--subject Yes Email subject line
--summary Yes Event title (shown on calendar)
--start Yes Start time, ISO 8601 (2026-03-20T14:00:00)
--end Yes End time, ISO 8601 (2026-03-20T15:00:00)
--from No Sender email (default: youlu@luyanxin.com)
--timezone No IANA timezone (default: America/Los_Angeles)
--location No Event location
--description No Event description / notes
--organizer No Organizer display name (defaults to --from)
--uid No Custom event UID (auto-generated if omitted)
--account No Himalaya account name (if not default)
--dry-run No Print ICS + MIME without sending

Send Examples

# Simple invite (--from and --timezone default to youlu@luyanxin.com / LA)
$SKILL_DIR/scripts/calendar-invite.sh send \
  --to "alice@example.com" \
  --subject "Coffee Chat" \
  --summary "Coffee Chat" \
  --start "2026-03-25T10:00:00" \
  --end "2026-03-25T10:30:00"

# Multiple attendees with details
$SKILL_DIR/scripts/calendar-invite.sh send \
  --to "alice@example.com, bob@example.com" \
  --subject "Team Sync" \
  --summary "Weekly Team Sync" \
  --start "2026-03-23T09:00:00" \
  --end "2026-03-23T09:30:00" \
  --location "Zoom - https://zoom.us/j/123456" \
  --description "Weekly check-in. Agenda: updates, blockers, action items."

# Dry run
$SKILL_DIR/scripts/calendar-invite.sh send \
  --to "test@example.com" \
  --subject "Test" \
  --summary "Test Event" \
  --start "2026-04-01T15:00:00" \
  --end "2026-04-01T16:00:00" \
  --dry-run

Replying to Invites

# Accept by himalaya envelope ID
$SKILL_DIR/scripts/calendar-invite.sh reply \
  --envelope-id 42 \
  --action accept

# Decline with a comment
$SKILL_DIR/scripts/calendar-invite.sh reply \
  --envelope-id 42 \
  --action decline \
  --comment "Sorry, I have a conflict."

# From an .ics file
$SKILL_DIR/scripts/calendar-invite.sh reply \
  --ics-file ~/Downloads/meeting.ics \
  --action tentative

Reply Options

Flag Required Description
--action Yes accept, decline, or tentative
--envelope-id * Himalaya envelope ID containing the .ics attachment
--ics-file * Path to an .ics file (alternative to --envelope-id)
--from No Your email (default: youlu@luyanxin.com)
--account No Himalaya account name
--folder No Himalaya folder (default: INBOX)
--comment No Optional message to include in reply
--dry-run No Preview without sending

* One of --envelope-id or --ics-file is required.

Typical Workflow

  1. List emails: himalaya envelope list
  2. Read the invite: himalaya message read 57
  3. Reply: $SKILL_DIR/scripts/calendar-invite.sh reply --envelope-id 57 --action accept

How It Works

Sending invites:

  1. Generates an RFC 5545 ICS file with METHOD:REQUEST (via icalendar library)
  2. Builds a MIME email with a text/calendar attachment (via Python email.mime)
  3. Sends via himalaya message send (piped through stdin)
  4. Saves the event to ~/.openclaw/workspace/calendars/home/
  5. Runs vdirsyncer sync to push to Migadu CalDAV

Replying to invites:

  1. Extracts the .ics attachment from the email (via himalaya attachment download)
  2. Parses the original event with the icalendar library
  3. Generates a reply ICS with METHOD:REPLY and the correct PARTSTAT
  4. Sends the reply to the organizer via himalaya message send (stdin)
  5. On accept/tentative: saves event to local calendar. On decline: removes it
  6. Runs vdirsyncer sync to push changes to Migadu CalDAV

CalDAV sync:

  • Events sync to Migadu and appear on all connected devices (DAVx5, etc.)
  • Heartbeat runs vdirsyncer sync periodically as a fallback
  • If sync fails, it warns but doesn't block — next heartbeat catches up

Integration with Email Processor

The email processor (scripts/email_processor/) may classify incoming calendar invites as reminder or confirmation. When reviewing pending emails:

  1. Check if the email contains a calendar invite (look for .ics attachment or "calendar" in subject)
  2. If it does, use reply instead of the email processor's delete/archive/keep actions
  3. The email processor handles the email lifecycle; this skill handles the calendar response

Checking the Calendar

# List upcoming events (next 7 days)
khal list today 7d

# List events for a specific date
khal list 2026-03-25

# Check for conflicts before sending an invite
khal list 2026-03-25 2026-03-26

Timezone Reference

Common IANA timezones:

  • America/Los_Angeles — Pacific (default)
  • America/Denver — Mountain
  • America/Chicago — Central
  • America/New_York — Eastern
  • Asia/Shanghai — China
  • Asia/Tokyo — Japan
  • Europe/London — UK
  • UTC — Coordinated Universal Time

Troubleshooting

Invite shows as attachment instead of calendar event?

  • Ensure the MIME part has Content-Type: text/calendar; method=REQUEST
  • Some clients require the METHOD:REQUEST line in the ICS body

Times are wrong?

  • Double-check --timezone matches the intended timezone
  • Use ISO 8601 format: YYYY-MM-DDTHH:MM:SS (no timezone offset in the value)

Event not showing on phone/other devices?

  • Run vdirsyncer sync manually to force sync
  • Check ~/.openclaw/workspace/logs/vdirsyncer.log for errors
  • Verify the .ics file exists in ~/.openclaw/workspace/calendars/home/

Recipient doesn't see Accept/Decline?

  • Gmail, Outlook, Apple Mail all support text/calendar method=REQUEST
  • Some webmail clients may vary