/start-11-3.en
Lesson command
$ npx -y skills add minicoohei/ai-agent-camp --agent claude-codeHow it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/start-11-3.en
Context preview
What this command does when you run it.
Lesson command
Command definition
start-11-3.en.mddescription: "Lesson command"
chapter: "courses/aiagent/lesson03-core/module11-github-actions"
duration: "~25 min"
prerequisites: ["start-11-2"]
level: "intermediate"
tags: ["github-actions", "news", "email", "slack", "webhook", "cron"]
nonInteractiveMode: deferred
๐ Lesson 11-3: News Fetching โ Email/Slack Distribution Workflow
๐ What You'll Do
**Lesson 11-3: News Fetching โ Email/Slack Distribution**!
| Item | Details | |------|------| | Goal | Build a GitHub Actions workflow that automatically fetches news and distributes it via email and Slack | | Duration | ~25 min | | Skills used | GitHub Actions, Python (requests), Slack Webhook, smtplib | | Prerequisites | Lesson 11-2 completed (understanding of Secrets configuration) |
**Session flow:** 1. Create the news fetching script 2. Implement email sending 3. Set up Slack Webhook notifications 4. Create the GitHub Actions workflow 5. Configure Secrets and test
By the end of this session, you'll have a complete pipeline that periodically collects news and automatically distributes it to email and Slack.
> **๐ก Hint**: If the AI response stops midway, type "please continue" or "keep going" to resume.
---
๐ฏ Readiness Check
**AskQuestion configuration:**
{
"title": "๐ฏ Pre-session check",
"questions": [{
"id": "readiness",
"prompt": "Are you ready?",
"options": [
{"id": "ready", "label": "Ready! Let's start"},
{"id": "check_prereq", "label": "Check prerequisites"},
{"id": "different_lesson", "label": "Go to a different lesson"}
]
}]
}(ready โ Go to Step 1) (check_prereq โ Verify Lesson 11-2 completion. Check that the `.github/workflows/` directory exists) (different_lesson โ Display module list)
---
๐ Step 1: Create the News Fetching Script
{
"title": "๐ Step 1: News Fetching Script",
"questions": [{
"id": "step_action",
"prompt": "Create a Python script that fetches news from RSS feeds or a News API.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how RSS/APIs work"},
{"id": "skip", "label": "Skip"}
]
}]
}**Guidance after selection:**
Create `tools/fetch_news.py`:
#!/usr/bin/env python3
"""News fetching script โ collects news from RSS feeds"""
import json
import xml.etree.ElementTree as ET
from datetime import datetime
import requests
# RSS feed URLs (e.g., Hacker News, TechCrunch)
RSS_FEEDS = [
{"name": "Hacker News", "url": "https://hnrss.org/newest?count=5"},
{"name": "TechCrunch", "url": "https://techcrunch.com/feed/"},
]
def fetch_rss(url, max_items=5):
"""Fetch news from an RSS feed"""
resp = requests.get(url, timeout=30)
resp.raise_for_status()
root = ET.fromstring(resp.text)
items = []
for item in root.iter("item"):
title = item.findtext("title", "")
link = item.findtext("link", "")
pub_date = item.findtext("pubDate", "")
items.append({"title": title, "link": link, "pubDate": pub_date})
if len(items) >= max_items:
break
return items
def main():
all_news = []
for feed in RSS_FEEDS:
try:
items = fetch_rss(feed["url"])
all_news.append({"source": feed["name"], "items": items})
except Exception as e:
print(f"[WARN] {feed['name']}: {e}")
# JSON output
output = {
"generated_at": datetime.utcnow().isoformat(),
"feeds": all_news
}
with open("output/news_digest.json", "w") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"Fetch complete: {sum(len(f['items']) for f in all_news)} news items")
return output
if __name__ == "__main__":
main()mkdir -p output && python tools/fetch_news.py
**Expected result:** News data is saved to `output/news_digest.json`.
---
๐ Step 2: Implement Email Sending
{
"title": "๐ Step 2: Email Sending",
"questions": [{
"id": "step_action",
"prompt": "Add email sending functionality for the fetched news.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how smtplib works"},
{"id": "skip", "label": "Skip"}
]
}]
}**Guidance after selection:**
Add a sending function to `tools/fetch_news.py`:
import smtplib
from email.mime.text import MIMEText
import os
def send_email(news_data):
"""Send a news digest email"""
smtp_user = os.environ.get("SMTP_USER", "")
smtp_pass = os.environ.get("SMTP_PASS", "")
to_email = os.environ.get("NOTIFY_EMAIL", smtp_user)
if not smtp_user or not smtp_pass:
print("[SKIP] SMTP credentials not configured, skipping email")
return
# Build email body
body_lines = [f"# News Digest ({news_data['generated_at'][:10]})\n"]
for feed in news_data["feeds"]:
body_lines.append(f"\n## {feed['source']}")
for item in feed["items"]:
body_lines.append(f"- [{item['title']}]({item['link']})")
body = "\n".join(body_lines)
msg = MIMEText(body, "plain", "utf-8")
msg["Subject"] = f"News Digest {news_data['generated_at'][:10]}"
msg["From"] = smtp_user
msg["To"] = to_email
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(smtp_user, smtp_pass)
server.send_message(msg)
print(f"Email sent: {to_email}")**Note**: For Gmail, an app password is required. Set `SMTP_USER` and `SMTP_PASS` in Secrets.
**Expected result:** The news digest is sent by email.
---
๐ Step 3: Set Up Slack Webhook Notifications
{
"title": "๐ Step 3: Slack Notifications",
"questions": [{
"id": "step_action",
"prompt": "Send news notifications via a Slack Incoming Webhook.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how to create a Slack Webhook"},
{"Read more
description: "Lesson command" chapter: "courses/aiagent/lesson03-core/module11-github-actions" duration: "~25 min" prerequisites: ["start-11-2"] level: "intermediate" tags: ["github-actions", "news", "email", "slack", "webhook", "cron"] nonInteractiveMode: deferred
๐ Lesson 11-3: News Fetching โ Email/Slack Distribution Workflow
๐ What You'll Do
**Lesson 11-3: News Fetching โ Email/Slack Distribution**!
| Item | Details | |------|------| | Goal | Build a GitHub Actions workflow that automatically fetches news and distributes it via email and Slack | | Duration | ~25 min | | Skills used | GitHub Actions, Python (requests), Slack Webhook, smtplib | | Prerequisites | Lesson 11-2 completed (understanding of Secrets configuration) |
**Session flow:** 1. Create the news fetching script 2. Implement email sending 3. Set up Slack Webhook notifications 4. Create the GitHub Actions workflow 5. Configure Secrets and test
By the end of this session, you'll have a complete pipeline that periodically collects news and automatically distributes it to email and Slack.
> **๐ก Hint**: If the AI response stops midway, type "please continue" or "keep going" to resume.
---
๐ฏ Readiness Check
**AskQuestion configuration:**
{
"title": "๐ฏ Pre-session check",
"questions": [{
"id": "readiness",
"prompt": "Are you ready?",
"options": [
{"id": "ready", "label": "Ready! Let's start"},
{"id": "check_prereq", "label": "Check prerequisites"},
{"id": "different_lesson", "label": "Go to a different lesson"}
]
}]
}(ready โ Go to Step 1) (check_prereq โ Verify Lesson 11-2 completion. Check that the `.github/workflows/` directory exists) (different_lesson โ Display module list)
---
๐ Step 1: Create the News Fetching Script
{
"title": "๐ Step 1: News Fetching Script",
"questions": [{
"id": "step_action",
"prompt": "Create a Python script that fetches news from RSS feeds or a News API.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how RSS/APIs work"},
{"id": "skip", "label": "Skip"}
]
}]
}**Guidance after selection:**
Create `tools/fetch_news.py`:
#!/usr/bin/env python3
"""News fetching script โ collects news from RSS feeds"""
import json
import xml.etree.ElementTree as ET
from datetime import datetime
import requests
# RSS feed URLs (e.g., Hacker News, TechCrunch)
RSS_FEEDS = [
{"name": "Hacker News", "url": "https://hnrss.org/newest?count=5"},
{"name": "TechCrunch", "url": "https://techcrunch.com/feed/"},
]
def fetch_rss(url, max_items=5):
"""Fetch news from an RSS feed"""
resp = requests.get(url, timeout=30)
resp.raise_for_status()
root = ET.fromstring(resp.text)
items = []
for item in root.iter("item"):
title = item.findtext("title", "")
link = item.findtext("link", "")
pub_date = item.findtext("pubDate", "")
items.append({"title": title, "link": link, "pubDate": pub_date})
if len(items) >= max_items:
break
return items
def main():
all_news = []
for feed in RSS_FEEDS:
try:
items = fetch_rss(feed["url"])
all_news.append({"source": feed["name"], "items": items})
except Exception as e:
print(f"[WARN] {feed['name']}: {e}")
# JSON output
output = {
"generated_at": datetime.utcnow().isoformat(),
"feeds": all_news
}
with open("output/news_digest.json", "w") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"Fetch complete: {sum(len(f['items']) for f in all_news)} news items")
return output
if __name__ == "__main__":
main()mkdir -p output && python tools/fetch_news.py
**Expected result:** News data is saved to `output/news_digest.json`.
---
๐ Step 2: Implement Email Sending
{
"title": "๐ Step 2: Email Sending",
"questions": [{
"id": "step_action",
"prompt": "Add email sending functionality for the fetched news.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how smtplib works"},
{"id": "skip", "label": "Skip"}
]
}]
}**Guidance after selection:**
Add a sending function to `tools/fetch_news.py`:
import smtplib
from email.mime.text import MIMEText
import os
def send_email(news_data):
"""Send a news digest email"""
smtp_user = os.environ.get("SMTP_USER", "")
smtp_pass = os.environ.get("SMTP_PASS", "")
to_email = os.environ.get("NOTIFY_EMAIL", smtp_user)
if not smtp_user or not smtp_pass:
print("[SKIP] SMTP credentials not configured, skipping email")
return
# Build email body
body_lines = [f"# News Digest ({news_data['generated_at'][:10]})\n"]
for feed in news_data["feeds"]:
body_lines.append(f"\n## {feed['source']}")
for item in feed["items"]:
body_lines.append(f"- [{item['title']}]({item['link']})")
body = "\n".join(body_lines)
msg = MIMEText(body, "plain", "utf-8")
msg["Subject"] = f"News Digest {news_data['generated_at'][:10]}"
msg["From"] = smtp_user
msg["To"] = to_email
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(smtp_user, smtp_pass)
server.send_message(msg)
print(f"Email sent: {to_email}")**Note**: For Gmail, an app password is required. Set `SMTP_USER` and `SMTP_PASS` in Secrets.
**Expected result:** The news digest is sent by email.
---
๐ Step 3: Set Up Slack Webhook Notifications
{
"title": "๐ Step 3: Slack Notifications",
"questions": [{
"id": "step_action",
"prompt": "Send news notifications via a Slack Incoming Webhook.",
"options": [
{"id": "practice", "label": "Proceed"},
{"id": "review", "label": "Review how to create a Slack Webhook"},
{"AI Agent Training for Non-Engineers - Complete Guide to Claude Code / Cursor / Codex ### โ ๏ธ Before you clone Official repository (maintained by the authors): Running AI agents from this repo grants them shell, file-write, and external-API permissions on your
Other commands on ai-agent-camp.
- /check-setup.en
Top-level alias โ see lesson/check-setup.en.md for the full body.
Open command - /check-setup.es
Alias de nivel superior โ el cuerpo completo estรก en lesson/check-setup.es.md.
Open command - /check-setup
Top-level alias โ see lesson/check-setup.md for the full body.
Open command - /check-security.en
Lesson command
Open command - /check-security.es
Lesson command
Open command - /check-security
Lesson command
Open command

