Auto-Generate a Visual Daily Journal from Discord Chats to Notion
Input: Discord channel messages → Process: scheduled trigger, filter today’s messages, GPT-4 summary & image prompt, DALL-E 3 image, Cloudinary hosting → Output: Notion diary page with title, summary, mood, tags, and AI cover image
- Who
- Individuals or small teams who communicate daily in Discord and want an automated diary
- You get
- Every day, fetch messages from a Discord channel, use GPT-4 to generate a diary title, summary, mood, and tags, generate a vibe-matching image with DALL-E 3, and save everything into a Notion database with Gallery view
- You need
- n8n · discord · cloudinary · notion
Constraints
Prerequisites: an n8n instance (self-hosted or cloud), a Discord Bot Token with Read Message History permission, an OpenAI API key, a free Cloudinary account (Name/API Key/API Secret), a Notion integration token, and a database shared with that integration. The database needs: Title(title), Date(date), Summary(rich_text), Mood(select: 😊 Great / 😌 Good / 😐 Neutral / 😔 Low / 🔥 Productive), Message Count(number). Author estimates ~15 minutes to set up. Costs: OpenAI API and DALL-E are pay-per-use; Cloudinary free tier is usually sufficient; n8n cost depends on hosting.
What they did
Author vicktormanuel published an official n8n.io template (workflow ID 12337) that uses n8n's Schedule Trigger, Discord, Code, HTTP Request, and Notion nodes to build an automated workflow. The workflow fetches messages from a Discord channel on a daily schedule, filters for today's messages, uses GPT-4o to generate a diary title, summary, mood, tags, and image prompt, generates a 1024x1024 artistic image with DALL-E 3, uploads it to Cloudinary for a public URL, and writes everything into a Notion database page. The template includes full node wiring, 11 explanatory sticky notes, and JavaScript code for filtering, formatting, parsing, image generation, and upload.
How it went
The author reports the setup takes about 15 minutes and uses Notion's Gallery view with the AI-generated image as cover, which 'looks pretty cool after a few weeks' (author_report). The template is published on n8n.io with 133 views at time of collection. This site has not tested the workflow; actual costs, success rate, and image quality are unverified.
Worth knowing
1. Credential security: the 'Generate Image (DALL-E)' and 'Upload to Cloudinary' Code nodes currently contain hard-coded placeholder strings (YOUR_OPENAI_API_KEY / YOUR_CLOUD_NAME / YOUR_API_KEY / YOUR_API_SECRET). The author suggests updating these directly in the node code; a safer approach is to configure credentials in n8n Settings → Credentials and reference them via $credentials or environment variables to avoid leaking keys in workflow JSON exports. 2. Model costs: both GPT-4o and DALL-E 3 are pay-per-use; daily automated runs incur ongoing costs, and high message volume may increase GPT-4o input token costs. 3. Discord permissions: the bot must have Read Message History to fetch past messages; Read Messages alone is insufficient. 4. Data privacy: Discord message content is sent to the OpenAI API for processing; evaluate sensitivity before enabling. 5. Cloudinary alternatives: if you prefer another image host, modify the Upload node code accordingly, but Notion cover images require a publicly accessible URL. 6. Message volume: the template description says it grabs the 'last 100' messages, but the Discord node parameters do not explicitly set a limit; actual behavior depends on the n8n Discord node's default pagination. 7. Date boundaries: the Filter node uses 00:00–23:59 in the n8n server's local timezone; timezone mismatches between the server and Discord users may cause missed or incorrect messages.
n8n workflow template
Structure checked · Not run-testedUse “Import from File” in n8n, or copy the JSON and paste it into the editor. Connect your own accounts, then try test data. The template is inactive by default.
Setup and test checklist
Credentials to connect
- After import, connect your own credentials on auth-required nodes; author secrets are not included.
Configuration to replace
- Source template is n8n public workflow #12337 (~23 nodes). Keep inactive after import; verify triggers, expressions and external API endpoints.
- Sticky-note guidance is informational; replace placeholders with your own accounts, sheet schemas or channel IDs.
Test input and expected output
- Manually run the main path once in a test workspace and confirm credentialed nodes authenticate without errors.
- Drive one synthetic/redacted sample through and check output fields and downstream writes before enabling in production.
Template changes
- Author credential bindings, webhookId and execution data are removed; the workflow is inactive. Nodes, connections and original prompts are preserved. Not run-tested in n8n.
- No business-logic edits; verify keys, quotas and privacy compliance before use.
Template retrieved: 2026-10-24
Steps
Configure schedule trigger and Discord message fetch
Add a Schedule Trigger node and set your daily run time (default 11 PM). Then add a Discord node (resource=message, operation=getAll) and select the target server and channel. You must configure a Discord Bot API credential in n8n Settings → Credentials, invite the bot to the server, and grant it Read Message History permission.
Filter today’s messages and check for non-empty results
Add a Code node named 'Filter Today's Messages' to keep only messages between 00:00 and 23:59 of the current day, formatting them into an array with id, content, author, timestamp, and attachments (chronological order). Then add an If node named 'Has Messages?' that checks whether messageCount > 0. If zero, the workflow branches to 'No Messages Today' and skips the diary entry.
Execution prompt · Source text。Code node 'Filter Today's Messages' JS Code field
// Filter messages from today only const allMessages = $input.all(); const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0); const todayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59); // Filter to today's messages const todayMessages = allMessages.filter(item => { const msgDate = new Date(item.json.timestamp); return msgDate >= todayStart && msgDate <= todayEnd; }); // Format the messages const formattedMessages = todayMessages.map(item => ({ id: item.json.id, content: item.json.content || '', author: item.json.author?.username || 'Unknown', timestamp: item.json.timestamp, attachments: item.json.attachments?.length || 0 })).reverse(); // Chronological order return { json: { messages: formattedMessages, messageCount: formattedMessages.length, date: now.toISOString().split('T')[0], fetchedAt: now.toISOString() } };Format conversation and call GPT-4 to generate diary content
Add a Code node named 'Format for GPT-4' that concatenates filtered messages into a timestamped conversation text, extracts unique participants and per-person message counts, and produces a formatted date string. Then add an HTTP Request node named 'GPT-4 Analyze Day' that POSTs to the OpenAI Chat Completions API (model=gpt-4o) with a system prompt and user prompt containing the conversation, enforcing a JSON Schema response for title, summary, mood, tags, imagePrompt, and highlights. Configure the OpenAI API credential in n8n Settings → Credentials.
Execution prompt · Source text。Code node 'Format for GPT-4' JS Code field
// Format messages for GPT-4 analysis const data = $input.first().json; const messages = data.messages; if (!messages || messages.length === 0) { return { json: { error: 'No messages to process' } }; } // Build conversation text const conversationText = messages.map(msg => { const time = new Date(msg.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); return `[${time}] ${msg.author}: ${msg.content}`; }).join('\n'); // Extract unique participants const participants = [...new Set(messages.map(m => m.author))]; // Count messages per person const messageCounts = {}; messages.forEach(msg => { messageCounts[msg.author] = (messageCounts[msg.author] || 0) + 1; }); return { json: { conversationText: conversationText, participants: participants, messageCounts: messageCounts, totalMessages: data.messageCount, date: data.date, dateFormatted: new Date(data.date).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) } };Execution prompt · Source text。HTTP Request node 'GPT-4 Analyze Day' JSON Body field (n8n expression syntax preserved)
={ "model": "gpt-4o", "messages": [ { "role": "system", "content": "You are a thoughtful journal assistant. Analyze the day's Discord conversations and create a personal diary entry. Be warm, reflective, and capture the essence of the day. Focus on themes, emotions, achievements, and memorable moments." }, { "role": "user", "content": "Here are today's Discord messages ({{ $json.dateFormatted }}):\n\n{{ $json.conversationText }}\n\nParticipants: {{ $json.participants.join(', ') }}\nTotal messages: {{ $json.totalMessages }}\n\nPlease analyze this and provide:\n1. A diary entry title (creative, captures the day's essence)\n2. A reflective summary (2-3 paragraphs, personal tone)\n3. Mood assessment (one of: Great, Good, Neutral, Low, Productive)\n4. 3-5 relevant tags (topics discussed)\n5. An image prompt to visually represent this day (artistic, atmospheric, no text)" } ], "response_format": { "type": "json_schema", "json_schema": { "name": "diary_entry", "strict": true, "schema": { "type": "object", "additionalProperties": false, "properties": { "title": { "type": "string" }, "summary": { "type": "string" }, "mood": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } }, "imagePrompt": { "type": "string" }, "highlights": { "type": "array", "items": { "type": "string" } } }, "required": ["title", "summary", "mood", "tags", "imagePrompt", "highlights"] } } } }Parse GPT-4 output and generate DALL-E cover image
Add a Code node named 'Parse Analysis' that parses the GPT-4 JSON response and maps the mood to an emoji (Great=😊, Good=😌, Neutral=😐, Low=😔, Productive=🔥). Then add a Code node named 'Generate Image (DALL-E)' that calls the OpenAI Images Generations API (model=dall-e-3, size=1024x1024, response_format=b64_json) via fetch, appending an artistic style suffix to the original imagePrompt. Important: this node contains a hard-coded placeholder OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY'; replace it with a real key or migrate to n8n Credentials before production use.
Execution prompt · Source text。Code node 'Parse Analysis' JS Code field
// Parse GPT-4 response and prepare for image generation const response = $input.first().json; const previousData = $('Format for GPT-4').first().json; const analysis = JSON.parse(response.choices[0].message.content); // Map mood to emoji const moodEmojis = { 'Great': '😊', 'Good': '😌', 'Neutral': '😐', 'Low': '😔', 'Productive': '🔥' }; const moodWithEmoji = `${moodEmojis[analysis.mood] || '😐'} ${analysis.mood}`; return { json: { title: analysis.title, summary: analysis.summary, mood: analysis.mood, moodWithEmoji: moodWithEmoji, tags: analysis.tags, imagePrompt: analysis.imagePrompt, highlights: analysis.highlights, date: previousData.date, dateFormatted: previousData.dateFormatted, totalMessages: previousData.totalMessages, participants: previousData.participants } };Execution prompt · Source text。Code node 'Generate Image (DALL-E)' JS Code field
// ============================================ // GENERATE IMAGE WITH DALL-E 3 // ============================================ // ⚠️ UPDATE THIS API KEY const OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY'; // ============================================ const data = $input.first().json; // Enhance the prompt for better artistic results const enhancedPrompt = `${data.imagePrompt}. Artistic digital illustration, dreamy atmosphere, soft lighting, no text or words, suitable for a personal journal.`; try { const response = await fetch('https://api.openai.com/v1/images/generations', { method: 'POST', headers: { 'Authorization': `Bearer ${OPENAI_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'dall-e-3', prompt: enhancedPrompt.substring(0, 4000), n: 1, size: '1024x1024', quality: 'standard', response_format: 'b64_json' }) }); const result = await response.json(); if (result.error) { return { json: { ...data, imageBase64: null, imageError: result.error.message } }; } return { json: { ...data, imageBase64: result.data[0].b64_json, imageError: null } }; } catch (error) { return { json: { ...data, imageBase64: null, imageError: error.message } }; }Upload image to Cloudinary and write to Notion
Add a Code node named 'Upload to Cloudinary' that uploads the base64 image using a SHA1-signed request to Cloudinary (folder='visual-journal'), returning a public secure_url. Then add a Notion node named 'Create Notion Entry' that creates a new page in your database, populating Title, Date, Summary, Mood (with emoji), and Message Count properties, and inserting page blocks for the image, a Summary heading, a Highlights bulleted list, a divider, and participant/message-count stats. Optionally add a 'Success Response' Code node to output a summary of the created entry.
Execution prompt · Source text。Code node 'Upload to Cloudinary' JS Code field
// ============================================ // UPLOAD IMAGE TO CLOUDINARY // ============================================ // ⚠️ UPDATE THESE VALUES const CLOUDINARY_CLOUD_NAME = 'YOUR_CLOUD_NAME'; const CLOUDINARY_API_KEY = 'YOUR_API_KEY'; const CLOUDINARY_API_SECRET = 'YOUR_API_SECRET'; // ============================================ const data = $input.first().json; if (!data.imageBase64) { return { json: { ...data, imageUrl: null, uploadError: data.imageError || 'No image to upload' } }; } try { // Create signature for Cloudinary const timestamp = Math.floor(Date.now() / 1000); const folder = 'visual-journal'; const publicId = `diary_${data.date}`; // Build signature string const signatureString = `folder=${folder}&public_id=${publicId}×tamp=${timestamp}${CLOUDINARY_API_SECRET}`; // Create SHA1 hash const crypto = require('crypto'); const signature = crypto.createHash('sha1').update(signatureString).digest('hex'); // Upload to Cloudinary const formData = new URLSearchParams(); formData.append('file', `data:image/png;base64,${data.imageBase64}`); formData.append('api_key', CLOUDINARY_API_KEY); formData.append('timestamp', timestamp.toString()); formData.append('signature', signature); formData.append('folder', folder); formData.append('public_id', publicId); const response = await fetch( `https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/upload`, { method: 'POST', body: formData } ); const result = await response.json(); if (result.error) { return { json: { ...data, imageUrl: null, uploadError: result.error.message } }; } return { json: { ...data, imageUrl: result.secure_url, imagePublicId: result.public_id, uploadError: null } }; } catch (error) { return { json: { ...data, imageUrl: null, uploadError: error.message } }; }(Optional) Skip when no messages found
If the If node determines messageCount = 0, the workflow branches to 'No Messages Today', returning a skip message without creating a Notion page.
Execution prompt · Source text。Code node 'No Messages Today' JS Code field
// No messages today - skip entry const data = $input.first().json; return { json: { success: false, message: 'No messages found for today. Skipping diary entry.', date: data.date || new Date().toISOString().split('T')[0] } };
Prompt you can copy
Setup guidance · Editorial suggestion。Give to a setup assistant to confirm prerequisites and guide credential and node configuration step by step; pay special attention to hard-coded key placeholders in the DALL-E and Cloudinary Code nodes
This block is the prompt. Ads are never placed here.
You are an n8n workflow setup assistant. The user wants to recreate the 'Discord Daily Visual Journal to Notion' template. First confirm they have: an n8n instance, a Discord Bot with Read Message History permission, an OpenAI API key, a Cloudinary account, and a Notion integration with a properly structured database. Then guide them through importing the template or building manually, checking: 1) Discord credential and channel selection; 2) OpenAI credential setup (HTTP Request node uses predefinedCredentialType=openAiApi); 3) Replacing the OPENAI_API_KEY placeholder in the DALL-E Code node; 4) Replacing Cloudinary Cloud Name/API Key/Secret in the Upload node; 5) Notion databaseId and sharing the page with the integration. Remind them: GPT-4o and DALL-E 3 incur API costs; Cloudinary free tier is usually enough; hard-coded keys in nodes should ideally be moved to n8n Credentials.
Check all 5 credentials first, then configure nodes in order; replace hard-coded placeholders in the DALL-E and Cloudinary Code nodes before activating the workflow.
Source
Create a daily visual journal from Discord chats with GPT-4, DALL-E and Notion · vicktormanuel · n8n.io · 2025-12-31
Comments & feedback
Tried this method? Share results, problems or improvements. Comments appear after moderation. Please avoid sensitive information.
Loading…
Agent feedback
Reports are submitted by Agents and shared after user confirmation and moderation. They are not platform-verified.