Loading...
Please wait while we prepare your experience
Please wait while we prepare your experience
Automate your entire Instagram strategy with N8N. Save 35+ hours/week managing multiple accounts with auto-posting, DM automation, and analytics tracking.
Result: 35 hours/week saved + $8,000/month revenue increase
You MUST understand these limitations before building automation:
This architecture connects Airtable (content database), N8N (automation engine), Instagram Graph API (publishing), and analytics platforms (tracking).
Airtable Content Database
↓
N8N Scheduler (checks every 30 min)
↓
Content Formatter Node
↓
Instagram Graph API
↓
[Post Published]
↓
Analytics Tracker → Google Sheets/Notion
↓
Performance Reports → Slack Notification| Component | Purpose | Execution Time | Node Count |
|---|---|---|---|
| Content Scheduler | Fetch posts from Airtable, check scheduled time | 2-3 seconds | 4 nodes |
| Image Processor | Download, resize, optimize images | 5-8 seconds | 6 nodes |
| Caption Generator | Format caption, add hashtags, emojis | 1 second | 3 nodes |
| Publisher | Publish to Instagram via Graph API | 3-5 seconds | 5 nodes |
| Comment Monitor | Check for new comments, auto-respond | 2 seconds | 7 nodes |
| DM Automation | Respond to incoming DMs with templates | 1-2 seconds | 8 nodes |
| Analytics Collector | Gather engagement metrics, save to database | 4-6 seconds | 9 nodes |
Automatically publishes Instagram posts from your content calendar in Airtable or Google Sheets. Schedule posts weeks in advance, and N8N handles the rest - including image processing, caption formatting, and hashtag optimization.
Create your content calendar structure
| Field Name | Type | Purpose |
|---|---|---|
| Post ID | Auto Number | Unique identifier |
| Schedule Time | Date/Time | When to publish |
| Caption | Long Text | Post caption |
| Image URL | URL/Attachment | Image to post |
| Hashtags | Long Text | Hashtag list |
| Status | Single Select | Draft/Scheduled/Published |
| Account | Single Select | Which IG account |
💡 Pro Tip:
Add a "Performance" field group to track likes, comments, saves, and reach after publishing. This enables automatic ROI tracking.
Check Airtable every 30 minutes for posts ready to publish
Node Type:
Schedule TriggerInterval:
Every 30 minutesCron Expression (Alternative):
*/30 * * * *Runs at :00 and :30 of every hour
Query posts ready to publish
Operation: List
Table: Content Calendar
View: Ready to Publish
Filter Formula:
AND(
{Status} = "Scheduled",
{Schedule Time} <= NOW(),
{Schedule Time} >= DATEADD(NOW(), -30, 'minutes')
)
Fields to Return:
- Post ID
- Caption
- Image URL
- Hashtags
- Account
- Schedule Time⚠️ Important:
The filter ensures you only fetch posts scheduled in the last 30 minutes. This prevents re-publishing old posts if the workflow was temporarily disabled.
Prepare caption and download images
// Format caption with hashtags
const items = $input.all();
return items.map(item => {
const caption = item.json.Caption || '';
const hashtags = item.json.Hashtags || '';
// Combine caption and hashtags
let fullCaption = caption.trim();
// Add spacing before hashtags
if (hashtags) {
fullCaption += '\n\n' + hashtags;
}
// Ensure it's within Instagram's 2,200 character limit
if (fullCaption.length > 2200) {
fullCaption = fullCaption.substring(0, 2197) + '...';
}
return {
json: {
...item.json,
FormattedCaption: fullCaption,
CharacterCount: fullCaption.length
}
};
});Method: GET
URL: {{$json["Image URL"]}}
Response Format: File
Binary Property: image_data
Options:
- Timeout: 30000ms
- Follow Redirects: YesTwo-step publishing process (required by Instagram)
HTTP Request Node:
Method: POST
URL: https://graph.facebook.com/v18.0/{{$json["InstagramAccountID"]}}/media
Headers:
- Authorization: Bearer {{$credentials.facebookAccessToken}}
Body (JSON):
{
"image_url": "{{$json["Image URL"]}}",
"caption": "{{$json["FormattedCaption"]}}",
"access_token": "{{$credentials.facebookAccessToken}}"
}
Response contains:
- creation_id (needed for next step)HTTP Request Node:
Method: POST
URL: https://graph.facebook.com/v18.0/{{$json["InstagramAccountID"]}}/media_publish
Body (JSON):
{
"creation_id": "{{$json["creation_id"]}}",
"access_token": "{{$credentials.facebookAccessToken}}"
}
Response contains:
- id (published post ID)💡 Why Two Steps?
Instagram Graph API requires creating a media container first, then publishing it. This allows Instagram to process and validate your image/video before it goes live.
Mark as published and notify team
Operation: Update
Table: Content Calendar
Record ID: {{$json["Post ID"]}}
Fields to Update:
- Status: "Published"
- Instagram Post ID: {{$json["id"]}}
- Published At: {{$now.toISO()}}
- Post URL: https://www.instagram.com/p/{{$json["id"]}}/Channel: #instagram-updates
Message:
✅ Instagram Post Published!
Account: {{$json["Account"]}}
Caption: {{$json["Caption"].substring(0, 100)}}...
Post URL: https://www.instagram.com/p/{{$json["id"]}}/
Scheduled: {{$json["Schedule Time"]}}
Published: {{$now.format('MM/DD/YYYY HH:mm')}}Handle API failures gracefully
Error Trigger Node (connects to any node's error output)
↓
Check Error Type (IF Node)
↓
Rate Limit Error? → Wait 15 minutes → Retry
Image Error? → Send Alert → Mark as Failed
API Error? → Wait 5 minutes → Retry (max 3 times)
↓
Update Airtable Status = "Failed"
↓
Send Urgent Alert to Slack with Error Details⚠️ Common Errors to Handle:
{
"name": "Instagram Auto-Poster from Airtable",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"operation": "list",
"application": "appXXXXXXXXXXXXXX",
"table": "Content Calendar",
"options": {
"filterByFormula": "AND({Status}='Scheduled',{Schedule Time}<=NOW(),{Schedule Time}>=DATEADD(NOW(),-30,'minutes'))"
}
},
"name": "Airtable - Get Scheduled Posts",
"type": "n8n-nodes-base.airtable",
"typeVersion": 1,
"position": [450, 300],
"credentials": {
"airtableApi": {
"id": "1",
"name": "Airtable account"
}
}
},
{
"parameters": {
"functionCode": "const items = $input.all();\n\nreturn items.map(item => {\n const caption = item.json.Caption || '';\n const hashtags = item.json.Hashtags || '';\n let fullCaption = caption.trim();\n if (hashtags) {\n fullCaption += '\\n\\n' + hashtags;\n }\n if (fullCaption.length > 2200) {\n fullCaption = fullCaption.substring(0, 2197) + '...';\n }\n return {\n json: {\n ...item.json,\n FormattedCaption: fullCaption\n }\n };\n});"
},
"name": "Format Caption",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [650, 300]
},
{
"parameters": {
"url": "={{$json[\"Image URL\"]}}",
"responseFormat": "file",
"options": {}
},
"name": "Download Image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [850, 300]
},
{
"parameters": {
"url": "=https://graph.facebook.com/v18.0/{{$json[\"InstagramAccountID\"]}}/media",
"method": "POST",
"jsonParameters": true,
"options": {},
"bodyParametersJson": "={ \"image_url\": \"{{$json[\"Image URL\"]}}\" \"caption\": \"{{$json[\"FormattedCaption\"]}}\" }"
},
"name": "Create Media Container",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [1050, 300],
"credentials": {
"httpHeaderAuth": {
"id": "2",
"name": "Facebook Access Token"
}
}
},
{
"parameters": {
"url": "=https://graph.facebook.com/v18.0/{{$json[\"InstagramAccountID\"]}}/media_publish",
"method": "POST",
"jsonParameters": true,
"bodyParametersJson": "={ \"creation_id\": \"{{$json[\"creation_id\"]}}\" }"
},
"name": "Publish Post",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [1250, 300]
},
{
"parameters": {
"operation": "update",
"application": "appXXXXXXXXXXXXXX",
"table": "Content Calendar",
"id": "={{$json[\"Post ID\"]}}",
"fieldsUi": {
"fieldValues": [
{
"fieldName": "Status",
"fieldValue": "Published"
},
{
"fieldName": "Instagram Post ID",
"fieldValue": "={{$json[\"id\"]}}"
},
{
"fieldName": "Published At",
"fieldValue": "={{$now.toISO()}}"
}
]
}
},
"name": "Update Airtable",
"type": "n8n-nodes-base.airtable",
"typeVersion": 1,
"position": [1450, 300]
},
{
"parameters": {
"channel": "#instagram-updates",
"text": "=✅ Instagram Post Published!\n\nAccount: {{$json[\"Account\"]}}\nPost URL: https://www.instagram.com/p/{{$json[\"id\"]}}/",
"otherOptions": {}
},
"name": "Slack Notification",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [1650, 300],
"credentials": {
"slackApi": {
"id": "3",
"name": "Slack account"
}
}
}
],
"connections": {
"Schedule Trigger": {
"main": [[{ "node": "Airtable - Get Scheduled Posts", "type": "main", "index": 0 }]]
},
"Airtable - Get Scheduled Posts": {
"main": [[{ "node": "Format Caption", "type": "main", "index": 0 }]]
},
"Format Caption": {
"main": [[{ "node": "Download Image", "type": "main", "index": 0 }]]
},
"Download Image": {
"main": [[{ "node": "Create Media Container", "type": "main", "index": 0 }]]
},
"Create Media Container": {
"main": [[{ "node": "Publish Post", "type": "main", "index": 0 }]]
},
"Publish Post": {
"main": [[{ "node": "Update Airtable", "type": "main", "index": 0 }]]
},
"Update Airtable": {
"main": [[{ "node": "Slack Notification", "type": "main", "index": 0 }]]
}
}
}📥 How to Import:
Learn how to build powerful automation workflows with N8N, Make, and Zapier. Complete courses on automation strategies for creators and businesses.
Save 35+ hours per week with automation
Monitors all comments on your Instagram posts and automatically responds based on keywords, sentiment, and predefined rules. Flags spam, answers FAQs, and escalates complex questions to humans.
Webhook Trigger (Instagram sends comment event)
↓
Extract Comment Data (username, text, post_id)
↓
Check for Spam (IF Node - keywords, patterns)
↓
[Is Spam?] → Hide Comment → Log to Database
↓ [Not Spam]
Sentiment Analysis (positive/negative/question)
↓
Match Keywords (FAQ responses)
↓
[Match Found?] → Send Auto-Reply → Log to Database
↓ [No Match]
Notify Team → Store in "Needs Response" Database1. Go to developers.facebook.com 2. Select your app → Products → Webhooks 3. Subscribe to "comments" field for Instagram 4. Callback URL: https://your-n8n-instance.com/webhook/instagram-comments 5. Verify Token: your_secret_verify_token_here Subscribed Events: - comments (new comments) - mentions (when tagged) - live_comments (live video comments)
Node Type: Webhook
HTTP Method: POST
Path: instagram-comments
Authentication: None (verified by Facebook)
Response:
- Response Code: 200
- Response Mode: On Received
Webhook Verification (GET request):
Return: {{$parameter["hub.challenge"]}} when hub.verify_token matchesconst items = $input.all();
return items.map(item => {
const comment = item.json.entry[0].changes[0].value;
const text = comment.text.toLowerCase();
const username = comment.from.username;
const commentId = comment.id;
const postId = comment.media.id;
// Spam detection patterns
const spamKeywords = ['dm me', 'check my bio', 'follow me', 'buy followers', '🔥🔥🔥'];
const isSpam = spamKeywords.some(keyword => text.includes(keyword));
// Emoji/link spam
const emojiCount = (text.match(/[\u{1F600}-\u{1F64F}]/gu) || []).length;
const linkCount = (text.match(/http/g) || []).length;
const hasExcessiveEmojis = emojiCount > 5;
const hasLinks = linkCount > 0;
// Question detection
const isQuestion = text.includes('?') ||
text.startsWith('how') ||
text.startsWith('what') ||
text.startsWith('when') ||
text.startsWith('where') ||
text.startsWith('why');
// Sentiment (basic)
const positiveWords = ['love', 'great', 'amazing', 'awesome', 'beautiful', 'thanks'];
const negativeWords = ['bad', 'hate', 'terrible', 'worst', 'disappointed'];
const positiveCount = positiveWords.filter(word => text.includes(word)).length;
const negativeCount = negativeWords.filter(word => text.includes(word)).length;
let sentiment = 'neutral';
if (positiveCount > negativeCount) sentiment = 'positive';
if (negativeCount > positiveCount) sentiment = 'negative';
return {
json: {
commentId,
postId,
username,
text: comment.text,
timestamp: comment.timestamp,
isSpam: isSpam || hasExcessiveEmojis || hasLinks,
isQuestion,
sentiment,
needsResponse: isQuestion && !isSpam
}
};
});| Condition | Action | Response Template |
|---|---|---|
| isSpam = true | Hide comment | No response |
| text contains "price" | Auto-reply | Hi! Check our website for pricing: [link] |
| text contains "shipping" | Auto-reply | We offer free shipping on orders over $50! |
| sentiment = positive | Thank you reply | Thank you so much! ❤️ |
| isQuestion = true | Check FAQ match | Dynamic based on question |
| No match found | Notify team | We'll get back to you soon! |
Method: POST
URL: https://graph.facebook.com/v18.0/{{$json["commentId"]}}/replies
Body (JSON):
{
"message": "{{$json["responseText"]}}",
"access_token": "{{$credentials.facebookAccessToken}}"
}
Rate Limit Handling:
- Max 50 comment replies per day per account
- Implement counter in workflow variables
- If limit reached, queue responses for next dayModel: gpt-3.5-turbo
Temperature: 0.3 (more deterministic)
System Prompt:
"You are a helpful Instagram comment responder for [BRAND NAME].
Respond to customer questions briefly (max 50 words) and friendly.
Use 1-2 emojis maximum. Always include a call-to-action when appropriate.
FAQs:
- Shipping: Free on orders over $50, 3-5 business days
- Returns: 30-day return policy
- Custom orders: Available, DM us for details
- Pricing: Check our website [link]
- Product availability: In stock, ships within 24 hours
If the question is complex or outside these FAQs, say:
'Great question! Please DM us or email support@yourbrand.com for detailed help!'"
User Message:
{{$json["text"]}}
Max Tokens: 100
Stop Sequences: ["
"]💡 Pro Tip: AI Cost Optimization
Use AI only for questions that don't match your predefined templates. This saves API costs while maintaining quality:
Method: POST
URL: https://graph.facebook.com/v18.0/{{$json["commentId"]}}
Body (JSON):
{
"hide": true,
"access_token": "{{$credentials.facebookAccessToken}}"
}
Note: Hiding comments doesn't delete them, just makes them
invisible to other users. The commenter can still see it.
Alternative (Delete):
Method: DELETE
URL: https://graph.facebook.com/v18.0/{{$json["commentId"]}}⚠️ Spam Detection Best Practices:
Table: Comment Log Fields: - Comment ID (unique) - Post ID - Username - Comment Text - Timestamp - Sentiment (positive/neutral/negative) - Is Spam (checkbox) - Is Question (checkbox) - Response Sent (text) - Response Time (datetime) - Handled By (Auto/Human) - Status (Pending/Responded/Escalated) Benefits: ✓ Track response rate and speed ✓ Analyze common questions for FAQ updates ✓ Measure sentiment over time ✓ Audit auto-responses for quality ✓ Train better spam filters
Automatically responds to Instagram DMs with personalized messages, qualifying leads, and routing high-intent prospects to your sales team. Handles FAQs, sends product catalogs, and schedules consultations.
⚠️ API Limitation:
Instagram API only allows responding to incoming DMs. You cannot send cold DMs via automation. However, you can trigger DM responses with Story mentions, comment replies directing users to DM, or lead magnets.
Webhook (Instagram sends message event)
↓
Extract Message Data (sender, text, thread_id)
↓
Check if First Message (new conversation?)
↓ [Yes - New Lead]
Send Welcome Message + Quick Reply Options
↓
[User Selects Option]
↓
Route Based on Selection:
- "Pricing" → Send pricing info + link
- "Book Demo" → Send Calendly link
- "Product Info" → Send catalog
- "Support" → Route to support team
↓
Save Lead to CRM (Airtable/HubSpot)
↓
Qualify Lead (high/medium/low intent)
↓
[High Intent] → Notify Sales Team ImmediatelySubscribe to these fields:
- messages (new direct messages)
- messaging_postbacks (button clicks)
- message_reads (read receipts)
Webhook Fields to Monitor:
{
"entry": [
{
"id": "instagram-account-id",
"messaging": [
{
"sender": {"id": "sender-id"},
"recipient": {"id": "your-account-id"},
"timestamp": 1234567890,
"message": {
"mid": "message-id",
"text": "User's message here"
}
}
]
}
]
}Node Type: Webhook HTTP Method: POST Path: instagram-messages Response Code: 200 Webhook Verification (required): IF hub.mode = "subscribe" AND hub.verify_token = "your_secret_token" THEN return hub.challenge
const items = $input.all();
return items.map(item => {
const messaging = item.json.entry[0].messaging[0];
const senderId = messaging.sender.id;
const messageText = messaging.message?.text?.toLowerCase() || '';
const messageId = messaging.message?.mid;
const timestamp = messaging.timestamp;
// Intent detection
const intents = {
pricing: ['price', 'cost', 'how much', 'pricing', 'rates', '$'],
demo: ['demo', 'consultation', 'call', 'meeting', 'schedule'],
product: ['product', 'features', 'catalog', 'options', 'what do you'],
support: ['help', 'issue', 'problem', 'not working', 'error'],
general: ['hi', 'hello', 'hey', 'info', 'interested']
};
let detectedIntent = 'general';
for (const [intent, keywords] of Object.entries(intents)) {
if (keywords.some(keyword => messageText.includes(keyword))) {
detectedIntent = intent;
break;
}
}
// Lead scoring (basic)
const urgencyWords = ['asap', 'urgent', 'now', 'today', 'immediately'];
const buyingWords = ['buy', 'purchase', 'order', 'get started', 'sign up'];
let leadScore = 0;
if (urgencyWords.some(word => messageText.includes(word))) leadScore += 30;
if (buyingWords.some(word => messageText.includes(word))) leadScore += 40;
if (detectedIntent === 'demo' || detectedIntent === 'pricing') leadScore += 20;
if (messageText.length > 50) leadScore += 10; // Detailed message = serious
let leadQuality = 'low';
if (leadScore >= 50) leadQuality = 'high';
else if (leadScore >= 25) leadQuality = 'medium';
return {
json: {
senderId,
messageText: messaging.message?.text,
messageId,
timestamp,
detectedIntent,
leadScore,
leadQuality,
isFirstMessage: messaging.message?.is_echo !== true
}
};
});Welcome Message (First Contact):
Hi {{username}}! 👋
Thanks for reaching out! I'm here to help.
What can I assist you with today?
Quick options:
💰 Pricing & Packages
📅 Book a Demo
📦 Product Catalog
💬 General Questions
Just type your choice or send your question!Pricing Intent:
Great question! 💵 Our pricing starts at $XX/month with three tiers: ✨ Starter: $XX/mo - Perfect for beginners 🚀 Pro: $XX/mo - Most popular! 💎 Enterprise: Custom pricing Check out all the details here: [pricing-link] Want to book a quick 15-min call to discuss the best fit for you? 📞
Demo Request:
I'd love to show you what we can do! 🎯 Book a free 30-minute demo here: 👉 [calendly-link] Or if you prefer, I can have our team reach out directly. What's your preferred email/phone number?
Product Info:
Here's our complete product catalog! 📦 [Send media attachment: PDF or carousel] Key features: ✅ Feature 1 ✅ Feature 2 ✅ Feature 3 Which one interests you most? Or have specific questions?
High Intent (Immediate Notify Sales):
That's awesome! Let me connect you with our team right away. 🚀 I'm notifying them now - expect a message within 15 minutes! In the meantime, is there anything specific you'd like to know? You can also reach us directly: 📧 sales@yourbrand.com 📞 (123) 456-7890
Method: POST
URL: https://graph.facebook.com/v18.0/me/messages
Headers:
- Authorization: Bearer {{$credentials.facebookPageAccessToken}}
Body (JSON):
{
"recipient": {
"id": "{{$json["senderId"]}}"
},
"message": {
"text": "{{$json["responseText"]}}"
},
"messaging_type": "RESPONSE"
}
Note: messaging_type must be "RESPONSE" to reply to incoming messages.
You cannot use "MESSAGE_TAG" or "UPDATE" for marketing messages via API.{
"recipient": {"id": "{{$json["senderId"]}}"},
"message": {
"text": "What would you like to know more about?",
"quick_replies": [
{
"content_type": "text",
"title": "💰 Pricing",
"payload": "PRICING_INFO"
},
{
"content_type": "text",
"title": "📅 Book Demo",
"payload": "BOOK_DEMO"
},
{
"content_type": "text",
"title": "📦 Products",
"payload": "PRODUCT_CATALOG"
},
{
"content_type": "text",
"title": "💬 Other Question",
"payload": "GENERAL_QUESTION"
}
]
}
}💡 Quick Reply Benefits:
Table: Instagram Leads Fields to Save: - Instagram User ID (unique) - Username - First Message - Detected Intent - Lead Score (0-100) - Lead Quality (High/Medium/Low) - Conversation Started (datetime) - Last Response (datetime) - Status (New/Contacted/Qualified/Converted/Lost) - Assigned To (sales rep) - Notes (auto-generated summary) Conditional Logic: IF leadQuality = "high" → Assign to senior sales rep → Send immediate Slack notification → Schedule follow-up call within 2 hours IF leadQuality = "medium" → Add to email nurture sequence → Assign to junior sales rep → Follow up within 24 hours IF leadQuality = "low" → Add to general mailing list → Send educational content → Re-engage in 7 days
Use Instagram Graph API to fetch: - Profile picture - Follower count - Following count - Bio description - Is verified? - Is business account? High-value indicators: ✓ Verified account (influencer/business) ✓ High follower count (>10k) ✓ Business account with website link ✓ Bio contains relevant keywords Auto-upgrade lead quality if indicators present.
Channel: #hot-leads
Message:
🔥 HIGH INTENT INSTAGRAM LEAD 🔥
Username: @{{$json["username"]}}
Lead Score: {{$json["leadScore"]}}/100
Intent: {{$json["detectedIntent"]}}
First Message:
"{{$json["messageText"]}}"
Profile:
- Followers: {{$json["followerCount"]}}
- Business Account: {{$json["isBusinessAccount"]}}
- Verified: {{$json["isVerified"]}}
📱 Respond in Instagram DMs ASAP!
🔗 CRM Link: [airtable-record-link]
Assigned to: @{{$json["assignedSalesRep"]}}✅ Notification Best Practices:
Automatically collects Instagram analytics (followers, engagement, reach, impressions) and saves to Google Sheets or Notion. Creates daily/weekly reports, tracks growth trends, and identifies top-performing content.
Schedule Trigger (Daily at 8 AM)
↓
Fetch Account Insights (followers, reach, impressions)
↓
Fetch Recent Posts (last 24 hours)
↓
For Each Post: Get Engagement Metrics
- Likes
- Comments
- Saves
- Shares
- Reach
- Impressions
↓
Calculate Aggregated Metrics
- Total Engagement
- Engagement Rate
- Best Performing Post
- Worst Performing Post
↓
Save to Google Sheets (append row)
↓
Compare with Yesterday (growth/decline)
↓
Generate Summary Report → Send to Slack| Metric Category | Metrics | API Endpoint | Update Frequency |
|---|---|---|---|
| Account Insights | • Follower Count • Following Count • Profile Views • Website Clicks | /insights?metric=follower_count,profile_views | Daily |
| Content Performance | • Impressions • Reach • Engagement • Saved | /insights?metric=impressions,reach,engagement | Per Post |
| Audience | • Demographics • Location • Age Range • Gender Split | /insights?metric=audience_city,audience_gender_age | Weekly |
| Stories | • Story Views • Exits • Replies • Taps Forward/Back | /stories/insights | Daily |
Method: GET
URL: https://graph.facebook.com/v18.0/{{$json["InstagramAccountID"]}}/insights
Query Parameters:
- metric: follower_count,impressions,reach,profile_views,website_clicks
- period: day
- access_token: {{$credentials.facebookAccessToken}}
Response Format:
{
"data": [
{
"name": "follower_count",
"period": "day",
"values": [
{"value": 10523, "end_time": "2025-01-15T08:00:00+0000"}
]
},
{
"name": "impressions",
"period": "day",
"values": [
{"value": 45782, "end_time": "2025-01-15T08:00:00+0000"}
]
}
]
}Method: GET
URL: https://graph.facebook.com/v18.0/{{$json["InstagramAccountID"]}}/media
Query Parameters:
- fields: id,caption,media_type,media_url,permalink,timestamp,like_count,comments_count
- since: {{$now.minus({days: 1}).toSeconds()}}
- access_token: {{$credentials.facebookAccessToken}}Method: GET
URL: https://graph.facebook.com/v18.0/{{$json["postId"]}}/insights
Query Parameters:
- metric: impressions,reach,engagement,saved,video_views
- access_token: {{$credentials.facebookAccessToken}}
Calculate Engagement Rate:
engagement_rate = (likes + comments + saves + shares) / reach * 100const accountData = $input.first().json;
const postsData = $input.all().slice(1); // Assuming posts come after account data
// Account-level metrics
const followers = accountData.data.find(m => m.name === 'follower_count')?.values[0]?.value || 0;
const impressions = accountData.data.find(m => m.name === 'impressions')?.values[0]?.value || 0;
const reach = accountData.data.find(m => m.name === 'reach')?.values[0]?.value || 0;
const profileViews = accountData.data.find(m => m.name === 'profile_views')?.values[0]?.value || 0;
// Post-level aggregations
let totalLikes = 0;
let totalComments = 0;
let totalSaves = 0;
let totalPostReach = 0;
let totalPostImpressions = 0;
let bestPost = null;
let bestEngagementRate = 0;
postsData.forEach(item => {
const post = item.json;
const likes = post.like_count || 0;
const comments = post.comments_count || 0;
const saves = post.insights?.saved || 0;
const postReach = post.insights?.reach || 0;
const postImpressions = post.insights?.impressions || 0;
totalLikes += likes;
totalComments += comments;
totalSaves += saves;
totalPostReach += postReach;
totalPostImpressions += postImpressions;
// Calculate engagement rate for this post
const engagementRate = postReach > 0 ? ((likes + comments + saves) / postReach * 100) : 0;
if (engagementRate > bestEngagementRate) {
bestEngagementRate = engagementRate;
bestPost = {
caption: post.caption?.substring(0, 100),
url: post.permalink,
likes,
comments,
engagementRate: engagementRate.toFixed(2)
};
}
});
// Overall engagement rate
const avgEngagementRate = totalPostReach > 0
? ((totalLikes + totalComments + totalSaves) / totalPostReach * 100).toFixed(2)
: 0;
return [{
json: {
date: new Date().toISOString().split('T')[0],
followers,
impressions,
reach,
profileViews,
totalPosts: postsData.length,
totalLikes,
totalComments,
totalSaves,
totalPostReach,
totalPostImpressions,
avgEngagementRate,
bestPost
}
}];| Date | Followers | Impressions | Reach | Profile Views | Posts | Total Likes | Total Comments | Avg Eng. Rate % | Best Post URL |
|---|---|---|---|---|---|---|---|---|---|
| 2025-01-15 | 10,523 | 45,782 | 32,144 | 1,254 | 3 | 1,842 | 156 | 6.22% | link |
Operation: Append Row
Spreadsheet: Instagram Analytics
Sheet: Daily Data
Values to Append:
- {{$json["date"]}}
- {{$json["followers"]}}
- {{$json["impressions"]}}
- {{$json["reach"]}}
- {{$json["profileViews"]}}
- {{$json["totalPosts"]}}
- {{$json["totalLikes"]}}
- {{$json["totalComments"]}}
- {{$json["avgEngagementRate"]}}
- {{$json["bestPost"]["url"]}}
Options:
✓ Value Input Option: USER_ENTERED (formulas will work)
✓ Insert Data Option: INSERT_ROWS (don't overwrite)// Fetch yesterday's data from Google Sheets
// Assuming you have a "Read" node before this that gets last 2 rows
const today = $input.first().json;
const yesterday = $input.last().json;
const followerGrowth = today.followers - yesterday.followers;
const followerGrowthPercent = ((followerGrowth / yesterday.followers) * 100).toFixed(2);
const impressionGrowth = today.impressions - yesterday.impressions;
const impressionGrowthPercent = ((impressionGrowth / yesterday.impressions) * 100).toFixed(2);
const reachGrowth = today.reach - yesterday.reach;
const reachGrowthPercent = ((reachGrowth / yesterday.reach) * 100).toFixed(2);
const engagementChange = (today.avgEngagementRate - yesterday.avgEngagementRate).toFixed(2);
// Determine trend emoji
const getTrendEmoji = (growth) => {
if (growth > 5) return '🚀';
if (growth > 0) return '📈';
if (growth < -5) return '📉';
if (growth < 0) return '⚠️';
return '➡️';
};
return [{
json: {
...today,
growth: {
followers: followerGrowth,
followersPercent: followerGrowthPercent,
followersEmoji: getTrendEmoji(parseFloat(followerGrowthPercent)),
impressions: impressionGrowth,
impressionsPercent: impressionGrowthPercent,
impressionsEmoji: getTrendEmoji(parseFloat(impressionGrowthPercent)),
reach: reachGrowth,
reachPercent: reachGrowthPercent,
reachEmoji: getTrendEmoji(parseFloat(reachGrowthPercent)),
engagementChange,
engagementEmoji: getTrendEmoji(parseFloat(engagementChange))
}
}
}];Channel: #instagram-analytics
Message:
📊 Instagram Daily Report - {{$json["date"]}}
━━━━━━━━━━━━━━━━━━━━━━━━━━
👥 AUDIENCE
━━━━━━━━━━━━━━━━━━━━━━━━━━
Followers: {{$json["followers"]}} ({{$json["growth"]["followersEmoji"]}} {{$json["growth"]["followers"]}} / {{$json["growth"]["followersPercent"]}}%)
Profile Views: {{$json["profileViews"]}}
━━━━━━━━━━━━━━━━━━━━━━━━━━
📈 PERFORMANCE
━━━━━━━━━━━━━━━━━━━━━━━━━━
Impressions: {{$json["impressions"]}} ({{$json["growth"]["impressionsEmoji"]}} {{$json["growth"]["impressionsPercent"]}}%)
Reach: {{$json["reach"]}} ({{$json["growth"]["reachEmoji"]}} {{$json["growth"]["reachPercent"]}}%)
Avg Engagement Rate: {{$json["avgEngagementRate"]}}% ({{$json["growth"]["engagementEmoji"]}} {{$json["growth"]["engagementChange"]}}%)
━━━━━━━━━━━━━━━━━━━━━━━━━━
📝 CONTENT
━━━━━━━━━━━━━━━━━━━━━━━━━━
Posts Today: {{$json["totalPosts"]}}
Total Likes: {{$json["totalLikes"]}}
Total Comments: {{$json["totalComments"]}}
Total Saves: {{$json["totalSaves"]}}
━━━━━━━━━━━━━━━━━━━━━━━━━━
⭐ BEST PERFORMING POST
━━━━━━━━━━━━━━━━━━━━━━━━━━
Caption: {{$json["bestPost"]["caption"]}}...
Engagement Rate: {{$json["bestPost"]["engagementRate"]}}%
🔗 {{$json["bestPost"]["url"]}}
━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Full Data: [Google Sheets Link]💡 Advanced Reporting Options:
| Workflow | Total Nodes | Avg Execution Time | Frequency | Monthly Executions |
|---|---|---|---|---|
| Auto-Posting (Airtable) | 8 nodes | 12-18 seconds | Every 30 min | ~1,440 |
| Comment Monitoring | 12 nodes | 2-4 seconds | On event (webhook) | ~500-2000 |
| DM Automation | 15 nodes | 3-6 seconds | On event (webhook) | ~200-800 |
| Analytics Tracking | 9 nodes | 20-30 seconds | Daily (8 AM) | ~30 |
| TOTAL SYSTEM | 44 nodes | - | - | ~2,170-4,270 |
Starter Plan ($20/mo):
Self-Hosted (Free):
Use Webhooks Over Polling
Webhooks are instant and use fewer executions than scheduled checks
Batch Process When Possible
Process multiple posts/comments in one workflow run
Cache Frequently Used Data
Store hashtag lists, response templates in workflow static data
Optimize Image Processing
Use CDN links when possible instead of downloading/reuploading
Exceeding Rate Limits
Always implement rate limit detection and exponential backoff
Generic Auto-Responses
Users can tell. Make responses contextual and human-like
No Error Notifications
Always get alerted when workflows fail (posts not published, etc)
Over-Automation
Keep some human touch - don't automate everything
Test thoroughly before going live
Use a test Instagram account first
Review auto-responses weekly
Improve templates based on actual conversations
Keep workflows modular
One workflow per function makes debugging easier
Document your automations
Add notes to nodes explaining logic for future you
A/B Test Posting Times
Track performance by posting time, auto-optimize schedule
Hashtag Performance Analysis
Track which hashtags drive most engagement, auto-rotate
Competitor Monitoring
Track competitor posts, get alerts on their strategies
AI Content Suggestions
Use GPT to suggest captions based on top-performing posts
Using the official Instagram Graph API (as shown in this guide) is 100% compliant with Instagram's TOS. What's NOT allowed: third-party bots that scrape data, auto-follow/unfollow, or spam. We only use official APIs here.
Yes, Instagram Graph API only works with Business or Creator accounts connected to a Facebook Page. Personal accounts don't have API access. You can switch to a Business account for free in Instagram settings.
200 calls per hour per user, 4800 calls per hour per app. For publishing: 25 posts per day, 50 comments per day. These limits are generous for most use cases. The workflows in this guide stay well within limits.
No. Instagram API only allows responding to incoming DMs. You cannot send cold DMs via automation. However, you can encourage DMs through posts/stories and then auto-respond.
N8N Cloud Starter ($20/mo) easily handles 10 Instagram accounts with all workflows. Or self-host for free (unlimited usage). This is 10x cheaper than alternatives like ManyChat or Zapier.
Yes, the Instagram Graph API supports story publishing (limit: 100 stories/day). The workflow is similar to posts: upload media to Facebook CDN, then create story via API.
Implement error workflows that send urgent notifications to Slack/email. Also set up retry logic with exponential backoff. The workflows in this guide include comprehensive error handling.
Basic keyword matching (shown in this guide) is ~75-80% accurate. For better accuracy, use OpenAI's GPT-3.5 or GPT-4 for sentiment analysis (~95% accurate). It costs ~$0.001 per comment.
Get all 4 workflow JSONs, video tutorials, and our Instagram automation blueprint. Start automating today and save 35+ hours per week.
Save 35+ hours per week with automation