Back to n8n Workflows

Rate my Tweets

Mike Taylor Mike Taylor

Get your Twitter account rated by AI personas to discover which tweets resonate most with your audience. Scrape any Twitter handle and receive quantified engagement scores with detailed analytics.

Rate my Tweets n8n workflow diagram

Click to expand

Summarize in
OR

Overview

This workflow transforms Twitter content evaluation by automatically scraping any Twitter handle and subjecting their tweets to AI persona analysis. It fetches the most recent tweets from a specified account, filters out retweets to focus on original content, then presents the collection to Rally's AI audience for rating on a scale of 1-10. The system provides comprehensive analytics including vote distribution, average scores, and summary insights, giving you data-driven feedback on what content performs best with your target audience.

🎯 Pro Tips & Secret Sauce

The magic lies in the crowd-sourced content evaluation with persona-based scoring:

  1. Smart Content Filtering - Automatically excludes retweets and replies to focus analysis on original content that actually reflects the account's voice and creativity, ensuring authentic performance measurement
  2. Scalable Tweet Aggregation - Efficiently processes up to 100 recent tweets in a single workflow run, providing comprehensive content analysis without manual tweet selection or copy-pasting
  3. AI Persona Consensus Scoring - Uses Rally's diverse AI audience to rate tweets on a 1-10 scale, mimicking real human engagement patterns and providing statistically significant feedback across multiple perspectives
  4. Comprehensive Analytics Dashboard - Processes individual persona responses into meaningful metrics including vote distribution, weighted averages, and qualitative summary insights for actionable content strategy
  5. Instant Content Optimization - Provides immediate feedback on content performance without waiting for organic engagement, enabling rapid iteration and improvement of social media strategy

This creates a powerful content evaluation system that provides objective, data-driven insights into tweet performance, helping creators understand what resonates with their audience before publishing.

πŸ“ Step-by-Step Instructions

  1. Form Input - User enters any Twitter handle (without @) to analyze through the web form interface
  2. Apify Tweet Scraping - Automatically scrapes up to 100 recent tweets from the specified Twitter account using Apify's Twitter scraper service
  3. Content Filtering - Filters out retweets and replies to focus analysis exclusively on original content that reflects the account's authentic voice
  4. Tweet Aggregation - Combines all filtered tweets into a single comprehensive dataset for batch processing and analysis
  5. Rally AI Assessment - Sends the complete tweet collection to Rally's AI personas asking them to rate content on a 1-10 scale from "boring" to "absolute bangers"
  6. Response Processing - Splits Rally's response array to extract individual persona ratings and voting decisions for detailed analysis
  7. Statistical Analysis - Custom code processes all ratings to calculate vote distribution, weighted averages, and overall performance metrics
  8. Results Display - Formats comprehensive analytics into an easy-to-read report showing rating breakdown, average score, and AI-generated summary insights

πŸ“‹ Requirements

Required Integrations

  • Apify Twitter Scraper - Automated tweet collection service with rate limiting and data structuring capabilities
  • Rally API - AI persona testing service with numeric rating capabilities and audience-specific analysis
  • Form Trigger - Web interface for inputting Twitter handles and initiating analysis workflows

Required Credentials

  • Apify API credentials with access to Twitter scraper task and sufficient usage quotas
  • Rally API Bearer token with rating mode access and persona audience configuration
  • n8n instance with form trigger capabilities for web interface functionality

Setup Prerequisites

  • Active Rally account with configured audience personas (default: ra6832c53468b43)
  • Apify account with Twitter scraper task configured for English language tweets
  • Sufficient API quotas for both Apify (100 tweets per analysis) and Rally (persona rating requests)
  • No Twitter API access required - Apify handles all social media platform interactions

πŸš€ n8n Workflow Template

