mirror of
https://github.com/khoaliber/n8nworkflows.xyz.git
synced 2026-04-19 17:14:37 +00:00
creation
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
Analyze Meta ads daily with Google Gemini and Google Sheets
|
||||
|
||||
https://n8nworkflows.xyz/workflows/analyze-meta-ads-daily-with-google-gemini-and-google-sheets-12416
|
||||
|
||||
|
||||
# Analyze Meta ads daily with Google Gemini and Google Sheets
|
||||
|
||||
## 1. Workflow Overview
|
||||
|
||||
**Purpose:** Run once per day to pull **yesterday’s Meta (Facebook) Ads performance**, store **raw campaign + ad-level metrics** in **Google Sheets**, then send a **structured dataset** to **Google Gemini** to generate **actionable insights** (winners/losers + recommendations) and store those insights back into Google Sheets. It also includes **error handling** that emails a configured address when the workflow fails.
|
||||
|
||||
**Primary use cases:**
|
||||
- Daily performance monitoring for a Meta ad account
|
||||
- Producing consistent, client-ready qualitative insights from quantitative Meta metrics
|
||||
- Building a historical dataset in Google Sheets for reporting dashboards
|
||||
|
||||
### 1.1 Configuration & Scheduling
|
||||
Defines when the workflow runs and where account/report parameters live.
|
||||
|
||||
### 1.2 Campaign-level Data Collection → Clean → Store
|
||||
Pull campaign insights (yesterday), remove zero-spend campaigns, append rows to a “Campaigns” sheet.
|
||||
|
||||
### 1.3 Ad-level Data Collection → Normalize → Store
|
||||
Pull ad-level insights (yesterday), split records, append to an “Ads” sheet.
|
||||
|
||||
### 1.4 Data Structuring for AI
|
||||
Aggregate raw ad rows into a hierarchical structure (campaign → adset → ads) so the LLM can reason across entities.
|
||||
|
||||
### 1.5 AI Analysis (Gemini) with Structured Output Parsing
|
||||
Send structured data to Gemini with strict JSON requirements, parse result into machine-usable objects.
|
||||
|
||||
### 1.6 Insight Storage
|
||||
Split AI output into per-row items and append to “AI_Insights” sheet.
|
||||
|
||||
### 1.7 Error Handling
|
||||
On any failure, send an email containing message/stack/last node executed.
|
||||
|
||||
---
|
||||
|
||||
## 2. Block-by-Block Analysis
|
||||
|
||||
### Block 1 — Configuration & Scheduling
|
||||
**Overview:** Triggers daily execution and sets shared configuration values (ad account + notification email).
|
||||
**Nodes involved:** `Schedule - Once per day`, `Set Configuration`, `Template Guide`, `Sticky Note1`
|
||||
|
||||
#### Node: Schedule - Once per day
|
||||
- **Type / role:** Schedule Trigger; starts workflow automatically.
|
||||
- **Configuration:** Runs daily at **06:00** (instance timezone).
|
||||
- **Connections:**
|
||||
- Outputs to `Set Configuration`
|
||||
- Outputs directly to `Get Ad Insights - Ad Level` (parallel branch)
|
||||
- **Edge cases / failures:**
|
||||
- Timezone mismatch can cause “yesterday” to not align with business day.
|
||||
- If n8n is down at trigger time, the run is missed unless you add retry/backfill logic.
|
||||
- **Version notes:** `typeVersion 1.2`
|
||||
|
||||
#### Node: Set Configuration
|
||||
- **Type / role:** Set node; centralizes constants.
|
||||
- **Configuration choices:**
|
||||
- Sets:
|
||||
- `Ad Account ID` (string): `YOUR_ACT_ID_HERE`
|
||||
- `Email` (string): `user@example.com`
|
||||
- **Key variables:** Exposed via expressions like:
|
||||
- `$('Set Configuration').first().json['Ad Account ID']`
|
||||
- **Connections:**
|
||||
- Main output → `Get Ad Insights - Campaign Level`
|
||||
- **Edge cases / failures:**
|
||||
- If left as placeholder, Meta API nodes will query `act_YOUR_ACT_ID_HERE` and fail.
|
||||
- **Version notes:** `typeVersion 3.4`
|
||||
|
||||
#### Node: Template Guide (Sticky note)
|
||||
- **Type / role:** Sticky note documentation.
|
||||
- **Notable embedded resources:**
|
||||
- Facebook Graph API Guide: https://docs.google.com/document/d/1ydWNom0TUVh5SkU9IymlpnM_NXbTqHeL_YetDLn-9m0/edit?usp=sharing
|
||||
- Google Sheets template: https://docs.google.com/spreadsheets/d/11M6Co13t9t2P7NtFaTm9VXHnzUHXO_RGYf-FRnHkS28/edit?usp=sharing
|
||||
- **Operational notes in content:**
|
||||
- Mentions date-range query format: `time_range[since]=YYYY-MM-DD&time_range[until]=YYYY-MM-DD`
|
||||
|
||||
---
|
||||
|
||||
### Block 2 — Campaign-level Data Collection → Filter → Store
|
||||
**Overview:** Pulls yesterday’s campaign insights from Meta, splits into items, filters out zero-spend campaigns, then appends to Google Sheets.
|
||||
**Nodes involved:** `Get Ad Insights - Campaign Level`, `Split Campaign Data`, `Filter Zero Spend`, `Save Campaign Data`, `Sticky Note2`
|
||||
|
||||
#### Node: Get Ad Insights - Campaign Level
|
||||
- **Type / role:** Facebook Graph API; fetch campaign-level insights.
|
||||
- **Configuration choices:**
|
||||
- **Graph API version:** `v24.0`
|
||||
- **Node (object):** `act_{{ $('Set Configuration').first().json['Ad Account ID'] }}`
|
||||
- **Edge:** `insights?...&level=campaign&date_preset=yesterday`
|
||||
- Requests fields: `campaign_id,campaign_name,spend,impressions,clicks,results,marketing_messages_delivered,actions,reach,ctr,cost_per_result`
|
||||
- **Connections:** Main output → `Split Campaign Data`
|
||||
- **Credentials:** `facebookGraphApi`
|
||||
- **Edge cases / failures:**
|
||||
- Auth/permissions: missing `ads_read` / access token expired.
|
||||
- API rate limits.
|
||||
- `results`/`cost_per_result` shape varies by objective; may be empty arrays.
|
||||
- If no spend yesterday, response `data` may be empty (downstream nodes must handle 0 items).
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Split Campaign Data
|
||||
- **Type / role:** Split Out; converts `data[]` array into one item per campaign.
|
||||
- **Configuration:** `fieldToSplitOut = "data"`
|
||||
- **Connections:** Main output → `Filter Zero Spend`
|
||||
- **Edge cases / failures:**
|
||||
- If Meta response has no `data` field or it’s not an array, this node errors.
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Filter Zero Spend
|
||||
- **Type / role:** Filter; removes campaigns where spend == "0".
|
||||
- **Configuration choices:**
|
||||
- Condition: `$json.spend` **string** `notEquals` `"0"`
|
||||
- Strict type validation enabled in node options.
|
||||
- **Connections:** “true” output → `Save Campaign Data`
|
||||
- **Edge cases / failures:**
|
||||
- Meta often returns spend as `"0.00"` rather than `"0"`. Those would **pass** this filter unintentionally and be stored.
|
||||
- If spend is numeric rather than string, strict validation can behave unexpectedly.
|
||||
- **Version notes:** `typeVersion 2.2`
|
||||
|
||||
#### Node: Save Campaign Data
|
||||
- **Type / role:** Google Sheets; append campaign metrics to a sheet.
|
||||
- **Configuration choices:**
|
||||
- Operation: **Append**
|
||||
- Spreadsheet: `YOUR_SPREADSHEET_ID_HERE`
|
||||
- Sheet name: `Campaigns`
|
||||
- Column mapping (examples):
|
||||
- `Date` = `$json.date_start`
|
||||
- `Campaign ID` = `$json.campaign_id`
|
||||
- `Spend` = `$json.spend`
|
||||
- `Conversations Started` = `$json.results[0].values[0].value`
|
||||
- `CPL` = `$json.cost_per_result[0].values[0].value`
|
||||
- **Credentials:** `googleSheetsOAuth2Api`
|
||||
- **Edge cases / failures:**
|
||||
- If `results` or `cost_per_result` arrays are empty, expressions like `[0].values[0].value` will throw.
|
||||
- Spreadsheet/sheet not found, insufficient permissions, or wrong Spreadsheet ID.
|
||||
- **Version notes:** `typeVersion 4.7`
|
||||
|
||||
---
|
||||
|
||||
### Block 3 — Ad-level Data Collection → Store
|
||||
**Overview:** Pulls yesterday’s ad-level insights from Meta, splits rows, stores raw data in Sheets, and simultaneously forwards data for aggregation.
|
||||
**Nodes involved:** `Get Ad Insights - Ad Level`, `Split Ad Data`, `Save Ad Data`, `Sticky Note4`
|
||||
|
||||
#### Node: Get Ad Insights - Ad Level
|
||||
- **Type / role:** Facebook Graph API; fetch ad-level insights.
|
||||
- **Configuration choices:**
|
||||
- Graph API version: `v24.0`
|
||||
- Node: `act_{{ $('Set Configuration').first().json['Ad Account ID'] }}`
|
||||
- Edge: `insights?fields=ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,spend,reach,impressions,clicks,results,ctr,cpc,cpm&level=ad&date_preset=yesterday`
|
||||
- **Connections:** Main output → `Split Ad Data`
|
||||
- **Credentials:** `facebookGraphApi`
|
||||
- **Edge cases / failures:**
|
||||
- Same auth/rate-limit risks as campaign-level.
|
||||
- Some ads may have missing metrics or zero impressions; later AI prompt says to ignore zero-impression ads, but code does not filter them.
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Split Ad Data
|
||||
- **Type / role:** Split Out; one item per ad insight row.
|
||||
- **Configuration:** `fieldToSplitOut = "data"`
|
||||
- **Connections:**
|
||||
- → `Save Ad Data`
|
||||
- → `Structure Data Hierarchy`
|
||||
- **Edge cases / failures:**
|
||||
- If `data` missing/non-array, node errors.
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Save Ad Data
|
||||
- **Type / role:** Google Sheets; append raw ad metrics to `Ads`.
|
||||
- **Configuration choices:**
|
||||
- Operation: **Append**
|
||||
- Spreadsheet: `YOUR_SPREADSHEET_ID_HERE`
|
||||
- Sheet: `Ads`
|
||||
- Mapping highlights:
|
||||
- `date` = `$json.date_stop`
|
||||
- `results` = `$json.results[0].values[0].value`
|
||||
- plus spend/reach/impressions/clicks/ctr/cpc/cpm and ids/names
|
||||
- **Credentials:** `googleSheetsOAuth2Api`
|
||||
- **Edge cases / failures:**
|
||||
- `results[0]...` may not exist depending on account/objective → expression failure.
|
||||
- Wrong sheet column names/order can cause missing data or append errors.
|
||||
- **Version notes:** `typeVersion 4.7`
|
||||
|
||||
---
|
||||
|
||||
### Block 4 — Data Structuring for AI
|
||||
**Overview:** Converts flat ad-level rows into a nested hierarchy grouped by campaign and adset, with per-ad metrics included.
|
||||
**Nodes involved:** `Structure Data Hierarchy`
|
||||
|
||||
#### Node: Structure Data Hierarchy
|
||||
- **Type / role:** Code node; aggregation/grouping.
|
||||
- **Configuration choices (interpreted):**
|
||||
- Reads **all items** from input (`$input.all()`), each representing one ad insight row.
|
||||
- Groups into:
|
||||
- `campaigns[campaign_id] = { campaign_id, campaign_name, adsets: { ... } }`
|
||||
- `adsets[adset_id] = { adset_id, adset_name, ads: [] }`
|
||||
- Pushes `ads[]` entries containing:
|
||||
- `ad_id`, `ad_name`
|
||||
- `metrics`: spend, reach, impressions, clicks, ctr, cpc, cpm
|
||||
- `results`
|
||||
- `date`: start/stop
|
||||
- Outputs a single item: `{ campaigns: [...] }`
|
||||
- **Connections:** Main output → `Write Insights`
|
||||
- **Edge cases / failures:**
|
||||
- If the Meta API returns numbers as strings, comparisons/logic are deferred to Gemini; no numeric normalization is done here.
|
||||
- Missing `campaign_id` or `adset_id` would create `undefined` keys and messy grouping.
|
||||
- **Version notes:** `typeVersion 2`
|
||||
|
||||
---
|
||||
|
||||
### Block 5 — AI Analysis & Structured Output
|
||||
**Overview:** Sends hierarchical JSON to Gemini with strict output constraints and parses the response into structured items for downstream storage.
|
||||
**Nodes involved:** `Google Gemini Chat Model`, `Structured Output Parser`, `Write Insights`, `Sticky Note5`
|
||||
|
||||
#### Node: Google Gemini Chat Model
|
||||
- **Type / role:** LangChain chat model wrapper for Google Gemini; provides LLM to the chain node.
|
||||
- **Configuration choices:** Defaults (no special options set).
|
||||
- **Connections:** AI language model output → `Write Insights` (as the model provider)
|
||||
- **Credentials:** Not shown in JSON, but required in n8n (Gemini/Google AI).
|
||||
- **Edge cases / failures:**
|
||||
- Model credential misconfiguration, quota exceeded, or safety blocks.
|
||||
- Latency/timeouts with large payloads (campaign/adset/ad volume).
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Structured Output Parser
|
||||
- **Type / role:** LangChain structured output parser; enforces JSON schema.
|
||||
- **Configuration choices:**
|
||||
- Provides an example schema expecting:
|
||||
- array of campaigns, each with `campaign_name`, and `adsets[]`
|
||||
- each adset contains `adset_name`, `best_performing_ad`, `worst_performing_ad`, `suggestion_for_agency`, `suggestion_for_client`
|
||||
- **Connections:** AI output parser → `Write Insights`
|
||||
- **Edge cases / failures:**
|
||||
- If the model returns invalid JSON or deviates from schema, parsing fails and triggers error flow.
|
||||
- **Version notes:** `typeVersion 1.3`
|
||||
|
||||
#### Node: Write Insights
|
||||
- **Type / role:** LangChain “Chain LLM”; orchestrates prompt + model + parser.
|
||||
- **Configuration choices:**
|
||||
- **Prompt text** embeds `{{ JSON.stringify($json.campaigns, null, 2) }}`.
|
||||
- Instructs: “Return only valid JSON following this schema…”
|
||||
- **System/message content** defines “MetaInsightsGPT” behavior and evaluation rules (CTR/conversions prioritized, ignore zero-impression ads, quantify insights, etc.).
|
||||
- Has output parser enabled (`hasOutputParser: true`), connected to `Structured Output Parser`.
|
||||
- **Connections:** Main output → `Split AI Insights`
|
||||
- **Critical schema mismatch to note:**
|
||||
- The **prompt’s required JSON schema** asks for `adset_suggestion` and `campaign_suggestion`.
|
||||
- The **parser schema** expects `suggestion_for_agency` and `suggestion_for_client`.
|
||||
- The **Save AI Insights** node expects `suggestion_for_agency` and `suggestion_for_client` but the prompt doesn’t require them.
|
||||
- This mismatch is a common cause of parsing or missing-field issues.
|
||||
- **Version notes:** `typeVersion 1.7`
|
||||
|
||||
---
|
||||
|
||||
### Block 6 — Insights Storage
|
||||
**Overview:** Splits the AI output array into separate items and appends rows into Google Sheets.
|
||||
**Nodes involved:** `Split AI Insights`, `Save AI Insights`, `Sticky Note6`
|
||||
|
||||
#### Node: Split AI Insights
|
||||
- **Type / role:** Split Out; turns AI output array into one item per campaign (or per element).
|
||||
- **Configuration:** `fieldToSplitOut = "output"`
|
||||
- **Connections:** Main output → `Save AI Insights`
|
||||
- **Edge cases / failures:**
|
||||
- Requires that `Write Insights` produces an `output` field containing an array. If the chain node outputs a different property name, this fails.
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Save AI Insights
|
||||
- **Type / role:** Google Sheets; append insights rows.
|
||||
- **Configuration choices:**
|
||||
- Operation: **Append**
|
||||
- Spreadsheet: `YOUR_SPREADSHEET_ID_HERE`
|
||||
- Sheet: `AI_Insights`
|
||||
- Column mapping highlights:
|
||||
- `Date` = `$now.minus({ days: 1 }).toFormat('yyyy-MM-dd')`
|
||||
- `Campaign` = `$json.campaign_name`
|
||||
- Adset fields use index `[0]`: `$json.adsets[0].adset_name`, etc.
|
||||
- `Agency Suggestion` = `$json.adsets[0].suggestion_for_agency`
|
||||
- `Client Suggestion` = `$json.adsets[0].suggestion_for_client`
|
||||
- **Credentials:** `googleSheetsOAuth2Api`
|
||||
- **Edge cases / failures:**
|
||||
- Only the **first adset** (`adsets[0]`) is saved per campaign item. If the AI returns multiple adsets, they are ignored.
|
||||
- If `adsets` is empty, `[0]` access fails.
|
||||
- If the model returned `adset_suggestion` instead of `suggestion_for_*`, fields will be blank or expression errors.
|
||||
- **Version notes:** `typeVersion 4.7`
|
||||
|
||||
---
|
||||
|
||||
### Block 7 — Error Handling
|
||||
**Overview:** If any node fails, the workflow sends an email containing error details.
|
||||
**Nodes involved:** `Error Trigger`, `Get Config`, `Send Error Email`, `Sticky Note7`
|
||||
|
||||
#### Node: Error Trigger
|
||||
- **Type / role:** Error Trigger; starts this branch on workflow failure.
|
||||
- **Configuration:** Default.
|
||||
- **Connections:** Main output → `Get Config`
|
||||
- **Edge cases / failures:**
|
||||
- If error branch nodes also fail (e.g., Gmail auth), you may lose visibility.
|
||||
- **Version notes:** `typeVersion 1`
|
||||
|
||||
#### Node: Get Config
|
||||
- **Type / role:** Set node; duplicates config for the error branch.
|
||||
- **Configuration choices:** Same fields as `Set Configuration`:
|
||||
- `Ad Account ID`, `Email`
|
||||
- **Connections:** Main output → `Send Error Email`
|
||||
- **Edge cases / failures:**
|
||||
- If email is placeholder, notifications won’t reach you.
|
||||
- **Version notes:** `typeVersion 3.4`
|
||||
|
||||
#### Node: Send Error Email
|
||||
- **Type / role:** Gmail; sends error notification.
|
||||
- **Configuration choices:**
|
||||
- To: `$('Get Config').first().json.Email`
|
||||
- Subject includes Ad Account ID.
|
||||
- HTML body includes:
|
||||
- `execution.error.message`
|
||||
- `execution.error.stack`
|
||||
- `execution.lastNodeExecuted`
|
||||
- Attribution disabled.
|
||||
- **Credentials:** `gmailOAuth2`
|
||||
- **Edge cases / failures:**
|
||||
- Gmail OAuth expired/revoked.
|
||||
- Large stack traces could exceed email size limits (rare).
|
||||
- **Version notes:** `typeVersion 2.1`
|
||||
|
||||
---
|
||||
|
||||
## 3. Summary Table
|
||||
|
||||
| Node Name | Node Type | Functional Role | Input Node(s) | Output Node(s) | Sticky Note |
|
||||
|---|---|---|---|---|---|
|
||||
| Template Guide | stickyNote | Embedded setup notes and links | — | — | ## Analyze Meta ads with Gemini and Google Sheets… (includes Graph API guide + Sheets template links) |
|
||||
| Schedule - Once per day | scheduleTrigger | Daily trigger at 06:00 | — | Set Configuration; Get Ad Insights - Ad Level | ## Configuration & schedule • Define run frequency • Set account and reporting parameters |
|
||||
| Set Configuration | set | Stores Ad Account ID + Email for main flow | Schedule - Once per day | Get Ad Insights - Campaign Level | ## Configuration & schedule • Define run frequency • Set account and reporting parameters |
|
||||
| Get Ad Insights - Campaign Level | facebookGraphApi | Fetch yesterday campaign-level insights | Set Configuration | Split Campaign Data | ## Campaign-level data collection • Fetch campaign performance data • Remove zero-spend campaigns • Store clean campaign metrics |
|
||||
| Split Campaign Data | splitOut | Split Meta response `data[]` into items | Get Ad Insights - Campaign Level | Filter Zero Spend | ## Campaign-level data collection • Fetch campaign performance data • Remove zero-spend campaigns • Store clean campaign metrics |
|
||||
| Filter Zero Spend | filter | Remove campaigns with spend == "0" | Split Campaign Data | Save Campaign Data | ## Campaign-level data collection • Fetch campaign performance data • Remove zero-spend campaigns • Store clean campaign metrics |
|
||||
| Save Campaign Data | googleSheets | Append campaign metrics to sheet `Campaigns` | Filter Zero Spend | — | ## Campaign-level data collection • Fetch campaign performance data • Remove zero-spend campaigns • Store clean campaign metrics |
|
||||
| Get Ad Insights - Ad Level | facebookGraphApi | Fetch yesterday ad-level insights | Schedule - Once per day | Split Ad Data | ## Ad-level data collection • Fetch ad-level performance data • Normalize and structure records • Persist raw ad metrics |
|
||||
| Split Ad Data | splitOut | Split Meta response `data[]` into ad items | Get Ad Insights - Ad Level | Save Ad Data; Structure Data Hierarchy | ## Ad-level data collection • Fetch ad-level performance data • Normalize and structure records • Persist raw ad metrics |
|
||||
| Save Ad Data | googleSheets | Append ad metrics to sheet `Ads` | Split Ad Data | — | ## Ad-level data collection • Fetch ad-level performance data • Normalize and structure records • Persist raw ad metrics |
|
||||
| Structure Data Hierarchy | code | Group ads into campaign→adset→ads JSON | Split Ad Data | Write Insights | ## AI analysis & insights • Analyze performance with Gemini • Detect winners and underperformers • Generate actionable recommendations |
|
||||
| Google Gemini Chat Model | lmChatGoogleGemini | LLM provider for analysis | — | Write Insights (AI model input) | ## AI analysis & insights • Analyze performance with Gemini • Detect winners and underperformers • Generate actionable recommendations |
|
||||
| Structured Output Parser | outputParserStructured | Enforce JSON schema parsing | — | Write Insights (AI parser input) | ## AI analysis & insights • Analyze performance with Gemini • Detect winners and underperformers • Generate actionable recommendations |
|
||||
| Write Insights | chainLlm | Prompt Gemini + parse into structured result | Structure Data Hierarchy; Google Gemini Chat Model; Structured Output Parser | Split AI Insights | ## AI analysis & insights • Analyze performance with Gemini • Detect winners and underperformers • Generate actionable recommendations |
|
||||
| Split AI Insights | splitOut | Split AI `output[]` into items | Write Insights | Save AI Insights | ## Insights storage • Split structured AI output • Save insights for reporting |
|
||||
| Save AI Insights | googleSheets | Append insights to `AI_Insights` | Split AI Insights | — | ## Insights storage • Split structured AI output • Save insights for reporting |
|
||||
| Error Trigger | errorTrigger | Starts on any workflow error | — | Get Config | ## Error handling • Catch workflow failures • Send notification email |
|
||||
| Get Config | set | Stores Ad Account ID + Email for error flow | Error Trigger | Send Error Email | ## Error handling • Catch workflow failures • Send notification email |
|
||||
| Send Error Email | gmail | Email error details | Get Config | — | ## Error handling • Catch workflow failures • Send notification email |
|
||||
| Sticky Note1 | stickyNote | Documentation block label | — | — | ## Configuration & schedule • Define run frequency • Set account and reporting parameters |
|
||||
| Sticky Note2 | stickyNote | Documentation block label | — | — | ## Campaign-level data collection • Fetch campaign performance data • Remove zero-spend campaigns • Store clean campaign metrics |
|
||||
| Sticky Note4 | stickyNote | Documentation block label | — | — | ## Ad-level data collection • Fetch ad-level performance data • Normalize and structure records • Persist raw ad metrics |
|
||||
| Sticky Note5 | stickyNote | Documentation block label | — | — | ## AI analysis & insights • Analyze performance with Gemini • Detect winners and underperformers • Generate actionable recommendations |
|
||||
| Sticky Note6 | stickyNote | Documentation block label | — | — | ## Insights storage • Split structured AI output • Save insights for reporting |
|
||||
| Sticky Note7 | stickyNote | Documentation block label | — | — | ## Error handling • Catch workflow failures • Send notification email |
|
||||
|
||||
---
|
||||
|
||||
## 4. Reproducing the Workflow from Scratch
|
||||
|
||||
1. **Create a new workflow** in n8n named: *Analyze Meta ads daily with Google Gemini and Google Sheets*.
|
||||
|
||||
2. **Add trigger**
|
||||
1. Add node: **Schedule Trigger** named `Schedule - Once per day`.
|
||||
2. Set rule: **Every day at 06:00** (adjust timezone in n8n settings if needed).
|
||||
|
||||
3. **Add configuration node**
|
||||
1. Add node: **Set** named `Set Configuration`.
|
||||
2. Add fields:
|
||||
- `Ad Account ID` (string): your Meta Ad Account numeric ID (without `act_`)
|
||||
- `Email` (string): notification email address
|
||||
3. Connect: `Schedule - Once per day` → `Set Configuration`.
|
||||
|
||||
4. **Campaign-level branch (from Set Configuration)**
|
||||
1. Add node: **Facebook Graph API** named `Get Ad Insights - Campaign Level`.
|
||||
- Graph API version: **v24.0**
|
||||
- Node: `act_{{$('Set Configuration').first().json['Ad Account ID']}}`
|
||||
- Edge:
|
||||
- `insights?fields=campaign_id,campaign_name,spend,impressions,clicks,results,marketing_messages_delivered,actions,reach,ctr,cost_per_result&level=campaign&date_preset=yesterday`
|
||||
- Set credentials: **Facebook Graph API** (token with ads read permissions).
|
||||
2. Add node: **Split Out** named `Split Campaign Data`
|
||||
- Field to split out: `data`
|
||||
3. Add node: **Filter** named `Filter Zero Spend`
|
||||
- Condition: `$json.spend` **not equals** `"0"`
|
||||
- (Recommended improvement: treat `"0.00"` as zero too.)
|
||||
4. Add node: **Google Sheets** named `Save Campaign Data`
|
||||
- Operation: **Append**
|
||||
- Document ID: your spreadsheet ID
|
||||
- Sheet name: `Campaigns`
|
||||
- Map columns to campaign fields (Date, Campaign ID/Name, Reach, Impressions, Spend, Clicks, CTR, etc.)
|
||||
- Credentials: **Google Sheets OAuth2**
|
||||
5. Connect in order:
|
||||
- `Set Configuration` → `Get Ad Insights - Campaign Level` → `Split Campaign Data` → `Filter Zero Spend` → `Save Campaign Data`
|
||||
|
||||
5. **Ad-level branch (from Schedule Trigger)**
|
||||
1. Add node: **Facebook Graph API** named `Get Ad Insights - Ad Level`.
|
||||
- Graph API version: **v24.0**
|
||||
- Node: `act_{{$('Set Configuration').first().json['Ad Account ID']}}`
|
||||
- Note: in this workflow JSON, this node is triggered directly from the schedule; to keep that design, either:
|
||||
- also connect `Schedule - Once per day` → `Set Configuration` and then `Set Configuration` → `Get Ad Insights - Ad Level`, **or**
|
||||
- keep schedule → ad node but ensure `Set Configuration` runs before it by using a single branch. (n8n parallel branches don’t guarantee ordering.)
|
||||
- Edge:
|
||||
- `insights?fields=ad_id,ad_name,adset_id,adset_name,campaign_id,campaign_name,spend,reach,impressions,clicks,results,ctr,cpc,cpm&level=ad&date_preset=yesterday`
|
||||
2. Add node: **Split Out** named `Split Ad Data`
|
||||
- Field: `data`
|
||||
3. Add node: **Google Sheets** named `Save Ad Data`
|
||||
- Operation: **Append**
|
||||
- Document ID: your spreadsheet ID
|
||||
- Sheet: `Ads`
|
||||
- Map columns (campaign/adset/ad identifiers + metrics)
|
||||
4. Add node: **Code** named `Structure Data Hierarchy`
|
||||
- Paste logic that groups items by `campaign_id` then `adset_id` and outputs `{ campaigns: [...] }` (as in the workflow).
|
||||
5. Connect:
|
||||
- `Get Ad Insights - Ad Level` → `Split Ad Data`
|
||||
- `Split Ad Data` → `Save Ad Data`
|
||||
- `Split Ad Data` → `Structure Data Hierarchy`
|
||||
|
||||
6. **AI block (Gemini + parsing)**
|
||||
1. Add node: **Google Gemini Chat Model** named `Google Gemini Chat Model`
|
||||
- Select Gemini credentials (Google AI / Gemini API key depending on your n8n setup).
|
||||
2. Add node: **Structured Output Parser** named `Structured Output Parser`
|
||||
- Provide schema example (campaign → adsets with best/worst + suggestions).
|
||||
3. Add node: **Chain LLM** named `Write Insights`
|
||||
- Set prompt to include: `{{ JSON.stringify($json.campaigns, null, 2) }}`
|
||||
- Add system/instruction message to enforce:
|
||||
- identify best/worst ad per adset
|
||||
- quantify CTR/CPC/etc.
|
||||
- return valid JSON only
|
||||
- Enable structured output parsing and connect parser + model (see next step).
|
||||
4. Connect:
|
||||
- `Structure Data Hierarchy` → `Write Insights`
|
||||
- `Google Gemini Chat Model` → `Write Insights` (AI language model connection)
|
||||
- `Structured Output Parser` → `Write Insights` (AI output parser connection)
|
||||
|
||||
7. **Insights split + save**
|
||||
1. Add node: **Split Out** named `Split AI Insights`
|
||||
- Field to split: `output`
|
||||
2. Add node: **Google Sheets** named `Save AI Insights`
|
||||
- Operation: **Append**
|
||||
- Document ID: your spreadsheet ID
|
||||
- Sheet: `AI_Insights`
|
||||
- Map columns:
|
||||
- `Date` = `$now.minus({ days: 1 }).toFormat('yyyy-MM-dd')`
|
||||
- `Campaign` = `$json.campaign_name`
|
||||
- Adset-related fields (ensure your AI schema matches what you save)
|
||||
3. Connect:
|
||||
- `Write Insights` → `Split AI Insights` → `Save AI Insights`
|
||||
|
||||
8. **Error handling branch**
|
||||
1. Add node: **Error Trigger** named `Error Trigger`.
|
||||
2. Add node: **Set** named `Get Config` (duplicate configuration fields, or reference a global/static config).
|
||||
3. Add node: **Gmail** named `Send Error Email`
|
||||
- To: `={{ $('Get Config').first().json.Email }}`
|
||||
- Subject: include ad account id
|
||||
- Body: include error message/stack/last node executed from the error trigger payload.
|
||||
- Credentials: **Gmail OAuth2**
|
||||
4. Connect:
|
||||
- `Error Trigger` → `Get Config` → `Send Error Email`
|
||||
|
||||
9. **Google Sheets preparation**
|
||||
- Create a spreadsheet with sheets named exactly:
|
||||
- `Campaigns`, `Ads`, `AI_Insights`
|
||||
- Ensure columns match the names used in the Google Sheets nodes.
|
||||
|
||||
---
|
||||
|
||||
## 5. General Notes & Resources
|
||||
|
||||
| Note Content | Context or Link |
|
||||
|---|---|
|
||||
| Facebook Graph API Guide | https://docs.google.com/document/d/1ydWNom0TUVh5SkU9IymlpnM_NXbTqHeL_YetDLn-9m0/edit?usp=sharing |
|
||||
| Google Sheets template | https://docs.google.com/spreadsheets/d/11M6Co13t9t2P7NtFaTm9VXHnzUHXO_RGYf-FRnHkS28/edit?usp=sharing |
|
||||
| Date-range query format note | `time_range[since]=YYYY-MM-DD&time_range[until]=YYYY-MM-DD` |
|
||||
| Attribution/credit in sticky note | “Happy setup, Bakdaulet Abdikhan… Feel free to add slack/email nodes to notify alerts/updates ;)” |
|
||||
Reference in New Issue
Block a user