Introduction

I’ve always wanted a personalized dashboard that greets me every morning with relevant information: today’s weather, my calendar events, currency rates, and curated tech news. Instead of checking multiple apps, I built StartPage - an automated system that aggregates data from various sources and publishes it daily to a Notion page.

It runs completely serverless on AWS Lambda, costing virtually nothing (within the free tier) and requiring zero maintenance.

You can check code in GitHub

What script can do:

  • 🌤️ Daily weather updates for your city
  • 📅 Today’s calendar events from iCloud
  • 💱 Currency and cryptocurrency rates
  • 📰 Curated tech news from RSS feeds
  • 🎲 A random interesting fact to start your day

Tech stack:

  • Python 3.12 with asyncio for concurrent data fetching
  • Notion API for publishing content
  • AWS Lambda for serverless execution
  • AWS CloudFormation for infrastructure as code
  • AWS EventBridge for daily scheduling
  • GitHub Actions for CI/CD

Part 1: Understanding the Project

StartPage is a Python application that:

  1. Fetches data concurrently from multiple APIs (weather, calendar, RSS feeds, currency rates)
  2. Formats the data as Notion blocks
  3. Publishes everything to your Notion page as a daily summary
  4. Updates a “fact of the day” callout block

The architecture is simple:

Runtime:

┌─────────────────┐
│  EventBridge    │  Triggers daily at 6 AM UTC
│  (Scheduler)    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  AWS Lambda     │  Runs StartPage application
│  (Python 3.12)  │
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────────────────────┐
│  Concurrent Data Fetching (asyncio.gather)      │
├──────────┬──────────┬──────────┬──────────┬─────┤
│ Weather  │ Calendar │ Currency │   RSS    │ Fact│
└──────────┴──────────┴──────────┴──────────┴─────┘
         │
         ▼
┌─────────────────┐
│   Notion API    │  Publishes formatted blocks
└─────────────────┘

The entire execution takes about 10-15 seconds, well within Lambda’s free tier limits.


Part 2: Setting Up Your Notion Page

Step 1: Create a Notion Integration

  1. Go to Notion Integrations
  2. Click "+ New integration"
  3. Give it a name (e.g., “StartPage Bot”)
  4. Select the workspace where you want to use it
  5. Click “Submit”
  6. Copy the Internal Integration Token - you’ll need this for your .env file

Step 2: Create Your Dashboard Page

  1. In Notion, create a new page (this will be your daily dashboard)
  2. Give it a title like “📊 Daily Dashboard” or “Morning Briefing”
  3. Add a Callout block at the top - this will display your daily random fact
  4. You can add any other static content you want (headers, dividers, etc.)

Your page structure should look like this:

📊 Daily Dashboard
├── 💡 [Callout Block] ← This will show the daily fact
└── [Daily updates will appear here]

Step 3: Get Your Page ID and Block ID

To get the Page ID:

  1. Click “Share” on your Notion page
  2. Click “Copy link”
  3. The URL will look like: https://www.notion.so/Your-Page-Title-abc123def456...
  4. The Page ID is the part after the last dash: abc123def456...

To get the Block ID (Callout):

  1. Hover over your callout block
  2. Click the ⋮⋮ (six dots) icon
  3. Click “Copy link to block”
  4. The URL will look like: https://www.notion.so/...#xyz789abc123...
  5. The Block ID is the part after the #: xyz789abc123...

Step 4: Share Page with Integration

  1. Open your dashboard page in Notion
  2. Click the "…" menu in the top right
  3. Scroll down and click “Add connections”
  4. Find and select your integration (e.g., “StartPage Bot”)
  5. Click “Confirm”

Now your integration has access to read and write to this page!


Part 3: Gathering Environment Variables

Create a .env file in the project root with all necessary credentials:

# Notion Configuration
NOTION_TOKEN=secret_xxx...                    # From Step 2.1
PAGE_ID=abc123def456ghi789                    # From Step 2.3
BLOCK_ID=xyz789abc123def456                   # From Step 2.3

# Location Settings
CITY=London                                   # Or your preferred city
TIMEZONE=Europe/London                        # IANA timezone

# iCloud Calendar (for calendar events)
ICLOUD_USERNAME=your.email@icloud.com
ICLOUD_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx      # See below

# AWS Deployment (needed for `make deploy`)
S3_BUCKET=your-startpage-bucket              # S3 bucket for Lambda package

Getting iCloud App-Specific Password

Apple requires app-specific passwords for third-party access:

  1. Go to appleid.apple.com
  2. Sign in with your Apple ID
  3. In the Security section, find App-Specific Passwords
  4. Click “Generate an app-specific password”
  5. Enter a label (e.g., “StartPage Calendar”)
  6. Copy the generated password (format: xxxx-xxxx-xxxx-xxxx)
  7. Paste it into your .env file as ICLOUD_APP_PASSWORD

Note: You only see this password once, so save it immediately!

Configuration Checklist

