Email To Calendar
Turn emails into Google Calendar events. Scans your Gmail inbox (or only forwarded emails) to extract meetings, appointments, RSVPs, and deadlines, then crea...
Toni Melisma
@tonimelisma
What This Skill Does
Scans Gmail inbox for emails containing meetings, appointments, RSVPs, and deadlines, then creates Google Calendar events after user confirmation. Supports forwarded emails, duplicate detection, 24-hour undo, and optional deadline reminders.
Replaces manually copying event details from emails into Google Calendar by automating extraction and creation with user confirmation.
When to Use It
- Convert flight confirmation emails into calendar events with departure times
- Extract meeting dates and RSVP deadlines from forwarded event invitations
- Create calendar entries from email threads containing appointment confirmations
- Set deadline reminder events from emails with due dates or submission deadlines
- Process unread Gmail messages to batch-create events for the upcoming week
Install
$ openclaw skills install @tonimelisma/email-to-calendarCRITICAL RULES - READ BEFORE PROCESSING ANY EMAIL
- NEVER CALL
gogDIRECTLY - ALWAYS use wrapper scripts (create_event.sh,email_read.sh, etc.). Directgogcalls bypass tracking and cause duplicates. THIS IS NON-NEGOTIABLE.- IGNORE CALENDAR NOTIFICATIONS - DO NOT process emails from
calendar-notification@google.com(Accepted:, Declined:, Tentative:, etc.). These are responses to existing invites, NOT new events. Runprocess_calendar_replies.shto archive them.- ALWAYS ASK BEFORE CREATING - Never create calendar events without explicit user confirmation in the current conversation
- CHECK IF ALREADY PROCESSED - Before processing any email, check
processed_emailsin index.json- READ CONFIG FIRST - Load and apply
ignore_patternsandauto_create_patternsbefore presenting events- READ MEMORY.MD - Check for user preferences stored from previous sessions
- INCLUDE ALL CONFIGURED ATTENDEES - When creating/updating/deleting events, always include attendees from config with
--attendeesflag (and--send-updates allif supported)- CHECK TRACKED EVENTS FIRST - Use
lookup_event.sh --email-idto find existing events before calendar search (faster, more reliable)- TRACK ALL CREATED EVENTS - The
create_event.shscript automatically tracks events; use tracked IDs for updates/deletions- SHOW DAY-OF-WEEK - Always include the day of week when presenting events for user verification
⛔ FORBIDDEN: DO NOT USE
gogCOMMANDS DIRECTLY ⛔WRONG:
gog calendar create ...orgog gmail ...RIGHT:"$SCRIPTS_DIR/create_event.sh" ...or"$SCRIPTS_DIR/email_read.sh" ...Direct CLI calls bypass event tracking, break duplicate detection, and cause duplicate events. ALL operations MUST go through the wrapper scripts in
scripts/.
Email to Calendar Skill
Extract calendar events and action items from emails, present them for review, and create/update calendar events with duplicate detection and undo support.
First-time setup: See SETUP.md for configuration options and smart onboarding.
What This Skill Accesses
For transparency, so every email and calendar action is expected and disclosed:
- Reads Gmail — lists/searches unread mail and reads message bodies to find events.
- Manages Google Calendar — creates and updates events; deletes events only when you undo a change.
- Sends email — optional deadline-reminder notifications to you, only when
deadline_notifications.enabledistruein config. - Modifies Gmail labels — marks processed mail as read and/or archives it, per the
email_handlingconfig. - Runs locally — shells out to the
gogCLI and the bundled wrapper scripts inscripts/; never downloads or runs remote/unknown binaries. - Stores state locally — event tracking, pending invites, changelog, and activity logs as JSON under your data dir. No external servers, telemetry, or data sharing.
Event creation always requires explicit confirmation, and changes are undoable within 24 hours.
Reading Email Content
IMPORTANT: Before you can extract events, you must read the email body. Use the wrapper scripts.
SCRIPTS_DIR="$HOME/.openclaw/workspace/skills/email-to-calendar/scripts"
# Get a single email by ID (PREFERRED)
"$SCRIPTS_DIR/email_read.sh" --email-id "<messageId>"
# Search with body content included
"$SCRIPTS_DIR/email_search.sh" --query "in:inbox is:unread" --max 20 --include-body
Note on stale forwards: Don't use newer_than:1d because it checks the email's original date header, not when it was received. Process all UNREAD emails and rely on the "already processed" check.
Workflow
0. Pre-Processing Checks (MANDATORY)
SCRIPTS_DIR="$HOME/.openclaw/workspace/skills/email-to-calendar/scripts"
CONFIG_FILE="$HOME/.config/email-to-calendar/config.json"
INDEX_FILE="$HOME/.openclaw/workspace/memory/email-extractions/index.json"
# Start activity logging
"$SCRIPTS_DIR/activity_log.sh" start-session
# Check email mode
EMAIL_MODE=$(jq -r '.email_mode // "forwarded"' "$CONFIG_FILE")
# Check if email was already processed
EMAIL_ID="<the email message ID>"
if jq -e ".extractions[] | select(.email_id == \"$EMAIL_ID\")" "$INDEX_FILE" > /dev/null 2>&1; then
"$SCRIPTS_DIR/activity_log.sh" log-skip --email-id "$EMAIL_ID" --subject "Subject" --reason "Already processed"
exit 0
fi
# Load ignore/auto-create patterns
IGNORE_PATTERNS=$(jq -r '.event_rules.ignore_patterns[]' "$CONFIG_FILE")
AUTO_CREATE_PATTERNS=$(jq -r '.event_rules.auto_create_patterns[]' "$CONFIG_FILE")
1. Find Emails to Process
DIRECT mode: Scan all unread emails for event indicators (dates, times, meeting keywords).
FORWARDED mode: Only process emails with forwarded indicators (Fwd:, forwarded message headers).
2. Extract Events (Agent does this directly)
Read the email and extract events as structured data. Include for each event:
- title: Descriptive name (max 80 chars)
- date: Event date(s)
- day_of_week: For verification
- time: Start/end times (default: 9 AM - 5 PM)
- is_multi_day: Whether it spans multiple days
- is_recurring: Whether it repeats (and pattern)
- confidence: high/medium/low
- urls: Any URLs found in the email (REQUIRED - always look for registration links, info pages, ticketing sites, etc.)
- deadline_date: RSVP/registration/ticket deadline date (if found)
- deadline_action: What user needs to do (e.g., "RSVP", "get tickets", "register")
- deadline_url: Direct link for taking action (often same as event URL)
URL Extraction Rule: ALWAYS scan the email for URLs and include the most relevant one at the BEGINNING of the event description.
2.1 Deadline Detection
Scan the email for deadline patterns that indicate action is required before the event:
Common Deadline Patterns:
- "RSVP by [date]", "Please RSVP by [date]"
- "Register by [date]", "Registration closes [date]"
- "Tickets available until [date]", "Get tickets by [date]"
- "Early bird ends [date]", "Early registration deadline [date]"
- "Must respond by [date]", "Respond by [date]"
- "Sign up by [date]", "Sign up deadline [date]"
- "Deadline: [date]", "Due by [date]"
- "Last day to [action]: [date]"
When a deadline is found:
- Extract the deadline date
- Determine the required action (RSVP, register, buy tickets, etc.)
- Find the URL for taking that action
- Flag the event for special handling (see sections below)
3. Present Items to User and WAIT
Apply event rules, then present with numbered selection:
I found the following potential events:
1. ~~ELAC Meeting (Feb 2, Monday at 8:15 AM)~~ - SKIP (matches ignore pattern)
2. **Team Offsite (Feb 2-6, Sun-Thu)** - PENDING
3. **Staff Development Day (Feb 12, Wednesday)** - AUTO-CREATE
Reply with numbers to create (e.g., '2, 3'), 'all', or 'none'.
STOP AND WAIT for user response.
After presenting, record pending invites for follow-up reminders:
# Record pending invites using add_pending.sh
"$SCRIPTS_DIR/add_pending.sh" \
--email-id "$EMAIL_ID" \
--email-subject "$EMAIL_SUBJECT" \
--events-json '[{"title":"Event Name","date":"2026-02-15","time":"14:00","status":"pending"}]'
4. Check for Duplicates (MANDATORY)
ALWAYS check before creating any event:
# Step 1: Check local tracking first (fast)
TRACKED=$("$SCRIPTS_DIR/lookup_event.sh" --email-id "$EMAIL_ID")
if [ "$(echo "$TRACKED" | jq 'length')" -gt 0 ]; then
EXISTING_EVENT_ID=$(echo "$TRACKED" | jq -r '.[0].event_id')
fi
# Step 2: If not found, try summary match
if [ -z "$EXISTING_EVENT_ID" ]; then
TRACKED=$("$SCRIPTS_DIR/lookup_event.sh" --summary "$EVENT_TITLE")
fi
# Step 3: Fall back to calendar search using wrapper script
if [ -z "$EXISTING_EVENT_ID" ]; then
"$SCRIPTS_DIR/calendar_search.sh" --calendar-id "$CALENDAR_ID" --from "${EVENT_DATE}T00:00:00" --to "${EVENT_DATE}T23:59:59"
fi
Use LLM semantic matching for fuzzy duplicates (e.g., "Team Offsite" vs "Team Offsite 5-6pm").
5. Create or Update Calendar Events
Use create_event.sh (recommended) - handles date parsing, tracking, and changelog:
# Create new event
"$SCRIPTS_DIR/create_event.sh" \
"$CALENDAR_ID" \
"Event Title" \
"February 11, 2026" \
"9:00 AM" \
"5:00 PM" \
"Description" \
"$ATTENDEE_EMAILS" \
"" \
"$EMAIL_ID"
# Update existing event (pass event_id as 8th parameter)
"$SCRIPTS_DIR/create_event.sh" \
"$CALENDAR_ID" \
"Updated Title" \
"February 11, 2026" \
"10:00 AM" \
"6:00 PM" \
"Updated description" \
"$ATTENDEE_EMAILS" \
"$EXISTING_EVENT_ID" \
"$EMAIL_ID"
For direct gog commands and advanced options, see references/gog-commands.md.
6. Email Disposition (Automatic)
Email disposition (mark as read and/or archive) is handled automatically by create_event.sh based on config settings. No manual step needed - emails are dispositioned after event creation.
To manually disposition an email:
"$SCRIPTS_DIR/disposition_email.sh" --email-id "$EMAIL_ID"
To process calendar reply emails (accepts, declines, tentatives):
"$SCRIPTS_DIR/process_calendar_replies.sh" # Process all
"$SCRIPTS_DIR/process_calendar_replies.sh" --dry-run # Preview only
# End activity session
"$SCRIPTS_DIR/activity_log.sh" end-session
Event Creation Rules
Date/Time Handling
- Single-day events: Default 9:00 AM - 5:00 PM
- Multi-day events (e.g., Feb 2-6): Use
--rrule "RRULE:FREQ=DAILY;COUNT=N" - Events with specific times: Use exact time from email
Event Descriptions
Format event descriptions in this order:
-
ACTION WARNING (if deadline exists):
*** ACTION REQUIRED: [ACTION] BY [DATE] *** -
Event Link (if URL found):
Event Link: [URL] -
Event Details: Information extracted from the email
Example WITH deadline:
*** ACTION REQUIRED: GET TICKETS BY FEB 15 ***
Event Link: https://example.com/tickets
Spring Concert at Downtown Theater
Doors open at 7 PM
VIP meet & greet available
Example WITHOUT deadline:
Event Link: https://example.com/event
Spring Concert at Downtown Theater
Doors open at 7 PM
Duplicate Detection
Consider it a duplicate if:
- Same date AND similar title (semantic matching) AND overlapping time
Always update existing events rather than creating duplicates.
Creating Deadline Events
When an event has a deadline (RSVP, registration, ticket purchase, etc.), create TWO calendar events:
1. Main Event (as normal, but with warning in description):
"$SCRIPTS_DIR/create_event.sh" \
"$CALENDAR_ID" \
"Spring Concert" \
"March 1, 2026" \
"7:00 PM" \
"10:00 PM" \
"*** ACTION REQUIRED: GET TICKETS BY FEB 15 ***
Event Link: https://example.com/tickets
Spring Concert at Downtown Theater
Doors open at 7 PM" \
"$ATTENDEE_EMAILS" \
"" \
"$EMAIL_ID"
2. Deadline Reminder Event (separate event on the deadline date):
# Use create_event.sh for deadline reminders too (ensures tracking)
"$SCRIPTS_DIR/create_event.sh" \
"$CALENDAR_ID" \
"DEADLINE: Get tickets for Spring Concert" \
"2026-02-15" \
"09:00" \
"09:30" \
"Action required: Get tickets
Event Link: https://example.com/tickets
Main event: Spring Concert on March 1, 2026" \
"" \
"" \
"$EMAIL_ID"
Deadline Event Properties:
- Title format:
DEADLINE: [Action] for [Event Name] - Date: The deadline date
- Time: 9:00 AM (30 minute duration)
- Reminders: Email 1 day before + popup 1 hour before
- Description: Action required, URL, reference to main event
Email Notifications for Deadlines
When creating events with deadlines, send a notification email to alert the user:
# Load config
CONFIG_FILE="$HOME/.config/email-to-calendar/config.json"
USER_EMAIL=$(jq -r '.deadline_notifications.email_recipient // .gmail_account' "$CONFIG_FILE")
NOTIFICATIONS_ENABLED=$(jq -r '.deadline_notifications.enabled // false' "$CONFIG_FILE")
# Send notification if enabled (using wrapper script)
if [ "$NOTIFICATIONS_ENABLED" = "true" ]; then
"$SCRIPTS_DIR/email_send.sh" \
--to "$USER_EMAIL" \
--subject "ACTION REQUIRED: Get tickets for Spring Concert by Feb 15" \
--body "A calendar event has been created that requires your action.
Event: Spring Concert
Date: March 1, 2026
Deadline: February 15, 2026
Action Required: Get tickets
Link: https://example.com/tickets
Calendar events created:
- Main event: Spring Concert (March 1)
- Deadline reminder: DEADLINE: Get tickets for Spring Concert (Feb 15)
---
This notification was sent by the email-to-calendar skill."
fi
When to send notifications:
- Only when
deadline_notifications.enabledistruein config - Only for events that have action-required deadlines
- Include the deadline date, action, URL, and event details
Activity Log
# Start session
"$SCRIPTS_DIR/activity_log.sh" start-session
# Log skipped emails
"$SCRIPTS_DIR/activity_log.sh" log-skip --email-id "abc" --subject "Newsletter" --reason "No events"
# Log events
"$SCRIPTS_DIR/activity_log.sh" log-event --email-id "def" --title "Meeting" --action created
# End session
"$SCRIPTS_DIR/activity_log.sh" end-session
# Show recent activity
"$SCRIPTS_DIR/activity_log.sh" show --last 3
Changelog and Undo
Changes can be undone within 24 hours:
# List recent changes
"$SCRIPTS_DIR/changelog.sh" list --last 10
# List undoable changes
"$SCRIPTS_DIR/undo.sh" list
# Undo most recent change
"$SCRIPTS_DIR/undo.sh" last
# Undo specific change
"$SCRIPTS_DIR/undo.sh" --change-id "chg_20260202_143000_001"
Pending Invites
Events not immediately actioned are tracked for reminders:
# Add pending invites (after presenting events to user)
"$SCRIPTS_DIR/add_pending.sh" \
--email-id "$EMAIL_ID" \
--email-subject "Party Invite" \
--events-json '[{"title":"Birthday Party","date":"2026-02-15","time":"14:00","status":"pending"}]'
# List pending invites (JSON)
"$SCRIPTS_DIR/list_pending.sh"
# Human-readable summary
"$SCRIPTS_DIR/list_pending.sh" --summary
# Update reminder tracking
"$SCRIPTS_DIR/list_pending.sh" --summary --update-reminded
# Auto-dismiss after 3 ignored reminders
"$SCRIPTS_DIR/list_pending.sh" --summary --auto-dismiss
Event Tracking
# Look up by email ID
"$SCRIPTS_DIR/lookup_event.sh" --email-id "19c1c86dcc389443"
# Look up by summary
"$SCRIPTS_DIR/lookup_event.sh" --summary "Staff Development"
# List all tracked events
"$SCRIPTS_DIR/lookup_event.sh" --list
# Validate events exist (removes orphans)
"$SCRIPTS_DIR/lookup_event.sh" --email-id "abc" --validate
File Locations
| File | Purpose |
|---|---|
~/.config/email-to-calendar/config.json | User configuration |
~/.openclaw/workspace/memory/email-extractions/ | Extracted data |
~/.openclaw/workspace/memory/email-extractions/index.json | Processing index |
~/.openclaw/workspace/memory/email-to-calendar/events.json | Event tracking |
~/.openclaw/workspace/memory/email-to-calendar/pending_invites.json | Pending invites |
~/.openclaw/workspace/memory/email-to-calendar/activity.json | Activity log |
~/.openclaw/workspace/memory/email-to-calendar/changelog.json | Change history |
~/.openclaw/workspace/skills/email-to-calendar/scripts/ | Utility scripts |
~/.openclaw/workspace/skills/email-to-calendar/MEMORY.md | User preferences |
References
- Setup Guide: SETUP.md - Configuration and onboarding
- CLI Reference: references/gog-commands.md - Detailed gog CLI usage
- Extraction Patterns: references/extraction-patterns.md - Date/time parsing
- Workflow Example: references/workflow-example.md - Complete example
Notes
Date Parsing
Handles common formats:
- January 15, 2026, Wednesday January 15
- 01/15/2026, 15/01/2026
- Date ranges like "Feb 2-6"
Time Zones
All times assumed local timezone. Time zone info preserved in descriptions.
Top skills in this category
gmail
@byungkyuGmail API integration with managed OAuth. Read, send, and manage emails, threads, labels, and drafts. Use this skill when users want to interact with Gmail. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Calls run through the `maton` CLI with OAuth login, or over raw HTTP with a Maton API key where the CLI cannot be installed. Every call is authenticated as the user's connection and reaches only what that connection's authorization allows, which the provider enforces on every request; the endpoints documented here are the ones this skill uses, and any other endpoint of this app needs the user to ask for it by name. Default to read and list calls, and confirm every write or new connection with the user. This file also documents the three constructs that turn a Gmail connection into automation, in the order they are used: the connection (the first step), a hosted function that runs a Gmail action through the Maton SDK, and a trigger that calls that function on a schedule or on an event. Those sections are the platform's own reference text, shared with the api-gateway skill, with Gmail examples; they add no Gmail capability - Gmail is itself an event source, so its own events are in `references/google-mail/triggers.md`, and the files under `references/<source>/triggers.md` are the platform's event catalogues for the sources Maton offers (time, Calendly, GitHub, Gmail, HubSpot, Linear, Notion, Slack, Stripe).
WhatsApp Business
@byungkyuWhatsApp Business API integration with managed OAuth. Send messages, manage templates, and handle conversations. Use this skill when users want to interact with WhatsApp Business. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Calls run through the `maton` CLI with OAuth login, or over raw HTTP with a Maton API key where the CLI cannot be installed. Every call is authenticated as the user's connection and reaches only what that connection's authorization allows, which the provider enforces on every request; the endpoints documented here are the ones this skill uses, and any other endpoint of this app needs the user to ask for it by name. Default to read and list calls, and confirm every write or new connection with the user. This file also documents the three constructs that turn a WhatsApp Business connection into automation, in the order they are used: the connection (the first step), a hosted function that runs a WhatsApp Business action through the Maton SDK, and a trigger that calls that function on a schedule or on an event. Those sections are the platform's own reference text, shared with the api-gateway skill, with WhatsApp Business examples; they add no WhatsApp Business capability - WhatsApp Business is not an event source, a trigger cannot read WhatsApp Business data, and the files under `references/<source>/triggers.md` are the platform's event catalogues for the sources Maton offers (time, Calendly, GitHub, Gmail, HubSpot, Linear, Notion, Slack, Stripe).
WeChat MP CN
@guohongbin-git微信公众号监控 - 文章监控、阅读量追踪、舆情分析(WeChat Official Account)
Wechat Article Search
@wuchubuzai2018搜索微信公众号文章技能。通过微信搜索获取文章列表,覆盖科技/AI、社会热点、财经、教育、职场等各类中文资讯;可按关键词检索并返回标题、概要、发布时间、来源公众号与链接。当用户需要查找微信公众号文章、整理参考资料或快速获取文章信息时使用此技能。
Wechat Publisher
@0731coderlee-sudo一键发布 Markdown 到微信公众号草稿箱。基于 wenyan-cli,支持多主题、代码高亮、图片自动上传。