Real Case Study: Digital Marketing Agency
Before N8N Automation:
- ⢠Managing 10 Instagram accounts manually
- ⢠40 hours/week spent on posting and engagement
- ⢠Inconsistent posting schedules
- ⢠Missed DMs and comments
- ⢠Manual analytics reporting (5 hours/week)
- ⢠Team burnout and errors
After N8N Automation:
- ⢠All 10 accounts fully automated
- ⢠Only 5 hours/week for strategy
- ⢠Consistent posting (3x per day per account)
- ⢠Auto-responses to 80% of DMs
- ⢠Automated daily analytics reports
- ⢠3x engagement increase in 60 days
Result: 35 hours/week saved + $8,000/month revenue increase
ā ļø Critical: Instagram API Limits & Restrictions
You MUST understand these limitations before building automation:
- āBusiness/Creator Account Required: Personal accounts don't have API access
- āFacebook Page Connection: Instagram must be linked to a Facebook Business Page
- āAPI Rate Limits: 200 calls/hour per user, 4800 calls/hour per app
- āContent Publishing Limits: 25 posts per day, 50 comments per day
- āMessaging Limitations: Can only respond to incoming messages (no cold DMs via API)
- āStory Publishing: Limited to 100 stories per day
- āHashtag Limits: Maximum 30 hashtags per post
š” Pro Tips for Staying Within Limits
- āUse scheduling to spread posts evenly throughout the day
- āImplement exponential backoff for rate limit handling
- āCache data to minimize API calls
- āUse webhooks instead of polling when possible
- āMonitor your rate limit usage in N8N workflows
- āConsider third-party tools like Apify for scraping (use carefully)
šļøComplete Instagram Automation Architecture
System Overview
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 NotificationWorkflow Components Breakdown
| 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 |
š Workflow 1: Auto-Posting from Airtable/Google Sheets
What This Workflow Does
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.
Set Up Airtable Content Database
Create your content calendar structure
Required Airtable Fields:
| 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.
Configure N8N Schedule Trigger
Check Airtable every 30 minutes for posts ready to publish
Trigger Node Configuration:
Node Type:
Schedule TriggerInterval:
Every 30 minutesCron Expression (Alternative):
*/30 * * * *Runs at :00 and :30 of every hour
Fetch Scheduled Posts from Airtable
Query posts ready to publish
Airtable Node Setup:
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.
Process and Format Content
Prepare caption and download images
Function Node - Format Caption:
// 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
}
};
});HTTP Request Node - Download Image:
Method: GET
URL: {{$json["Image URL"]}}
Response Format: File
Binary Property: image_data
Options:
- Timeout: 30000ms
- Follow Redirects: YesPublish to Instagram via Graph API
Two-step publishing process (required by Instagram)
Step 5a: Create Media Container
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)Step 5b: Publish Media Container
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.
Update Airtable & Send Notification
Mark as published and notify team
Airtable Update Node:
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"]}}/Slack Notification Node:
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')}}Error Handling & Retry Logic
Handle API failures gracefully
Error Workflow Implementation:
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:
- ⢠Rate limit exceeded (Error code 32)
- ⢠Invalid image format or size
- ⢠Expired access token
- ⢠Image URL not accessible
- ⢠Caption too long (>2,200 chars)
Complete Workflow JSON (Import to N8N)
{
"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:
- 1. Copy the entire JSON above
- 2. In N8N, click "Import from File" or "Import from JSON"
- 3. Paste the JSON and click Import
- 4. Update credentials and configuration values
- 5. Test with a sample post from Airtable
Master Automation & Scale Your Growth
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
š¬Workflow 2: Comment Monitoring & Auto-Responses
What This Workflow Does
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.
Comment Monitoring Flow
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" DatabaseStep 1: Configure Instagram Webhook
Webhook Setup in Facebook Developer Portal:
1. 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)
N8N Webhook Node Configuration:
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 matchesStep 2: Analyze Comment
Function Node - Extract & Analyze:
const 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
}
};
});Step 3: Auto-Response Rules
IF Node - Route Comments:
| 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! |
HTTP Request - Post Reply:
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 dayStep 4: Advanced FAQ Matching with AI
OpenAI Node - Smart Response Generation:
Model: 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:
- ⢠Check keyword matches first (free, instant)
- ⢠Only call OpenAI for complex/unique questions
- ⢠Cache AI responses for similar future questions
- ⢠Cost: ~$0.002 per response with GPT-3.5-turbo
Step 5: Hide Spam Comments
HTTP Request - Hide Comment:
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:
- ⢠Start conservative - better to miss spam than hide legitimate comments
- ⢠Review hidden comments weekly to improve filters
- ⢠Never auto-hide comments from verified accounts
- ⢠Log all spam detections for pattern analysis
Step 6: Log Comments to Database
Airtable/Google Sheets - Log Entry:
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
Comment Automation Results
Workflow 3: DM Automation for Lead Generation
What This Workflow Does
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.
DM Automation Flow
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 ImmediatelyStep 1: Configure DM Webhook
Facebook Webhook Subscription:
Subscribe 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"
}
}
]
}
]
}N8N Webhook Node:
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
Step 2: Analyze Incoming Message
Function Node - Message Parser:
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
}
};
});Step 3: Send Automated Responses
Response Template Library:
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
HTTP Request - Send DM Reply:
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.Step 4: Add Quick Reply Buttons
Quick Reply Implementation:
{
"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:
- ⢠Users can tap instead of typing (better UX)
- ⢠You get structured data (easier to process)
- ⢠Higher response rate (30-40% vs text-only)
- ⢠Cleaner conversation flow
- ⢠Works on mobile perfectly
Step 5: Save Lead to CRM
Airtable/HubSpot - Create Lead:
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
Enrich Lead Data (Optional):
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.
Step 6: Notify Sales Team (High Intent Leads)
Slack Notification (Urgent):
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:
- ⢠Only notify for genuinely high-intent leads (avoid notification fatigue)
- ⢠Include all context needed to respond immediately
- ⢠Assign to specific person (not just general channel)
- ⢠Set SLA: respond within 15 minutes during business hours
- ⢠Track response times and conversion rates
DM Automation Performance
Workflow 4: Analytics Tracking to Google Sheets/Notion
What This Workflow Does
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.
Analytics Collection Flow
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 SlackKey Metrics to Track
| 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 |
Step 1: Fetch Account Insights
HTTP Request - Account Metrics:
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": "2026-01-15T08:00:00+0000"}
]
},
{
"name": "impressions",
"period": "day",
"values": [
{"value": 45782, "end_time": "2026-01-15T08:00:00+0000"}
]
}
]
}Step 2: Fetch Post-Level Analytics
Get Recent Posts:
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}}Get Post Insights (Loop Through Posts):
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 * 100Step 3: Process and Calculate Metrics
Function Node - Calculate Aggregates:
const 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
}
}];Step 4: Save to Google Sheets
Google Sheets Structure:
| Date | Followers | Impressions | Reach | Profile Views | Posts | Total Likes | Total Comments | Avg Eng. Rate % | Best Post URL |
|---|---|---|---|---|---|---|---|---|---|
| 2026-01-15 | 10,523 | 45,782 | 32,144 | 1,254 | 3 | 1,842 | 156 | 6.22% | link |
Google Sheets Node Configuration:
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)Step 5: Calculate Growth Trends
Function Node - Compare with Yesterday:
// 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))
}
}
}];Step 6: Generate & Send Daily Report
Slack Report Message:
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:
- ⢠Weekly summary every Monday (7-day trends)
- ⢠Monthly report with charts (use Google Charts API)
- ⢠Automated insights: "Your engagement is 25% higher on Wednesdays"
- ⢠Competitor comparison (if tracking multiple accounts)
- ⢠Goal tracking: "You're 85% to your 15K follower goal"
Complete System: Node Breakdown & Execution Times
| 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 |
š” Execution Cost Estimate (N8N Cloud)
Starter Plan ($20/mo):
- ⢠50K executions included
- ⢠This system uses: ~2,170-4,270/month
- ⢠Easily fits within limit!
Self-Hosted (Free):
- ⢠Unlimited executions
- ⢠Only pay for server (~$10-20/mo)
- ⢠Best value for high volume!
Optimization Tips for Instagram Automation
š” Performance Optimization
- ā
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
ā ļø Common Pitfalls to Avoid
- ā
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
ā Best Practices
- ā¢
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
ā” Advanced Techniques
- ā
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
Frequently Asked Questions
Is automating Instagram against their Terms of Service?
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.
Do I need a Business or Creator account?
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.
What's the Instagram API rate limit?
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.
Can I auto-DM people who aren't following me?
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.
How much does N8N cost for Instagram automation?
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.
Can I schedule Instagram Stories automatically?
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.
What if my workflow fails while I'm sleeping?
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.
How accurate is the AI sentiment analysis?
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.
Want to master Instagram Ignited? Get it + 3 more complete courses
Complete Creator Academy - All Courses
Master Instagram growth, AI influencers, n8n automation, and digital products for just $99/month. Cancel anytime.
All 4 premium courses (Instagram, AI Influencers, Automation, Digital Products)
100+ hours of training content
Exclusive templates and workflows
Weekly live Q&A sessions
Private community access
New courses and updates included
Cancel anytime - no long-term commitment
⨠Includes: Instagram Ignited ⢠AI Influencers Academy ⢠AI Automations ⢠Digital Products Empire