Morning Brief avatar

Morning Brief

Pricing

from $5.00 / 1,000 data fetcheds

Go to Apify Store
Morning Brief

Morning Brief

Fetch your daily context from Slack, Gmail, Google Calendar, iCal, and Notion — then generate a formatted morning brief via Claude API and deliver it to Slack. Supports up to 3 accounts per integration. Schedule it before you start your day for a fully automated brief in Slack. No code required.

Pricing

from $5.00 / 1,000 data fetcheds

Rating

0.0

(0)

Developer

Ondra Mrstny

Ondra Mrstny

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

Your morning brief, waiting when you open Slack.

Fetch your daily context from Slack, Gmail, Google Calendar, iCal, and Notion — generate a formatted brief via Claude API — deliver it to Slack. Schedule it once and forget it.

Typical run: all integrations fetched, brief generated, and delivered to Slack in under 30 seconds.


What you get

Every morning, a brief that looks like this lands in your Slack DM before you start work:

*AT A GLANCE*
Heavy meeting day — 4 syncs before noon. One customer escalation needs a response before the 11:00 standup.
📆 *TODAY — Wednesday*
*09:00* — Adoption weekly sync (Google Meet)
*11:00* — Standup
*14:00*1:1 with Lukas
*16:30* — Quarterly review prep
📆 *TOMORROW — Thursday*
*10:00* — Customer onboarding call — Acme Corp
🔴 *NEEDS ACTION*
*Customer escalation: Acme Corp* — Lena escalated an issue in #customer-success. Needs acknowledgment before 11:00.
*DEGIRO tax document* — Arrived by email yesterday, download before April 30 deadline.
📢 *FYI*
*Adoption experiment — A/B test* — Results posted in #adoption-team, 12% lift on aha moment for guided-tour variant.
📧 *INBOX*
*Lena Novak* — Re: Acme Corp onboarding issue (waiting on response)
*DEGIRO* — Your tax document for 2025 is ready
*ACTIVE TODOS — top 5*
• Respond to Acme Corp escalation before standup
• Review A/B test results from adoption experiment
• Download DEGIRO tax document

What it does

Each run:

  1. Fetches recent Slack messages from all channels and DMs you are a member of, plus mentions
  2. Fetches unread Gmail messages from the last N hours
  3. Fetches Google Calendar events for today and tomorrow
  4. Fetches iCal feeds — any calendar with a secret URL, no OAuth needed
  5. Fetches Notion database entries and specific pages you configure
  6. Stores a raw JSON snapshot in a named key-value store (morning-brief-latest)
  7. (Optional) Calls the Claude API to generate a formatted brief from the snapshot
  8. (Optional) Posts the formatted brief to a Slack channel or DM

Steps 7 and 8 require an Anthropic API key. Without it, only the raw snapshot is saved — you can still read it via the Apify MCP server and ask Claude to generate the brief on demand.


Quick start

  1. Add credentials for the integrations you use (Slack, Gmail/Calendar, iCal, Notion)
  2. Add your Anthropic API key and target Slack channel in Brief generation
  3. Run the Actor once to verify — your brief should appear in Slack within 30 seconds
  4. Set a schedule: in Apify Console → your Actor → Schedules0 7 * * * for 7am daily

That's it. The brief runs automatically every morning from then on.


Schedule it

In Apify Console, open your Actor → SchedulesNew schedule.

GoalCron expressionDescription
Daily at 7am0 7 * * *Standard morning brief
Weekdays only at 7:30am30 7 * * 1-5Skip weekends
Catch-up on Mondays at 8am0 8 * * 1With hours_lookback: 72

Set your local timezone in the schedule settings so the brief arrives before you start work.


Input configuration

General settings

hours_lookback — How many hours back to fetch Slack messages and Gmail. Default: 24. Use 48 or 72 to catch up after a weekend.


Slack

Supports up to 3 separate Slack workspaces (e.g. personal and work).

Token types:

TypePrefixReads channels + DMsReads mentions
Bot tokenxoxb-YesNo
User tokenxoxp-YesYes

User tokens are recommended — they are the only way to get @mention results via Slack's search API.

How to get a Slack token:

  1. Go to api.slack.com/appsCreate New App → From scratch
  2. Choose the workspace you want to read from
  3. Go to OAuth & Permissions and add the following scopes under Bot Token Scopes (or User Token Scopes for a user token):
    • channels:history, channels:read — public channels
    • groups:history, groups:read — private channels
    • im:history, im:read — direct messages
    • mpim:history, mpim:read — group DMs
    • search:read — mention search (user tokens only)
  4. Click Install to Workspace and copy the token from the confirmation screen

