This script looks for recent meetings in Supersonic, identifies attendees, and creates follow-up tasks for each one. Run it daily on cron, or trigger it from a webhook after each meeting ends.
How it works
- Search the timeline for meetings in the last 24 hours.
- For each meeting, extract the attendees.
- Create a follow-up task linked to each attendee with a due date 2 days out.
- Skip meetings that already have follow-up tasks (tracked via a state file).
The script
#!/usr/bin/env python3
"""
Meeting follow-up task creator.
Finds recent meetings, creates follow-up tasks for attendees.
Env vars: SUPERSONIC_API_KEY
State file: ~/.supersonic_meeting_followups.json
"""
import json
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
import httpx
API_URL = "https://mcp.supersonic.cv/api/developers/mcp/call/"
API_KEY = os.environ["SUPERSONIC_API_KEY"]
STATE_FILE = Path.home() / ".supersonic_meeting_followups.json"
FOLLOWUP_DAYS = 2 # due date = meeting date + N days
def api_call(tool: str, params: dict) -> dict:
resp = httpx.post(
API_URL,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={"tool": tool, "params": params},
timeout=15.0,
)
resp.raise_for_status()
return resp.json()
def load_processed() -> set:
"""Load IDs of meetings we've already processed."""
if STATE_FILE.exists():
data = json.loads(STATE_FILE.read_text())
return set(data.get("processed", []))
return set()
def save_processed(ids: set):
STATE_FILE.write_text(json.dumps({"processed": list(ids)}))
def get_recent_meetings(hours: int = 24) -> list[dict]:
"""Search timeline for meetings in the last N hours."""
since = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
data = api_call("timeline.search", {
"activity_type": "meeting",
"since": since,
"limit": 50,
})
return data.get("activities", [])
def create_followup_task(
title: str,
description: str,
due_date: str,
record_id: str | None = None,
):
"""Create a task in Supersonic."""
params = {
"title": title,
"description": description,
"due_date": due_date,
}
if record_id:
params["record_id"] = record_id
return api_call("tasks.create", params)
def main():
processed = load_processed()
meetings = get_recent_meetings(hours=24)
print(f"Found {len(meetings)} recent meetings.")
new_tasks = 0
for meeting in meetings:
meeting_id = meeting.get("id")
if meeting_id in processed:
continue
subject = meeting.get("subject") or meeting.get("title") or "Meeting"
attendees = meeting.get("attendees", [])
meeting_date = meeting.get("date") or meeting.get("created_at", "")
record_id = meeting.get("record_id")
# Calculate due date
try:
if meeting_date:
mt = datetime.fromisoformat(meeting_date.replace("Z", "+00:00"))
else:
mt = datetime.now(timezone.utc)
due = (mt + timedelta(days=FOLLOWUP_DAYS)).strftime("%Y-%m-%d")
except (ValueError, TypeError):
due = (datetime.now(timezone.utc) + timedelta(days=FOLLOWUP_DAYS)).strftime("%Y-%m-%d")
# Build task description
if attendees:
attendee_names = [a.get("name") or a.get("email", "Unknown") for a in attendees]
attendee_list = ", ".join(attendee_names)
description = f"Follow up after meeting: {subject}\nAttendees: {attendee_list}"
else:
description = f"Follow up after meeting: {subject}"
task_title = f"Follow up: {subject}"
print(f" Creating task: {task_title} (due {due})")
create_followup_task(
title=task_title,
description=description,
due_date=due,
record_id=record_id,
)
new_tasks += 1
processed.add(meeting_id)
save_processed(processed)
print(f"Created {new_tasks} follow-up task(s).")
if __name__ == "__main__":
main()
Setup
Set environment variables
export SUPERSONIC_API_KEY="supersonic_live_YOUR_KEY"
Test it
Run the script once. If you have recent meetings, it will create tasks:python meeting_followups.py
Verify tasks were created:npx supersonic-cli tasks list
Schedule with cron
Run daily at 6 PM (after the day’s meetings):0 18 * * * SUPERSONIC_API_KEY=supersonic_live_YOUR_KEY /usr/bin/python3 /path/to/meeting_followups.py >> /var/log/meeting_followups.log 2>&1
Or every 2 hours for faster follow-ups:0 */2 * * * SUPERSONIC_API_KEY=supersonic_live_YOUR_KEY /usr/bin/python3 /path/to/meeting_followups.py >> /var/log/meeting_followups.log 2>&1
Customizing follow-up rules
Different due dates by meeting type
def get_due_days(subject: str) -> int:
"""Return follow-up days based on meeting type."""
subject_lower = subject.lower()
if "demo" in subject_lower:
return 1 # follow up quickly after demos
if "review" in subject_lower or "check-in" in subject_lower:
return 7 # weekly reviews don't need urgent follow-up
return 2 # default
Create tasks for specific attendees only
Filter attendees to external contacts (skip your own team):
TEAM_DOMAINS = {"yourcompany.com", "supersonic.cv"}
def is_external(attendee: dict) -> bool:
email = attendee.get("email", "")
domain = email.split("@")[-1] if "@" in email else ""
return domain not in TEAM_DOMAINS
# In main(), replace the attendees line:
attendees = [a for a in meeting.get("attendees", []) if is_external(a)]
Add Slack notification when tasks are created
def notify_slack(task_title: str, due: str):
webhook = os.environ.get("SLACK_WEBHOOK_URL")
if not webhook:
return
httpx.post(webhook, json={
"text": f"Follow-up task created: *{task_title}* (due {due})"
}, timeout=10.0)
The state file prevents duplicate tasks. If you delete the state file, the script will reprocess the last 24 hours of meetings and may create duplicate tasks. Keep the state file backed up if this matters.
To change the lookback window, adjust the hours parameter in get_recent_meetings(). If you run the script every 2 hours, set it to hours=3 for some overlap to avoid missing meetings.