RECIPE P1
미니 프로젝트 — 나만의 아침 브리핑 봇 만들기
- ▸챕터 1에서 배운 노드들을 조합해 완성형 워크플로우를 구축할 수 있다
- ▸RSS 뉴스 + 날씨 + 캘린더 일정을 하나의 메시지로 합칠 수 있다
- ▸Slack 또는 이메일로 매일 아침 자동 브리핑을 발송할 수 있다
✅사전 준비
- □레시피 001~010 워크플로우 각각 정상 동작 확인
- □Slack Webhook URL 또는 Gmail 자격증명 연결 완료
- □OpenWeatherMap API 키 발급 (https://openweathermap.org/api)
- □Google Calendar 자격증명 연결 완료
- □수집할 RSS 피드 URL 2~3개 준비
전체 워크플로우 구성 순서
[Schedule Trigger] 매일 07:00 (Cron: 0 7 * * *)
↓
[HTTP Request] 날씨 API 호출
↓
[RSS Feed Read] 뉴스 피드 1~3개 병렬 수집
↓
[Google Calendar] 오늘 일정 조회
↓
[Code 노드] 메시지 조합
↓
[Slack / Gmail] 브리핑 발송날씨 API 호출 — HTTP Request 설정
Method: GET
URL: https://api.openweathermap.org/data/2.5/weather
Query Parameters:
q: Seoul,KR
appid: YOUR_API_KEY
units: metric
lang: krCode 노드 — 브리핑 메시지 조합
const weather = $('HTTP Request').item.json;
const news = $('RSS Feed Read').all().slice(0, 3);
const events = $('Google Calendar').all();
const today = new Date().toLocaleDateString('ko-KR', {
year: 'numeric', month: 'long', day: 'numeric', weekday: 'long'
});
// 날씨 섹션
const weatherText =
`🌤 오늘 날씨: ${weather.weather[0].description}` +
` 🌡 ${Math.round(weather.main.temp)}°C` +
` (체감 ${Math.round(weather.main.feels_like)}°C)`;
// 일정 섹션
const eventText = events.length
? events.map(e => ` • ${e.json.summary} (${e.json.start?.dateTime?.slice(11,16) ?? '종일'})`).join('\n')
: ' • 오늘 일정 없음';
// 뉴스 섹션
const newsText = news
.map((n, i) => ` ${i + 1}. ${n.json.title}`)
.join('\n');
const message =
`📅 *${today} 아침 브리핑*
${weatherText}
📆 *오늘의 일정*
${eventText}
📰 *오늘의 주요 뉴스*
${newsText}
`;
return [{ json: { message } }];Slack 노드 설정
Resource: Message
Operation: Send
Channel: #daily-briefing (또는 개인 DM)
Text: {{ $json.message }}
Options:
- Parse Mode: Markdown💡 TIP뉴스 피드가 여러 개라면 Merge 노드(Mode: Combine, Combination Mode: Multiplex)를 사용해 병합한 뒤 Code 노드로 넘기세요. 피드별로 RSS Feed Read 노드를 각각 추가하고 모두 Merge 노드에 연결하면 됩니다.
⚠️ 주의Google Calendar API는 당일 자정(00:00) 이후부터 이벤트를 조회합니다. timeMin을 오늘 00:00, timeMax를 오늘 23:59로 설정해야 당일 일정만 나옵니다. 표현식: {{ $today.startOf('day').toISO() }}
⚡
워크플로우 JSON
아래 JSON을 복사해 n8n에 바로 임포트하세요
📥 n8n 임포트 방법
n8n 화면 우측 상단 메뉴 (⋮) → Import from JSON → 아래 JSON 전체 복사 후 붙여넣기 → Import 클릭
n8n 화면 우측 상단 메뉴 (⋮) → Import from JSON → 아래 JSON 전체 복사 후 붙여넣기 → Import 클릭
{
"name": "mini_project_ch01_morning_briefing_bot",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"triggerAtHour": 8,
"triggerAtMinute": 0
}
]
}
},
"id": "schedule-mp01-01",
"name": "매일 오전 8시 트리거",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
200,
400
]
},
{
"parameters": {
"resource": "event",
"operation": "getAll",
"calendarId": {
"__rl": true,
"mode": "id",
"value": "primary"
},
"timeMin": "={{ $now.startOf('day').toISO() }}",
"timeMax": "={{ $now.endOf('day').toISO() }}",
"options": {
"orderBy": "startTime",
"singleEvents": true
},
"returnAll": true
},
"id": "gcal-mp01-02",
"name": "오늘 일정 가져오기",
"type": "n8n-nodes-base.googleCalendar",
"typeVersion": 1.3,
"position": [
420,
200
]
},
{
"parameters": {
"url": "={{ $env.NEWS_RSS_URL }}"
},
"id": "rss-mp01-03",
"name": "뉴스 RSS 3건 수집",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.1,
"position": [
420,
400
]
},
{
"parameters": {
"maxItems": 3
},
"id": "limit-mp01-04",
"name": "뉴스 3건 제한",
"type": "n8n-nodes-base.limit",
"typeVersion": 1,
"position": [
640,
400
]
},
{
"parameters": {
"method": "GET",
"url": "https://api.openweathermap.org/data/2.5/weather",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "Seoul,KR"
},
{
"name": "appid",
"value": "={{ $env.OPENWEATHER_API_KEY }}"
},
{
"name": "units",
"value": "metric"
},
{
"name": "lang",
"value": "kr"
}
]
},
"options": {}
},
"id": "http-mp01-05",
"name": "날씨 API 요청",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
420,
600
]
},
{
"parameters": {
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.NOTION_TASK_DB_ID }}"
},
"returnAll": true,
"filterType": "manual",
"filters": {
"conditions": [
{
"key": "마감일",
"condition": "date_on_or_before",
"dateValue": {
"type": "expression",
"expressionValue": "={{ $now.plus({days: 3}).toFormat('yyyy-MM-dd') }}"
}
},
{
"key": "상태",
"condition": "does_not_equal",
"textValue": "완료"
}
]
},
"options": {}
},
"id": "notion-mp01-06",
"name": "마감 임박 작업 가져오기",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [
420,
800
]
},
{
"parameters": {
"mode": "passThrough",
"output": "all"
},
"id": "merge-mp01-07",
"name": "4개 데이터 병합",
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
860,
500
]
},
{
"parameters": {
"jsCode": "// 각 소스에서 데이터 추출\nconst allItems = $input.all();\n\n// 오늘 날짜 헤더\nconst today = $now.setLocale('ko').toFormat('yyyy년 M월 d일 cccc');\n\n// 일정 섹션\nconst calItems = allItems.filter(i => i.json.kind === 'calendar#event' || i.json.summary !== undefined);\nlet scheduleSection = '🗓 *오늘의 일정*\\n';\nif (calItems.length === 0) {\n scheduleSection += '등록된 일정이 없어요.\\n';\n} else {\n calItems.forEach(item => {\n const ev = item.json;\n const title = ev.summary || '(제목 없음)';\n const startRaw = ev.start?.dateTime || ev.start?.date || '';\n const dt = new Date(startRaw);\n const time = ev.start?.dateTime\n ? `${String(dt.getHours()).padStart(2,'0')}:${String(dt.getMinutes()).padStart(2,'0')}`\n : '종일';\n scheduleSection += ` • ${time} ${title}\\n`;\n });\n}\n\n// 뉴스 섹션\nconst newsItems = allItems.filter(i => i.json.link !== undefined && i.json.title !== undefined);\nlet newsSection = '📰 *오늘의 주요 뉴스*\\n';\nif (newsItems.length === 0) {\n newsSection += '수집된 뉴스가 없어요.\\n';\n} else {\n newsItems.forEach((item, idx) => {\n newsSection += ` ${idx+1}. <${item.json.link}|${item.json.title}>\\n`;\n });\n}\n\n// 날씨 섹션\nconst weatherItem = allItems.find(i => i.json.weather !== undefined);\nlet weatherSection = '🌤 *오늘 날씨*\\n';\nif (weatherItem) {\n const desc = weatherItem.json.weather?.[0]?.description || '';\n const temp = Math.round(weatherItem.json.main?.temp ?? 0);\n const tempMax = Math.round(weatherItem.json.main?.temp_max ?? 0);\n weatherSection += ` 서울 ${desc} / 현재 ${temp}°C / 최고 ${tempMax}°C\\n`;\n} else {\n weatherSection += '날씨 정보를 가져오지 못했어요.\\n';\n}\n\n// Notion 마감 작업 섹션\nconst taskItems = allItems.filter(i => i.json.properties !== undefined);\nlet taskSection = '⚠️ *마감 임박 작업 (3일 이내)*\\n';\nif (taskItems.length === 0) {\n taskSection += '임박한 마감 작업이 없어요. 👍\\n';\n} else {\n taskItems.forEach(item => {\n const title = item.json.properties?.이름?.title?.[0]?.plain_text\n || item.json.properties?.Name?.title?.[0]?.plain_text\n || '(제목 없음)';\n const dueRaw = item.json.properties?.마감일?.date?.start || '';\n const due = dueRaw\n ? new Date(dueRaw).toLocaleDateString('ko-KR', { month: 'long', day: 'numeric' })\n : '날짜 미정';\n taskSection += ` • ${title} (${due}까지)\\n`;\n });\n}\n\n// 최종 메시지 조합\nconst message = [\n `📅 *${today} 모닝 브리핑*`,\n '',\n scheduleSection,\n newsSection,\n weatherSection,\n taskSection,\n '좋은 하루 되세요! ☀️'\n].join('\\n');\n\nreturn [{ json: { message } }];"
},
"id": "code-mp01-08",
"name": "브리핑 메시지 통합 조합",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1080,
500
]
},
{
"parameters": {
"resource": "message",
"operation": "post",
"channel": "#morning-briefing",
"text": "={{ $json.message }}",
"otherOptions": {}
},
"id": "slack-mp01-09",
"name": "Slack 모닝 브리핑 발송",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
1300,
400
]
},
{
"parameters": {
"resource": "message",
"operation": "send",
"toEmail": "={{ $env.MY_EMAIL }}",
"subject": "={{ '[모닝 브리핑] ' + $now.setLocale('ko').toFormat('M월 d일 cccc') }}",
"message": "={{ $json.message.replace(/\\*/g, '').replace(/<([^|>]+)\\|([^>]+)>/g, '$2 ($1)') }}",
"options": {}
},
"id": "gmail-mp01-10",
"name": "Gmail 모닝 브리핑 발송",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1300,
600
]
}
],
"connections": {
"매일 오전 8시 트리거": {
"main": [
[
{
"node": "오늘 일정 가져오기",
"type": "main",
"index": 0
},
{
"node": "뉴스 RSS 3건 수집",
"type": "main",
"index": 0
},
{
"node": "날씨 API 요청",
"type": "main",
"index": 0
},
{
"node": "마감 임박 작업 가져오기",
"type": "main",
"index": 0
}
]
]
},
"오늘 일정 가져오기": {
"main": [
[
{
"node": "4개 데이터 병합",
"type": "main",
"index": 0
}
]
]
},
"뉴스 RSS 3건 수집": {
"main": [
[
{
"node": "뉴스 3건 제한",
"type": "main",
"index": 0
}
]
]
},
"뉴스 3건 제한": {
"main": [
[
{
"node": "4개 데이터 병합",
"type": "main",
"index": 1
}
]
]
},
"날씨 API 요청": {
"main": [
[
{
"node": "4개 데이터 병합",
"type": "main",
"index": 2
}
]
]
},
"마감 임박 작업 가져오기": {
"main": [
[
{
"node": "4개 데이터 병합",
"type": "main",
"index": 3
}
]
]
},
"4개 데이터 병합": {
"main": [
[
{
"node": "브리핑 메시지 통합 조합",
"type": "main",
"index": 0
}
]
]
},
"브리핑 메시지 통합 조합": {
"main": [
[
{
"node": "Slack 모닝 브리핑 발송",
"type": "main",
"index": 0
},
{
"node": "Gmail 모닝 브리핑 발송",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"timezone": "Asia/Seoul"
},
"meta": {
"chapter": 1,
"type": "mini_project",
"id": "mini_project_ch01",
"title": "나만의 아침 브리핑 봇 만들기",
"difficulty": "⭐⭐⭐",
"estimatedTime": "60분",
"linkedRecipes": [
"003",
"005",
"007",
"008"
],
"apps": [
"Google Calendar",
"RSS Feed",
"OpenWeatherMap API",
"Notion",
"Slack",
"Gmail"
],
"outcome": "매일 오전 8시, 오늘의 일정 / 주요 뉴스 3건 / 날씨 / 마감 임박 작업을 하나의 메시지로 Slack과 Gmail로 자동 수신",
"workflowUrl": "www.koreaaitimes.com/books/n8n-vol2/recipe010p"
}
}