{
  "active": false,
  "connections": {
    "Aggregate": {
      "main": [
        [
          {
            "index": 0,
            "node": "Ask Rally",
            "type": "main"
          }
        ]
      ]
    },
    "Apify Scrape Twitter": {
      "main": [
        [
          {
            "index": 0,
            "node": "Filter out retweets",
            "type": "main"
          }
        ]
      ]
    },
    "Ask Rally": {
      "main": [
        [
          {
            "index": 0,
            "node": "Split Out",
            "type": "main"
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "index": 0,
            "node": "Form",
            "type": "main"
          }
        ]
      ]
    },
    "Filter out retweets": {
      "main": [
        [
          {
            "index": 0,
            "node": "Aggregate",
            "type": "main"
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "index": 0,
            "node": "Apify Scrape Twitter",
            "type": "main"
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "index": 0,
            "node": "Code",
            "type": "main"
          }
        ]
      ]
    }
  },
  "id": "PoazjiIAKjgtJOxQ",
  "meta": {
    "instanceId": "7aa2e96d57ff40383569724f8ecb13d674a87bf09d39aa5d8fa5ba31f7a8407a"
  },
  "name": "Rate my Tweets",
  "nodes": [
    {
      "id": "ae302e15-e099-4fba-aa92-4b77df0f2a52",
      "name": "On form submission",
      "parameters": {
        "formFields": {
          "values": [
            {
              "fieldLabel": "handle",
              "placeholder": "elonmusk",
              "requiredField": true
            }
          ]
        },
        "formTitle": "Rate my tweets",
        "options": {}
      },
      "position": [
        0,
        0
      ],
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.2,
      "webhookId": "fe8c2090-666f-4f15-9074-ab94535b6975"
    },
    {
      "id": "7072f315-2bc3-432f-9bc3-e31becb24c78",
      "name": "Filter out retweets",
      "parameters": {
        "conditions": {
          "combinator": "and",
          "conditions": [
            {
              "id": "74aa2a63-e5b8-46e6-b488-3bae3bfb7c41",
              "leftValue": "={{ $json.isRetweet }}",
              "operator": {
                "operation": "false",
                "singleValue": true,
                "type": "boolean"
              },
              "rightValue": "false"
            }
          ],
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          }
        },
        "options": {}
      },
      "position": [
        440,
        0
      ],
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2
    },
    {
      "credentials": {
        "httpQueryAuth": {
          "id": "7E3Fo8Z6j8OTfWo2",
          "name": "Apify"
        }
      },
      "id": "664ab3c4-fba9-44a3-930c-cbb0ebc04af6",
      "name": "Apify Scrape Twitter",
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "jsonBody": "={{ JSON.stringify({\n  author: $json.handle,\n  customMapFunction: \"(object) =\u003e { return {...object} }\",\n  includeSearchTerms: false,\n  maxItems: 100,\n  onlyImage: false,\n  onlyQuote: false,\n  onlyTwitterBlue: false,\n  onlyVerifiedUsers: false,\n  onlyVideo: false,\n  sort: \"Latest\",\n  tweetLanguage: \"en\"\n}) }}\n",
        "options": {},
        "sendBody": true,
        "specifyBody": "json",
        "url": "https://api.apify.com/v2/actor-tasks/askrally~get-someones-tweets/run-sync-get-dataset-items"
      },
      "position": [
        220,
        0
      ],
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2
    },
    {
      "id": "d63f2736-9ab2-4bd8-a99d-082bd6e0bd39",
      "name": "Aggregate",
      "parameters": {
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "fullText"
            }
          ]
        },
        "options": {}
      },
      "position": [
        660,
        0
      ],
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1
    },
    {
      "credentials": {
        "httpBearerAuth": {
          "id": "HeUQ9K9oUtQ5TyHr",
          "name": "Bearer Auth account"
        }
      },
      "id": "7cb29c4d-f89a-40c2-8c39-9a491bb77686",
      "name": "Ask Rally",
      "parameters": {
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "smart",
              "value": "false"
            },
            {
              "name": "provider",
              "value": "google"
            },
            {
              "name": "query",
              "value": "=Rate these tweets out of 10, with 0 being boring and 10 being absolute bangers.\n\nHere are the tweets:\n\n{{ $json.fullText }}\n\n---\nReturn a number between 1 or 10."
            },
            {
              "name": "audience_id",
              "value": "ra6832c53468b43"
            },
            {
              "name": "voting_mode",
              "value": "true"
            }
          ]
        },
        "genericAuthType": "httpBearerAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "accept",
              "value": "application/json"
            }
          ]
        },
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "url": "https://api.askrally.com/api/v1/chat"
      },
      "position": [
        860,
        0
      ],
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2
    },
    {
      "id": "ebefbc69-0dd1-4ce9-9b9f-9038e0c8ad7c",
      "name": "Split Out",
      "parameters": {
        "fieldToSplitOut": "responses",
        "options": {}
      },
      "position": [
        360,
        220
      ],
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1
    },
    {
      "id": "a7b8886a-effb-436f-9321-7dc07b866f86",
      "name": "Code",
      "parameters": {
        "jsCode": "let optionCounts = {};\nlet total = 0;\n\nfor (const item of $input.all()) {\n  try {\n    const responseData = JSON.parse(item.json.response);\n    const optionRaw = responseData.option;\n\n    // Convert to number (safely)\n    const optionNum = Number(optionRaw);\n\n    // Only count if it\u0027s an integer between 1 and 10\n    if (Number.isInteger(optionNum) \u0026\u0026 optionNum \u003e= 1 \u0026\u0026 optionNum \u003c= 10) {\n      const key = optionNum.toString(); // Keep keys as strings\n      total++;\n      optionCounts[key] = (optionCounts[key] || 0) + 1;\n    }\n\n  } catch (error) {\n    console.log(`Error parsing response for item ${item.json.persona_id}:`, error.message);\n  }\n}\n\n// Get the summary from Ask Rally node\nconst summary = $(\u0027Ask Rally\u0027).first().json.summary;\n\nreturn [{\n  json: {\n    counts: optionCounts,\n    summary: summary\n  }\n}];\n"
      },
      "position": [
        580,
        260
      ],
      "type": "n8n-nodes-base.code",
      "typeVersion": 2
    },
    {
      "id": "7661dd39-faba-48db-8534-5a4415f6e290",
      "name": "Form",
      "parameters": {
        "completionMessage": "={{\n  (() =\u003e {\n    const counts = $json.counts;\n\n    // Use original insertion order\n    const ratingText = Object.keys(counts)\n      .map(key =\u003e `\u2b50${key}: ${counts[key]}`)\n      .join(\u0027  |  \u0027);\n\n    const totalVotes = Object.keys(counts)\n      .reduce((sum, key) =\u003e sum + counts[key], 0);\n\n    const weightedSum = Object.keys(counts)\n      .reduce((sum, key) =\u003e sum + (Number(key) * counts[key]), 0);\n\n    const avg = (weightedSum / totalVotes).toFixed(2);\n\n    return `Here\u0027s your rating: ${ratingText}  ||  Avg: ${avg}  ||  Summary: ${$json.summary}`;\n  })()\n}}\n",
        "completionTitle": "Rating",
        "operation": "completion",
        "options": {}
      },
      "position": [
        800,
        220
      ],
      "type": "n8n-nodes-base.form",
      "typeVersion": 1,
      "webhookId": "f61bfe09-d13f-463f-8c5e-4def8ed661e4"
    }
  ],
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [],
  "versionId": "500c60ab-fd1b-4df9-ae15-87dd2b369628"
}

About the Author

Mike Taylor

Mike Taylor

Mike Taylor is the CEO & Co-Founder of Rally. He previously co-founded a 50-person growth marketing agency called Ladder, created marketing & AI courses on LinkedIn, Vexpower, and Udemy taken by over 450,000 people, and published a book with O’Reilly on prompt engineering.