Fill in Account label (e.g. personal, work) and Bot or user token for each workspace. Accounts 2 and 3 are optional.


Gmail and Google Calendar

Both services use the same OAuth credentials. One account entry covers Gmail and Calendar together.

How to get Google OAuth credentials:

  1. Go to console.cloud.google.com and create a new project (or use an existing one)
  2. Enable two APIs: Gmail API and Google Calendar API (APIs & Services → Library)
  3. Go to APIs & Services → Credentials → Create Credentials → OAuth client ID
  4. Set Application type to Desktop app — give it any name
  5. Note down your Client ID and Client Secret
  6. Run the included setup script to get a refresh token:
pip install google-auth-oauthlib
python3 google_auth_setup.py

A browser window opens for authorization. The script prints your refresh token when complete. Copy it into the Refresh token field.

Per-account settings:

FieldDescription
Account labelName for this account, e.g. work or personal
Client IDEnds in .apps.googleusercontent.com
Client secretFrom the same OAuth client
Refresh tokenGenerated by google_auth_setup.py
Calendar IDsOne per line. primary = your main calendar. Find others in Google Calendar → Settings → specific calendar → Calendar ID
Fetch GmailEnable/disable Gmail for this account
Fetch CalendarEnable/disable Calendar for this account

Supports up to 3 Google accounts. A common setup: Account 1 = work (Gmail + Calendar), Account 2 = personal (Calendar only).

Note: Google refresh tokens expire if unused for 6 months or if you revoke access. Re-run google_auth_setup.py to generate a new one.


iCal calendar feeds

No OAuth required. Works with any calendar app that provides a secret iCal URL. The simplest way to add a Google Workspace calendar when the account blocks third-party OAuth consent.

