A small Python agent that scans your Google Calendar for upcoming external meetings, pulls relevant context from HubSpot, Gmail, and Slack, then generates a short pre-meeting brief with Claude and posts it to Slack. Built on the Scalekit Python SDK so you never write OAuth code yourself.
This is a reference example. Read the modules end-to-end and adapt them to your own stack.
main.py
-> filter_meetings.py # Google Calendar: external meetings in next 48h
-> for each meeting:
-> lookup_contact.py # HubSpot: contact by email, fallback to company by domain
-> lookup_gmail.py # Gmail: recent threads from contact/domain
-> lookup_slack.py # Slack: find channel by company name, fetch last 20 messages
-> generate_brief.py # Anthropic SDK call (claude-sonnet-4-6)
-> deliver_brief.py # Print to terminal + send Slack notification
Meetings are processed sequentially. No database, no scheduler, no streaming — run python main.py whenever you want a fresh briefing.
- A Scalekit account (free).
- Python 3.11+
- An Anthropic API key.
- Accounts you can authorize in Google Calendar, Gmail, HubSpot, and Slack. Use a workspace where you have read access to the channels you care about.
In the Scalekit dashboard, copy your API credentials from Developers → Settings → API Credentials:
SCALEKIT_ENV_URLSCALEKIT_CLIENT_IDSCALEKIT_CLIENT_SECRET
Now create four connections. For each one, go to AgentKit → Connections → Create Connection, pick the provider, and set the Connection name exactly as listed below. The code resolves connections by name, so the name in the dashboard must match the constant in the corresponding Python module.
| Provider | Connection name | Used in |
|---|---|---|
| Google Calendar | meeting-prep-google-calendar |
filter_meetings.py |
| HubSpot | meeting-prep-hubspot |
lookup_contact.py |
| Gmail | meeting-prep-gmail |
lookup_gmail.py |
| Slack | meeting-prep-slack |
lookup_slack.py, deliver_brief.py |
For each provider:
- Google Calendar. Create a connection → choose Google Calendar. Scalekit pre-configures scopes (
calendar.readonly). Save. - HubSpot. Create a connection → choose HubSpot. Scalekit pre-configures the OAuth app. Save. You can adjust scopes to
contactsandcompaniesread. - Gmail. Create a connection → choose Gmail. Default scope (
gmail.readonly) is enough. Save. - Slack. Create a connection → choose Slack. Required scopes:
channels:read,groups:read,channels:history,groups:history,chat:write. Save.
If you'd rather use different names, edit the CONNECTOR_NAME constant at the top of each module.
You do not need to authorize the connectors yet — the first run of main.py will print a Scalekit authorization URL for each one.
git clone https://github.com/<your-org>/meeting-prep-agent-example.git
cd meeting-prep-agent-example
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Fill in the values:
# SCALEKIT_ENV_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET
# SCALEKIT_USER_ID (your email — Scalekit keys connected accounts by this)
# ANTHROPIC_API_KEY
# SLACK_NOTIFICATION_CHANNEL_ID (open the channel in Slack -> About -> bottom)The user's email domain (the part after @ in SCALEKIT_USER_ID) is treated as "internal". Any attendee outside that domain is considered external.
python main.pyOn first run, the script checks all four connectors and prints any that still need authorization, then exits:
The following connectors need authorization. Open each link, authorize, then re-run `python main.py`.
Google Calendar:
https://auth.<your-env>.scalekit.dev/sso/v1/oauth/.../authorize?...
HubSpot:
https://auth.<your-env>.scalekit.dev/sso/v1/oauth/.../authorize?...
Gmail:
https://auth.<your-env>.scalekit.dev/sso/v1/oauth/.../authorize?...
Slack:
https://auth.<your-env>.scalekit.dev/sso/v1/oauth/.../authorize?...
Open each URL in your browser, complete the OAuth flow for each provider, then run python main.py again. Scalekit stores refresh tokens, so you only do this once.
[2026-05-21 18:02:11 UTC] Fetching external meetings in the next 48 hours...
[2026-05-21 18:02:13 UTC] Found 2 external meeting(s).
[2026-05-21 18:02:13 UTC] START Acme - Discovery
[2026-05-21 18:02:13 UTC] HubSpot lookup for jane@acme.com...
[2026-05-21 18:02:14 UTC] Gmail thread search...
[2026-05-21 18:02:16 UTC] Slack channel search for 'Acme Inc'...
[2026-05-21 18:02:18 UTC] Generating brief...
[2026-05-21 18:02:23 UTC] Delivering brief...
============================================================
Brief for Acme Inc - Jane Doe - May 22
============================================================
## Quick Facts
- Role: VP Engineering at Acme Inc
- HubSpot: https://app.hubspot.com/contacts/12345
- Relationship signal: Existing contact
## Email History
- Jane raised a question about SSO pricing in last week's thread "Re: Acme - Q3 rollout". She asked for SAML + SCIM bundled.
- Open commitment from your side: send revised quote by Friday (thread "Quote follow-up", May 18).
## Slack Context
- Internal channel #acme-deal: solutions team flagged that Acme's IdP is Okta; integration scoped at ~3 days.
- Discussion of contract length — Acme pushed for 1-yr term over 2-yr.
## Talking Points
- Walk through the revised SSO + SCIM bundle pricing.
- Confirm Okta integration timeline and any blockers.
- Reopen the 2-yr vs 1-yr term discussion now that pricing is finalized.
- Ask Jane about Acme's rollout calendar before Q3.
- Offer a paid pilot if procurement is slow.
============================================================
[2026-05-21 18:02:24 UTC] DONE Acme - Discovery
[2026-05-21 18:02:24 UTC] All meetings processed.
The same brief is posted to the channel you set as SLACK_NOTIFICATION_CHANNEL_ID:
Meeting brief: Acme - Discovery (May 22) Contact: Jane Doe · Company: Acme Inc
- Role: VP Engineering at Acme Inc
- HubSpot: https://app.hubspot.com/contacts/12345 ...
- Add another connector. Create a new connection in Scalekit, add a
lookup_*.pymodule that mirrors the existing ones (set up the client, ensure the account is active, callexecute_tool), and add a step insideprocess_meeting()inmain.py. See Scalekit's connector catalog for available tool names. - Change the brief format. Edit
PROMPT_TEMPLATEingenerate_brief.py. The function returns a markdown string — everything downstream is format-agnostic. - Swap the LLM.
generate_brief.pycalls the Anthropic SDK. Replace theAnthropic(...)client with OpenAI / your provider of choice; the rest of the pipeline doesn't care. - Run on a schedule. Wrap
python main.pyincron, GitHub Actions, or any scheduler. The script is idempotent within a 48-hour window but does not deduplicate across runs — add your own state file or DB if you only want each meeting briefed once.
KeyError: 'SCALEKIT_ENV_URL'— copy.env.exampleto.envand fill in the values.ConnectionErrororrequests.exceptions.ConnectionError— the Scalekit client authenticates on import. CheckSCALEKIT_ENV_URLfor typos and confirm the environment is reachable from your machine.- Connector keeps showing "not active" — open the authorization link in the same browser session as your Scalekit account. After authorizing, run
python main.pyagain. - No external meetings found — the script only looks ahead 48 hours and skips meetings where every attendee shares your
SCALEKIT_USER_IDdomain. Add an external attendee to a test event in Google Calendar. - Slack notification didn't arrive — confirm the bot user installed by your Slack connection is a member of the target channel. The Slack OAuth flow only grants access to channels the authorizing user is in.