Rapport PDF hebdomadaire de publicités Meta piloté par IA - envoyé vers Slack ou e-mail

Intermédiaire

Ceci est unMarket Research, AI Summarizationworkflow d'automatisation du domainecontenant 14 nœuds.Utilise principalement des nœuds comme Set, Code, Slack, SplitOut, Pdforge. Générer et envoyer un rapport hebdomadaire sur les performances publicitaires Meta dans Slack avec les aperçus de GPT-4

Prérequis
  • Token Bot Slack ou URL Webhook
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
  • Clé API OpenAI
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "id": "OGdHKG9jhEKDrZAC",
  "meta": {
    "instanceId": "33bfcf6494ef5d9b0fac5fde544f7913410659bb957613631505fa8056a7625a",
    "templateCredsSetupCompleted": true
  },
  "name": "AI-Powered Meta Ads Weekly PDF Report –  Sends to your Slack or Email",
  "tags": [],
  "nodes": [
    {
      "id": "e2a34995-dd70-432e-8bbc-3432421db01d",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -880,
        40
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "a7c0b061-00a3-47a8-9ed6-3e8b840cc6a7",
      "name": "HTTP Request - Meta Ads",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -1100,
        40
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v23.0/act_{{$json.ad_account_id}}/insights",
        "options": {
          "pagination": {
            "pagination": {
              "nextURL": "={{ $response.body.paging.next }}",
              "paginationMode": "responseContainsNextURL",
              "requestInterval": 500,
              "completeExpression": "={{ !$response.body.paging.next }}",
              "paginationCompleteWhen": "other"
            }
          }
        },
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "=campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,reach,impressions,inline_link_clicks,inline_link_click_ctr,conversions,spend,cpc,cost_per_conversion,action_values,purchase_roas"
            },
            {
              "name": "time_increment",
              "value": "1"
            },
            {
              "name": "date_preset",
              "value": "={{ $json.date_range }}"
            },
            {
              "name": "level",
              "value": "ad"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "Z9SGcPUjCFlHCdPM",
          "name": "Meta Ads Auth"
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "93076858-210e-4142-b7a1-1c0f5ba0d17d",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1320,
        40
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9941e108-1e6c-4050-8401-9e6125a847de",
              "name": "ad_account_id",
              "type": "string",
              "value": "982086037409625232"
            },
            {
              "id": "a9ca0cda-adc0-40fd-8f8c-8aa7437276c5",
              "name": "date_range",
              "type": "string",
              "value": "last_7d"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "252d5491-0ecc-4c87-bce6-fa60e2aba035",
      "name": "Note adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2040,
        -80
      ],
      "parameters": {
        "width": 420,
        "height": 520,
        "content": "## Welcome to Weekly Meta Ads Report Workflow!\n\n**This workflow has the following sequence:**\n1. Time trigger (e.g. every Monday at 8 a.m.)\n2. Retrieval of Meta Ads data from the last 7 days (or any period you want)\n3. Format it and generate AI insight of the data\n4. Transform it into a PDF report using pdforge (Using the pre-made Meta Ads Template)\n5. Sending the report as a Slack Message (You can change it to send an e-mail, whatsapp or telegram message)\n\n\n**The following accesses are required for the workflow:**\n- Meta Ads (via Facebook Graph API): [Documentation](https://docs.n8n.io/integrations/builtin/credentials/facebookgraph/)\n- pdforge account: [Create here](https://app.pdforge.com/auth/sign-up)\n- AI API access (e.g. via OpenAI, Anthropic, Google or Ollama)\n- Slack Connection: [Documentation](https://docs.n8n.io/integrations/builtin/credentials/slack)\n\n\nYou can contact me via LinkedIn, if you have any questions: https://www.linkedin.com/in/marceloamiranda"
      },
      "typeVersion": 1
    },
    {
      "id": "fa9620e6-cee4-4d75-b19b-c6c25c56a555",
      "name": "Note adhésive1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1380,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 220,
        "height": 320,
        "content": "**Here you'll configure:**\n- AD Account ID\n- Date range from ads"
      },
      "typeVersion": 1
    },
    {
      "id": "7ad3d2d7-571f-4e84-b8d3-a14a39b93a6e",
      "name": "Pdforge",
      "type": "n8n-nodes-pdforge.pdforge",
      "position": [
        100,
        40
      ],
      "parameters": {
        "options": {},
        "variables": "={{ JSON.stringify($json) }}",
        "templateId": "meta_ads"
      },
      "credentials": {
        "pdforgeApi": {
          "id": "fFuvCIsBkIHHd0ct",
          "name": "Pdforge account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3ecfcea9-531e-47b2-8074-ed304b35f5e4",
      "name": "Formater les données",
      "type": "n8n-nodes-base.code",
      "position": [
        -660,
        40
      ],
      "parameters": {
        "jsCode": "// 1. extract raw records\nconst records = $input.all().map(item => item.json);\n\n// 2. date utilities\nfunction parseYMD(s){ return new Date(s + 'T00:00:00'); }\nfunction formatYMD(d){\n  const y = d.getFullYear();\n  const m = String(d.getMonth()+1).padStart(2,'0');\n  const da = String(d.getDate()).padStart(2,'0');\n  return `${y}-${m}-${da}`;\n}\nfunction rangeDates(start, end){\n  const a = [];\n  let cur = new Date(start);\n  while(cur <= end){\n    a.push(formatYMD(cur));\n    cur.setDate(cur.getDate()+1);\n  }\n  return a;\n}\n\n// 3. find global start/end\nconst dates = records.flatMap(r=>[r.date_start, r.date_stop]);\nconst minDate = new Date(Math.min(...dates.map(d=>parseYMD(d))));\nconst maxDate = new Date(Math.max(...dates.map(d=>parseYMD(d))));\nconst allLabels = rangeDates(minDate, maxDate);\n\n// 4. overview totals\nconst overviewAgg = records.reduce((acc, r) => {\n  const imp = Number(r.impressions || 0);\n  const clk = Number(r.inline_link_clicks || 0);\n  const spd = Number(r.spend || 0);\n  acc.impressions += imp;\n  acc.clicks     += clk;\n  acc.investment += spd;\n  return acc;\n}, { impressions:0, clicks:0, investment:0 });\n\nconst overview = {\n  investment:  { value: Number(overviewAgg.investment.toFixed(2)) },\n  impressions: { value: overviewAgg.impressions },\n  clicks:      { value: overviewAgg.clicks },\n  ctr:         { \n    value: overviewAgg.impressions\n      ? `${(overviewAgg.clicks / overviewAgg.impressions * 100).toFixed(2)}%`\n      : '0.00%' \n  },\n  cpc:         { \n    value: overviewAgg.clicks\n      ? Number((overviewAgg.investment / overviewAgg.clicks).toFixed(4))\n      : 0 \n  }\n};\n\n// 5. day_by_day\nconst dayMap = records.reduce((m, r) => {\n  const d = r.date_start;\n  if (!m[d]) m[d] = { impressions:0, clicks:0 };\n  m[d].impressions += Number(r.impressions || 0);\n  m[d].clicks     += Number(r.inline_link_clicks || 0);\n  return m;\n}, {});\nconst day_by_day = {\n  labels:      allLabels,\n  impressions: allLabels.map(d => dayMap[d]?.impressions || 0),\n  clicks:      allLabels.map(d => dayMap[d]?.clicks      || 0),\n};\n\n// 6. helper to aggregate by key (com formatação de investimento em 2 casas)\nfunction aggBy(records, keyField){\n  const map = {};\n  records.forEach(r => {\n    const key = r[keyField] || 'N/A';\n    if (!map[key]) map[key] = { \n      name: key, impressions:0, clicks:0, investment:0 \n    };\n    map[key].impressions += Number(r.impressions || 0);\n    map[key].clicks      += Number(r.inline_link_clicks || 0);\n    map[key].investment  += Number(r.spend || 0);\n  });\n  return Object.values(map).map(o => ({\n    name:        o.name,\n    impressions: o.impressions,\n    clicks:      o.clicks,\n    investment:  Number(o.investment.toFixed(2)),               // <-- 2 decimais aqui\n    ctr:         o.impressions\n                   ? `${(o.clicks / o.impressions * 100).toFixed(2)}%`\n                   : '0.00%',\n    cpc:         o.clicks\n                   ? Number((o.investment / o.clicks).toFixed(4))\n                   : 0,\n  }));\n}\n\n// 7. campaigns, ad-sets, ads\nconst byCampaign = aggBy(records, \"campaign_name\");\nconst byAdSet    = aggBy(records, \"adset_name\");\nconst byAd       = aggBy(records, \"ad_name\");\n\n// 8. top 10 cheapest CPC\nconst top10Campaigns = [...byCampaign]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(c=>({\n    campaign_name: c.name,\n    investment:    c.investment,\n    impressions:   c.impressions,\n    ctr:           c.ctr,\n    clicks:        c.clicks,\n    cpc:           c.cpc\n  }));\nconst top10AdSets = [...byAdSet]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(a=>({\n    ad_group_name: a.name,\n    investment:    a.investment,\n    impressions:   a.impressions,\n    ctr:           a.ctr,\n    clicks:        a.clicks,\n    cpc:           a.cpc\n  }));\nconst top10Ads = [...byAd]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(a=>({\n    ad_group_name: a.name,\n    investment:    a.investment,\n    impressions:   a.impressions,\n    ctr:           a.ctr,\n    clicks:        a.clicks,\n    cpc:           a.cpc\n  }));\n\n// 9. comparisons ordered by investment desc\nconst adsComparison    = byAd.sort((a,b)=>b.investment - a.investment);\nconst adSetsComparison = byAdSet.sort((a,b)=>b.investment - a.investment);\n\n// 10. monta o relatório final\nconst report = {\n  report_date: `${formatYMD(minDate)} - ${formatYMD(maxDate)}`,\n  overview,\n  day_by_day,\n  top_10_campaigns:   top10Campaigns,\n  ad_set_comparison: {\n    labels:      adSetsComparison.map(a=>a.name),\n    investment:  adSetsComparison.map(a=>Number(a.investment.toFixed(2))),\n    impressions: adSetsComparison.map(a=>a.impressions),\n    clicks:      adSetsComparison.map(a=>a.clicks),\n  },\n  top_10_ad_sets:     top10AdSets,\n  ads_comparison: {\n    labels:      adsComparison.map(a=>a.name),\n    investment:  adsComparison.map(a=>Number(a.investment.toFixed(2))),\n    impressions: adsComparison.map(a=>a.impressions),\n    clicks:      adsComparison.map(a=>a.clicks),\n  },\n  top_10_ads:        top10Ads\n};\n\n// 11. retorna um único item com o JSON pronto\nreturn [{ json: report }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4fc7c497-470f-4f24-adb2-e0b411c706f3",
      "name": "Télécharger le binaire",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        40
      ],
      "parameters": {
        "url": "={{ $json.signedUrl }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "06bf787e-7304-401e-b522-4867bf3cec3d",
      "name": "Génération d'Insights pour les Publicités Meta",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -460,
        40
      ],
      "parameters": {
        "text": "={{ JSON.stringify($json) }}",
        "options": {
          "systemMessage": "=## Persona\nYou are a data-driven Meta Ads Insights Agent: concise, analytical, and focused on turning raw campaign data into clear, actionable recommendations.\n\n## Objective\n- Ingest structured Meta Ads data (campaigns → adsets → ads).  \n- Generate three tiers of performance insights: campaign, adset, ad.  \n- Keep each insight 200–300 characters, in English, ending with a practical tip.\n\n## Rules\n### Formatting\n- Output **only** a JSON object with keys: `campaign_insights`, `adset_insights`, `ad_insights`.  \n- Do **not** include any additional text or explanation.  \n- Ensure each insight string is between **200 and 300 characters** (inclusive).\n\n### Content\n- All insights must be written in clear, idiomatic English.  \n- Each insight must:\n  1. Summarize performance (e.g., spend vs. CTR/CPC comparisons).  \n  2. Highlight one key strength or weakness.  \n  3. Conclude with a single, concrete, actionable tip.\n\n### Constraints\n- Base insights strictly on the supplied data.  \n- Do not reference hypothetical or external factors.  \n- Do not break JSON syntax or embed markdown in the output.\n\n## Step by Step Instructions\n1. **Parse Input**: Read the incoming JSON payload of `campaigns` → `adsets` → `ads`.  \n2. **Aggregate Metrics**:  \n   - For each campaign: compute total investment, average CTR (weighted by impressions), and average CPC.  \n   - For each adset: compute its CTR and CPC.  \n   - For each ad: read its individual metrics.  \n3. **Identify Highlights**:  \n   - Campaign: find overall high or low performance signal.  \n   - Adset: select the best or worst by CTR or CPC.  \n   - Ad: select the top or bottom ad by CTR or CPC.  \n4. **Compose Insights**: For each level, draft a 200–300 character string that:  \n   - Summarizes performance.  \n   - Notes one key strength or weakness.  \n   - Ends with “Tip: …” giving a clear improvement action.  \n5. **Validate Length**: Confirm each insight is within 200–300 characters.  \n6. **Return Output**: Emit exactly the JSON object described below.\n\n## Output Format\n```json\n{\n  \"campaign_insights\": \"string (200–300 chars)\",\n  \"adset_insights\":  \"string (200–300 chars)\",\n  \"ad_insights\":     \"string (200–300 chars)\"\n}\n```"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2
    },
    {
      "id": "be4f212e-78a9-4721-82e2-883e1ac02f9e",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -560,
        300
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1",
          "cachedResultName": "gpt-4.1"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "3hJLI2ahkB4Zjnwd",
          "name": "OpenAi account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "18aa7809-12f6-4ed4-9523-5b4a8efe8140",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -200,
        260
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"campaign_insights\": \"string (200–300 chars)\",\n  \"adset_insights\":  \"string (200–300 chars)\",\n  \"ad_insights\":     \"string (200–300 chars)\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "49f0bd44-ad30-482e-9c05-9027081d7c7a",
      "name": "Générer des variables",
      "type": "n8n-nodes-base.code",
      "position": [
        -100,
        40
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const base = $(\"Format data\").first().json\n\nreturn {\n      ...base,                                           \n      insights_content:     $json.output.campaign_insights,\n      insights_ad_set_content:$json.output.adset_insights,\n      insights_ads_content:  $json.output.ad_insights,\n    };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d2bfb619-b65c-4371-a074-9384bdefd34b",
      "name": "Slack - Send Message with File",
      "type": "n8n-nodes-base.slack",
      "position": [
        540,
        40
      ],
      "webhookId": "aae266d5-f681-4b86-bd2a-fceb1f5c182c",
      "parameters": {
        "options": {
          "fileName": "=Meta ADS Report - {{ $('Generate variables').first().json.report_date }}",
          "channelId": "C093723CAN6",
          "initialComment": "📊 *Here's your Weekly Meta Ads Report* \\n"
        },
        "resource": "file",
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "ombwJbyognfRtMoj",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0bd7647f-1f8b-4478-8e76-2a3f176ad39d",
      "name": "Weekly at monday 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1560,
        40
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "timezone": "America/Sao_Paulo",
    "callerPolicy": "workflowsFromSameOwner",
    "executionOrder": "v1",
    "executionTimeout": -1
  },
  "versionId": "85fcbd4c-7f9b-4aae-b04e-99a0980bd474",
  "connections": {
    "7ad3d2d7-571f-4e84-b8d3-a14a39b93a6e": {
      "main": [
        [
          {
            "node": "4fc7c497-470f-4f24-adb2-e0b411c706f3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "e2a34995-dd70-432e-8bbc-3432421db01d": {
      "main": [
        [
          {
            "node": "3ecfcea9-531e-47b2-8074-ed304b35f5e4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3ecfcea9-531e-47b2-8074-ed304b35f5e4": {
      "main": [
        [
          {
            "node": "06bf787e-7304-401e-b522-4867bf3cec3d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "93076858-210e-4142-b7a1-1c0f5ba0d17d": {
      "main": [
        [
          {
            "node": "a7c0b061-00a3-47a8-9ed6-3e8b840cc6a7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4fc7c497-470f-4f24-adb2-e0b411c706f3": {
      "main": [
        [
          {
            "node": "d2bfb619-b65c-4371-a074-9384bdefd34b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "be4f212e-78a9-4721-82e2-883e1ac02f9e": {
      "ai_languageModel": [
        [
          {
            "node": "06bf787e-7304-401e-b522-4867bf3cec3d",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "49f0bd44-ad30-482e-9c05-9027081d7c7a": {
      "main": [
        [
          {
            "node": "7ad3d2d7-571f-4e84-b8d3-a14a39b93a6e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0bd7647f-1f8b-4478-8e76-2a3f176ad39d": {
      "main": [
        [
          {
            "node": "93076858-210e-4142-b7a1-1c0f5ba0d17d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a7c0b061-00a3-47a8-9ed6-3e8b840cc6a7": {
      "main": [
        [
          {
            "node": "e2a34995-dd70-432e-8bbc-3432421db01d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18aa7809-12f6-4ed4-9523-5b4a8efe8140": {
      "ai_outputParser": [
        [
          {
            "node": "06bf787e-7304-401e-b522-4867bf3cec3d",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "d2bfb619-b65c-4371-a074-9384bdefd34b": {
      "main": [
        []
      ]
    },
    "06bf787e-7304-401e-b522-4867bf3cec3d": {
      "main": [
        [
          {
            "node": "49f0bd44-ad30-482e-9c05-9027081d7c7a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Intermédiaire - Étude de marché, Résumé IA

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Intermédiaire
Nombre de nœuds14
Catégorie2
Types de nœuds11
Description de la difficulté

Adapté aux utilisateurs expérimentés, avec des workflows de complexité moyenne contenant 6-15 nœuds

Auteur
Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34