How to get your iCal URL from Google Calendar:

  1. Open Google Calendar in a browser
  2. Click the menu next to the calendar you want → Settings
  3. Scroll to Integrate calendar → Secret address in iCal format
  4. Copy the URL (it looks like https://calendar.google.com/calendar/ical/.../private-xxxx/basic.ics)

The URL acts as a secret token. Anyone with it can read your calendar — treat it like a password and keep it out of shared config files.

Per-account settings:

FieldDescription
Account labelName for this feed, e.g. work or personal
iCal URLsOne URL per line. Each URL fetches one calendar. All events are merged into a single list for this account.

Supports up to 3 iCal accounts.


Notion

Fetches database entries and full page content from any Notion workspace you connect.

How to get a Notion integration token:

  1. Go to notion.so/my-integrationsNew integration
  2. Give it a name (e.g. "Morning Brief"), associate it with your workspace
  3. Set access to Read content only
  4. Copy the Internal Integration Secret (starts with secret_)

Connect each database or page you want to include:

  1. Open the database or page in Notion
  2. Click ... in the top-right → Add connections → select your integration
  3. Copy the database ID or page URL

Finding a database ID: Open the database. The URL looks like notion.so/Page-Title-abc123def456789012345678901234ab. The last 32 characters (before ?v=) are the database ID.

Per-account settings:

FieldDescription
Account labelName for this workspace, e.g. main
Integration tokenStarts with secret_
Database IDsOne per line — the 32-character ID from the database URL
Page URLsFull Notion page URLs, one per line — the ID is extracted automatically

Supports up to 3 Notion accounts.


Brief generation

Requires an Anthropic API key from console.anthropic.com. The brief is generated using the data snapshot from the same run — no second fetch needed.

FieldDefaultDescription
Anthropic API keyStarts with sk-ant-. Required for brief generation. Without it, only the raw snapshot is saved.
Claude modelclaude-haiku-4-5-20251001Model used for generation. Haiku is fast and cheap. Use claude-sonnet-4-6 for higher quality.
Slack channel or DMChannel ID (C...) or DM ID (D...) to post the brief. Right-click a channel or DM in Slack → View channel details → copy the ID at the bottom. Leave empty to skip delivery.
Slack token for deliveryToken used to post the brief. Defaults to Slack Account 1 if empty. Requires chat:write scope.
Custom brief instructionsPlain-text instructions appended to the system prompt. Adjust tone, add skip rules, focus specific sections, or give context about your role.

Custom instructions examples:

Skip any Slack messages about food, lunch orders, or office plants.
I am based in Prague (CET) — convert all times to CET.
If there are no urgent emails, write "(nothing actionable)" for the INBOX section.
Flag any message mentioning "churn" or "cancellation" as NEEDS ACTION.

Brief format

The generated brief uses Slack mrkdwn with these fixed sections. Custom instructions can adjust content and tone but not the section structure.

SectionWhat it contains
⚡ AT A GLANCE1-2 sentences. What kind of day is it? Any urgent watch-outs?
📆 TODAYAll meetings with other people: • HH:MM — Event name (location)
📆 TOMORROWSame format, next day
🔴 NEEDS ACTIONItems requiring a response or decision today — from Slack, email, or Notion
📢 FYIWorth knowing, no action needed
📧 INBOXReal, actionable emails only: • Sender — Subject (context)
✅ ACTIVE TODOSTop 5 priorities for today, sourced from Notion Todos first, then Slack/email

Raw data snapshot

The full snapshot is always stored in morning-brief-latest → key brief_data, regardless of whether brief generation is enabled. Use this with the Apify MCP server to generate briefs on demand, or access it directly via API.

Snapshot structure:

{
"fetched_at": "2026-05-13T07:00:00Z",
"period_hours": 24,
"slack": {
"work": {
"channels": [
{
"channel": "adoption-team",
"messages": [
{ "user": "Lena", "text": "...", "ts": "..." }
]
}
],
"dms": [...],
"mentions": [...]
}
},
"gmail": {
"work": [
{ "from": "lena@company.com", "subject": "Re: Acme onboarding", "snippet": "..." }
]
},
"calendar": {
"work": {
"primary": [
{ "summary": "Standup", "start": "2026-05-13T11:00:00+02:00", "end": "2026-05-13T11:30:00+02:00" }
]
}
},
"ical_calendar": {
"work": [
{ "summary": "Customer call", "start": "2026-05-14T10:00:00+02:00", "end": "2026-05-14T11:00:00+02:00" }
]
},
"notion": {
"main": {
"databases": {
"abc123...": {
"title": "Adoption team board",
"pages": [
{ "title": "Q2 experiment", "status": "In progress", "assignee": ["Ondrej"] }
]
}
},
"pages": {
"https://notion.so/...": {
"title": "Todos",
"content": "- Respond to Acme escalation\n- Review A/B test results"
}
}
}
}
}

Using with the Apify MCP server

If you prefer to generate the brief on demand rather than automatically, skip the Anthropic API key. The raw snapshot is always saved.

With the Apify MCP server connected to Claude or another AI:

  • "Give me my morning brief" — Claude reads the snapshot and generates a formatted brief
  • "What meetings do I have today?" — Calendar-only summary
  • "Any urgent Slack messages?" — Slack-focused filter
  • "What did I miss while I was out?" — Full briefing with hours_lookback: 72

The MCP server reads directly from morning-brief-latest. No additional configuration needed.


Use via API

Run the Actor and retrieve the output programmatically using the Apify API or SDK.

Python:

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("ondrej.mrstny/morning-brief-prefetcher").call(run_input={
"hours_lookback": 24,
"slack_1_label": "work",
"slack_1_token": "xoxp-...",
"anthropic_api_key": "sk-ant-...",
"brief_slack_channel": "D...",
})
# Get the brief text
store = client.key_value_store(run["defaultKeyValueStoreId"])
brief = store.get_record("brief_text")["value"]
print(brief)

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('ondrej.mrstny/morning-brief-prefetcher').call({
hours_lookback: 24,
slack_1_label: 'work',
slack_1_token: 'xoxp-...',
anthropic_api_key: 'sk-ant-...',
brief_slack_channel: 'D...',
});
const store = await client.keyValueStore(run.defaultKeyValueStoreId);
const brief = await store.getRecord('brief_text');
console.log(brief.value);

cURL:

# Start a run
curl -X POST \
"https://api.apify.com/v2/acts/ondrej.mrstny~morning-brief-prefetcher/runs?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"hours_lookback": 24,
"slack_1_label": "work",
"slack_1_token": "xoxp-...",
"anthropic_api_key": "sk-ant-..."
}'
# Get the brief text (replace STORE_ID from run response)
curl "https://api.apify.com/v2/key-value-stores/STORE_ID/records/brief_text?token=YOUR_APIFY_TOKEN"

Cost

The Actor uses per-event billing. You are charged each time the data is successfully fetched and stored — the brief generation via Claude API is billed separately by Anthropic on your own API key.

There is no charge for failed runs or runs with no accounts configured.


Data security

Your credentials never leave your Apify account.

  • All tokens and secrets are stored in your Actor input and encrypted at rest using per-user RSA keys
  • Fetched data is stored in your private Apify key-value store — only accessible to you and systems you authorize
  • The Actor runs in an isolated container with no network access beyond the APIs it explicitly calls
  • All integrations use read-only scopes — the Actor cannot send messages, modify emails, create calendar events, or edit Notion pages
  • Your brief data is not transmitted to the Actor author or any third party
  • Apify is SOC 2 Type II certified