Before proceeding, verify you have:

  • ✅ Notion integration token
  • ✅ Page ID (no hyphens)
  • ✅ Block ID (no hyphens)
  • ✅ City name for weather
  • ✅ Your timezone
  • ✅ iCloud username
  • ✅ iCloud app-specific password

Part 4: Testing Locally

Now let’s make sure everything works before deploying to AWS.

Step 1: Install Dependencies

# Install Poetry if you haven't already
curl -sSL https://install.python-poetry.org | python3 -

# Install project dependencies
poetry install

Step 2: Run the Application

poetry run python -m startpage.startpage

You should see output like:

2026-02-09 10:30:45 - startpage.startpage - INFO - Starting StartPage application
2026-02-09 10:30:45 - startpage.startpage - INFO - Fetching data from all sources concurrently
2026-02-09 10:30:52 - startpage.startpage - INFO - All data fetched successfully, building Notion page
2026-02-09 10:30:52 - startpage.startpage - INFO - Appending new day section: Sunday 09 of February
2026-02-09 10:30:53 - startpage.startpage - INFO - Updating fact block
2026-02-09 10:30:53 - startpage.startpage - INFO - StartPage update completed successfully

Step 3: Verify in Notion

Open your Notion page and you should see:

  • A new header with today’s date (e.g., “Sunday 09 of February”)
  • Weather information for your city
  • Currency rates (₽ and $ based)
  • Cryptocurrency prices
  • Your calendar events for today
  • 5 curated tech news articles
  • Updated fact in the callout block

Part 5: Deploying with CloudFormation

Now let’s deploy StartPage to AWS Lambda so it runs automatically every day.

Step 1: Install AWS CLI

macOS:

brew install awscli

Linux/Windows: Follow the official installation guide

Verify installation:

aws --version

Step 2: Configure AWS Credentials

aws configure

You’ll be prompted for:

  • AWS Access Key ID
  • AWS Secret Access Key
  • Default region (e.g., us-east-1)
  • Output format (just press Enter for default)

Don’t have AWS credentials?

  1. Log into AWS Console
  2. Go to IAM → Users
  3. Click “Add users”
  4. Enable “Programmatic access”
  5. Attach “AdministratorAccess” policy (or create a custom policy)
  6. Save the Access Key ID and Secret Access Key

Step 3: Create an S3 Bucket

You need an S3 bucket to store the Lambda deployment package:

aws s3 mb s3://your-startpage-bucket --region us-east-1

Step 4: Build and Upload the Lambda Package

# Install the Lambda build plugin (one-time setup)
poetry self add poetry-plugin-lambda-build

# Build the deployment package
poetry build-lambda

# Upload to S3
aws s3 cp package.zip s3://your-startpage-bucket/startpage/package.zip

Step 5: Deploy with CloudFormation

The project includes a template.yaml that defines all AWS resources: Lambda function, IAM role, EventBridge schedule, and CloudWatch log group.

aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name startpage \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides \
    S3Bucket=your-startpage-bucket \
    S3Key=startpage/package.zip \
    NotionToken=secret_xxx \
    PageId=your_page_id \
    BlockId=your_block_id \
    City=London \
    ICloudUsername=your@icloud.com \
    ICloudAppPassword=xxxx-xxxx-xxxx-xxxx \
    Timezone=Europe/London \
    ScheduleExpression='cron(0 6 * * ? *)'

This creates the entire infrastructure in one command. CloudFormation will:

  1. Create an IAM role with basic Lambda execution permissions
  2. Deploy your Lambda function with the code from S3
  3. Create an EventBridge rule to trigger the function daily
  4. Set up CloudWatch log group with 7-day retention

Alternatively, if you have your .env file configured with all the variables (including S3_BUCKET), you can use the Makefile shortcut which handles building, uploading to S3, and deploying in one command:

make deploy

The process takes 2-3 minutes. When complete, you’ll see:

Successfully created/updated stack - startpage in us-east-1

Step 6: Test Your Lambda Function

Invoke your function manually to verify it works, before invoking check name of the function, CloudFormation use uniq name:

aws lambda invoke \
  --function-name startpage \
  --invocation-type RequestResponse \
  --log-type Tail \
  output.json

# View the response
cat output.json

You should see:

{
  "statusCode": 200,
  "body": "StartPage updated successfully"
}

Check your Notion page - you should see a new daily entry!

Step 7: Monitor Execution

View your Lambda logs:

# See recent logs
aws logs tail /aws/lambda/startpage --since 1h

# Follow logs in real-time
aws logs tail /aws/lambda/startpage --follow

Adjusting the Schedule

To run at a different time, redeploy with a new ScheduleExpression:

aws cloudformation deploy \
  --template-file template.yaml \
  --stack-name startpage \
  --capabilities CAPABILITY_IAM \
  --parameter-overrides ScheduleExpression='cron(0 8 * * ? *)' ...

Common schedules:

  • cron(0 6 * * ? *) - Daily at 6 AM UTC
  • cron(0 */6 * * ? *) - Every 6 hours
  • cron(0 8 * * MON *) - Every Monday at 8 AM UTC

Note: AWS EventBridge uses UTC time. Convert your local time to UTC for the schedule.