Recommended practices:

  • Use a dedicated Google Cloud project for this Actor's OAuth credentials
  • Create a Notion integration with read-only access
  • Prefer a Slack bot token over a user token for any workspace where you do not need mention search
  • Rotate tokens periodically via each provider's settings

Limitations

  • Slack channel history is limited to channels the bot/user is a member of
  • Gmail fetches metadata and snippet only — full email body is not included to keep output size manageable
  • Notion page content fetches one level deep — nested sub-pages are not recursed
  • Notion database rows fetch visible properties only — formula results and rollups may be absent depending on API access
  • Google refresh tokens expire after 6 months of non-use — re-run google_auth_setup.py to generate a fresh token
  • iCal recurring events are expanded from the .ics file at fetch time — RSVP status is not available
  • Brief generation quality depends on the selected Claude model — Haiku is fast but may miss nuance; Sonnet gives higher-quality output

FAQ

Does this work with multiple Slack workspaces? Yes. Configure up to 3 separate Slack accounts with different tokens. Each account fetches its own channels, DMs, and mentions independently.

Can I have Gmail from one account and Calendar from another? Yes. Each Google account entry has separate toggles for Gmail and Calendar. Configure Account 1 for work Gmail + Calendar, Account 2 for personal Calendar only.

Why use iCal instead of Google OAuth for Calendar? Google Workspace managed accounts sometimes block OAuth consent for third-party apps. The iCal URL bypasses OAuth entirely — it is a direct subscription link. No app approval needed.

The brief is generating incorrect todos. How do I fix it? Add a custom brief instruction like: "Todos must come only from the Notion Todos page I share. Do not infer todos from Slack messages." The Todos section defaults to starting from Notion and backfilling from Slack/email if the Notion page is sparse.

Can I use this without the Anthropic API key? Yes. Without a key, the Actor fetches all data and stores the raw snapshot. You can then read it via the Apify MCP server and ask Claude to generate the brief on demand in your chat.

What Slack permissions does the delivery token need? The token used to post the brief needs chat:write scope. If using a bot token, the bot must also be added to the target channel. For a DM, this happens automatically.

How fresh is the data? The snapshot is generated at run time — there is no caching. If the Actor runs at 7am, all data is from the 24 hours prior to 7am.

Can I run this more than once a day? Yes. Each run generates a fresh snapshot and overwrites the morning-brief-latest key-value store. Multiple schedules work — e.g. a 7am daily brief and a 4pm end-of-day brief with hours_lookback: 9.

What if one integration fails? The Actor continues fetching from all other integrations. Failed integrations are logged with the error message and stored in the output as { "error": "..." }. The brief is still generated from whatever data was successfully retrieved.


Integrations

PlatformHow to use
SlackSet brief_slack_channel and a token with chat:write. The brief is posted as a formatted Slack mrkdwn message.
Apify MCP serverConnect Claude or another AI to the MCP server. The brief data is accessible as morning-brief-latest.
Zapier / MakeTrigger on Actor run completion. Read brief_text from the key-value store and forward to email, Notion, or another service.
Apify APIFull programmatic access — run via REST API or the Python/JavaScript SDK. See Use via API above.
Claude CodeA local Morning Brief skill (morning-brief.md) is available for generating the brief directly in Claude Code by saying "good morning".

Troubleshooting

No brief in Slack after running. Check that brief_slack_channel is set to a channel or DM ID (not the channel name). Verify the token has chat:write scope. For channels, confirm the bot is added to the channel.

Slack fetches zero messages. Confirm the bot/user token is active and has not been revoked. Bot tokens require the app to be installed in the workspace and the bot to be added to each private channel. Use a user token for broader access.

Google Calendar shows no events. Verify the calendar ID is correct — use primary for your main calendar. Check that the Fetch Calendar toggle is enabled for the account. If the OAuth credentials are expired, re-run google_auth_setup.py.

Notion database returns empty results. Confirm the integration has been connected to the database via ... → Add connections. Check that the database ID is the 32-character string before ?v=, not the full URL.

Brief quality is low — wrong section content. Switch from Haiku to claude-sonnet-4-6 in the Claude model field. Add custom instructions to give the model more context about your role and what to prioritize.


Responsible use

  • This Actor reads from your own accounts using credentials you provide — use it only on accounts you own or have permission to access
  • Fetched data may contain personal information — store and transmit it accordingly
  • Anthropic API usage is subject to Anthropic's usage policies
  • Do not use this Actor to aggregate or monitor other people's communications without their consent