AutomationFlowsAI & RAG › Validate Newsletter Quality with Gpt-5 Quality Gate Before Sending

Validate Newsletter Quality with Gpt-5 Quality Gate Before Sending

ByEmir Belkahia @emirbelkahia on n8n.io

This sub-workflow validates newsletter quality before sending to customers. It's triggered by the main newsletter workflow and acts as an automated quality gate to catch data issues, broken layouts, or missing content.

Event trigger★★★★☆ complexityAI-powered22 nodesGmailAgentOpenAI ChatOutput Parser StructuredExecute Workflow Trigger
AI & RAG Trigger: Event Nodes: 22 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #10734 — we link there as the canonical source.

This workflow follows the Agent → Execute Workflow Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "BdyzMyNpnkpiJvc9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "2025 Nov - Quality Assurance: review newsletter before sending to subscribers (LLM Judge)",
  "tags": [],
  "nodes": [
    {
      "id": "d2d3baf1-6092-4108-8707-80d7e5cd1f88",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2384,
        -352
      ],
      "parameters": {
        "color": 4,
        "width": 256,
        "height": 304,
        "content": "## Send Prepared Newsletter to LLM Judge's mailbox.\nSends the newsletter to the inbox of the LLM Judge so it can grab the rendered version of it."
      },
      "typeVersion": 1
    },
    {
      "id": "035aaeaf-eff9-4cf2-819d-6245a8e0875f",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        16
      ],
      "parameters": {
        "width": 624,
        "height": 944,
        "content": "## Newsletter expected structure\n\n![Newsletter structure](https://gkfvbshnthawbcaugpwp.supabase.co/storage/v1/object/public/n8n-diagrams/2025%20Nov%2011%20-%20Newsletter%20full.png \"Newsletter structure\")"
      },
      "typeVersion": 1
    },
    {
      "id": "e78daaf2-2ca8-4106-9ba6-67b2d0c8521f",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        -1376
      ],
      "parameters": {
        "width": 624,
        "height": 1360,
        "content": "## Newsletter Quality Assurance with LLM Judge\n\nThis sub-workflow validates newsletter quality before sending to customers. It's triggered by the main newsletter workflow and acts as an automated quality gate to catch data issues, broken layouts, or missing content.\n\n## Who's it for\n\nE-commerce teams who want to automate newsletter quality checks and prevent broken or incomplete emails from reaching customers. Perfect for ensuring consistent brand quality without manual review.\n\n## How it works\n\n1. **Receives newsletter HTML** - Triggered by parent workflow with the generated newsletter content\n2. **Sends to test inbox** - Delivers newsletter to LLM Judge's Gmail inbox to validate actual rendering\n3. **Retrieves rendered email** - Fetches the email back from Gmail to analyze how it actually renders (catches Gmail-specific issues)\n4. **AI-powered validation** - GPT-5 analyzes the newsletter against quality criteria:\n   - Verifies all 6 product cards have images, prices, and descriptions\n   - Checks layout integrity and date range formatting\n   - Detects broken images or unprocessed template variables\n   - Validates sale prices are lower than original prices\n5. **Decision gate** - Based on Judge's verdict:\n   - **PASS**: Returns approval to parent workflow \u2192 sends to customers\n   - **BLOCK**: Alerts admin via email \u2192 requires human review\n\n## Set up steps\n\n**Setup time: ~5 minutes**\n\n1. Connect your **Gmail account** for sending test emails\n2. Update the **Judge's email address** in \"Send newsletter to LLM Judge\" node\n3. Update the **admin alert email** in error handling nodes\n4. Connect your **OpenAI API** credentials (GPT-5 recommended for heavy HTML processing)\n5. (Optional) Adjust quality thresholds in the Judge's system prompt\n\n## Requirements\n\n- Gmail account for test sends and retrieving rendered emails\n- OpenAI API key (GPT-5 recommended)\n- Parent workflow that passes newsletter HTML content\n\n## How to customize\n\n- **Adjust validation strictness**: Modify the Judge's system prompt to change what triggers BLOCK vs PASS\n- **Change product count**: Update prompt if your newsletters have different numbers of products\n- **Add custom checks**: Extend the system prompt with brand-specific validation rules\n- **Modify alert recipients**: Update email addresses in error handling nodes\n\n\ud83d\udca1 **Pro tip**: The workflow validates the actual Gmail-rendered version to catch image loading issues and ensure consistent customer experience."
      },
      "typeVersion": 1
    },
    {
      "id": "b75572bd-3c93-4eed-8afe-8a8645a713c2",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4864,
        -192
      ],
      "parameters": {
        "width": 320,
        "height": 144,
        "content": "## Questions or Feedback?\n\n\ud83d\udce7 emir.belkahia@gmail.com  \n\ud83d\udcbc [linkedin.com/in/emirbelkahia](https://www.linkedin.com/in/emirbelkahia/)"
      },
      "typeVersion": 1
    },
    {
      "id": "f6e0d539-bfa9-4c95-b1ee-940396853f95",
      "name": "Send newsletter to LLM Judge",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2464,
        -16
      ],
      "parameters": {
        "sendTo": "=emir.belkahia@gmail.com",
        "message": "={{ $json.html }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "=FOR REVIEW BY JUDGE - Dogtreats - Promos of the week"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "ade87ac8-112f-4db3-988b-cde23037107d",
      "name": "Get the newsletter",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2928,
        -16
      ],
      "parameters": {
        "simple": false,
        "options": {},
        "messageId": "={{ $('Send newsletter to LLM Judge').item.json.id }}",
        "operation": "get"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "425491b5-65f9-4f0f-9f64-9e6c1dac568a",
      "name": "Give the email some time to arrive",
      "type": "n8n-nodes-base.wait",
      "position": [
        2704,
        -16
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "df057f13-684a-48e5-8b43-8121ecf4d865",
      "name": "Simplify output before giving to Agent",
      "type": "n8n-nodes-base.set",
      "position": [
        3248,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9edcaf20-6f14-4c16-a7ad-9b6da44e9bb9",
              "name": "html",
              "type": "string",
              "value": "={{ $json.html }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7c3e7626-dca0-4915-a051-8b357f7f7366",
      "name": "Inform human that there are errors",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4560,
        528
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=Judge wasn't able to review the newsletter of the day. \nNotification time: {{ $now }}.\nAdministrator must review N8N workflow.",
        "options": {},
        "subject": "=Judge wasn't able to review the newsletter {{ $now }}",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "02c6c39e-7991-46cb-9b0e-e0cc460682dc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        -352
      ],
      "parameters": {
        "color": 4,
        "width": 256,
        "height": 304,
        "content": "## Upon Newsletter Generation\nThis sub-workflow gets triggered by a parent workflow which needs a review of the newsletter by the LLM Judge. \nPrevious node sends the HTML content of the newsletter. "
      },
      "typeVersion": 1
    },
    {
      "id": "9459af27-5642-4eca-96db-4f6dacad2991",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4432,
        384
      ],
      "parameters": {
        "color": 3,
        "width": 368,
        "height": 352,
        "content": "## Workflow error handling\nSomething went wrong with the Agent (could be the model or the parser). Cause should be reviewed by Admin. "
      },
      "typeVersion": 1
    },
    {
      "id": "a29b6481-d79a-4455-ae88-55c4d74eb131",
      "name": "Judge happy ?",
      "type": "n8n-nodes-base.if",
      "position": [
        4192,
        -160
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3733282f-dad1-4751-80c8-722057c9f963",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.status }}",
              "rightValue": "=PASS"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9f0ebd7c-0471-4ed1-8095-5a29b182864e",
      "name": "Send approval to parent workflow",
      "type": "n8n-nodes-base.set",
      "position": [
        4544,
        -288
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "6baf5d1a-3b18-470a-98f5-d7f3844b2c58",
              "name": "output.status",
              "type": "string",
              "value": "={{ $json.output.status }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6c645c9e-6367-4a15-997f-e0a0f1be30c1",
      "name": "Judge \ud83d\udc69\u200d\u2696\ufe0f",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueErrorOutput",
      "position": [
        3616,
        -16
      ],
      "parameters": {
        "text": "=# LLM Judge System Prompt - Newsletter Quality Assurance\n\nYou are a Quality Assurance Agent responsible for validating newsletter emails before they are sent to customers. Your role is CRITICAL - you are the last line of defense against sending broken or incomplete emails.\n\n## Your Mission\n\nAnalyze the HTML email that was rendered by Gmail and determine if it's safe to send to customers. If you detect ANY issue, you must BLOCK the send and alert a human.\n\n## What to Check\n\n### 1. Data Completeness\n- All product cards have images (no broken image links or missing `src` attributes)\n- All products have names, prices, and descriptions\n- Original prices and sale prices are both present and formatted correctly\n- The promotion date range is present and makes logical sense (e.g., \"Nov 10 \u2013 Nov 16, 2025\")\n- No placeholder text like curly braces, \"undefined\", \"null\", or template variables left unprocessed\n\n### 2. Structural Integrity\n- All 6 product cards are rendered (not fewer, not more)\n- Product grid layout is intact (3 columns \u00d7 2 rows)\n- Header with logo and date range is visible\n- Footer with social links and unsubscribe options is present\n- \"View deal\" buttons are present on all cards\n- No broken HTML (unclosed tags, malformed structure)\n\n### 3. Visual Rendering Issues\n- Images are loading correctly (not showing broken image icons)\n- Text is readable (no overlapping, no cut-off content)\n- Colors and styling are applied (not showing unstyled HTML)\n- Buttons and links are styled properly (not plain text)\n- Layout is not collapsed or broken\n\n### 4. Data Logic Validation\n- Sale prices are LOWER than original prices (basic sanity check)\n- Product descriptions are not empty or just whitespace\n- Email subject line contains the correct date range\n- No duplicate products (same productID appearing twice)\n\n## Decision Rules\n\n- **PASS**: Email is perfect, no issues detected \u2192 status = \"PASS\", recommendation = \"SEND\"\n- **BLOCK**: Any CRITICAL issue detected \u2192 status = \"BLOCK\", recommendation = \"BLOCK_AND_ALERT\"\n- **REVIEW**: Minor issues that might be acceptable \u2192 status = \"PASS\", recommendation = \"REVIEW_RECOMMENDED\"\n\n## Critical Issues (Auto-Block)\n\n- Missing product data (no name, price, or image)\n- Broken layout (cards not rendering, structure collapsed)\n- Template variables not replaced (still showing curly braces syntax)\n- Fewer than 6 products loaded\n- Date range is malformed or illogical\n- Any broken image links (HTTP 404)\n\n## Minor Issues (Flag for Review)\n\n- One product description slightly longer than others\n- Minor styling inconsistencies\n- Footer text formatting slightly off\n\n## Example Outputs\n\n### Perfect Email Example\n\n{\n  \"status\": \"PASS\",\n  \"confidence\": 100,\n  \"issues_found\": [],\n  \"recommendation\": \"SEND\",\n  \"human_alert_message\": null\n}\n\n### Broken Email Example\n\n{\n  \"status\": \"BLOCK\",\n  \"confidence\": 95,\n  \"issues_found\": [\n    {\n      \"severity\": \"CRITICAL\",\n      \"category\": \"data_missing\",\n      \"description\": \"Product card #4 has no image - src attribute is empty\",\n      \"location\": \"Product card #4 (second row, first column)\"\n    },\n    {\n      \"severity\": \"CRITICAL\",\n      \"category\": \"data_missing\",\n      \"description\": \"Template variable not replaced.\n      \"location\": \"Header date range\"\n    }\n  ],\n  \"recommendation\": \"BLOCK_AND_ALERT\",\n  \"human_alert_message\": \"\ud83d\udea8 Newsletter send BLOCKED: Critical data missing (empty image + unprocessed template variable). Manual intervention required before sending to customers.\"\n}\n\n## Remember\n\nYou are protecting the brand reputation. It's better to BLOCK a send and alert a human than to let a broken email reach customers. When in doubt, BLOCK and alert.\n\nBe thorough, be strict, be the guardian of quality.\n\n {{ $json.html }}",
        "options": {
          "systemMessage": ""
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "4c8418a7-bebc-4b2a-91b6-b08b611f6321",
      "name": "GPT5 as the input to review is heavy",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        3536,
        304
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5",
          "cachedResultName": "gpt-5"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "98e7bc74-fcc6-4244-8ce4-83b2fef277a3",
      "name": "Expected Format",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        3824,
        304
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"status\": {\n      \"type\": \"string\",\n      \"enum\": [\"PASS\", \"BLOCK\"]\n    },\n    \"confidence\": {\n      \"type\": \"integer\"\n    },\n    \"issues_found\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"severity\": {\n            \"type\": \"string\"\n          },\n          \"description\": {\n            \"type\": \"string\"\n          }\n        }\n      }\n    },\n    \"recommendation\": {\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\"status\", \"confidence\", \"issues_found\", \"recommendation\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "d20d359e-432f-4891-8e81-549e4e5bba4d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2848,
        -352
      ],
      "parameters": {
        "color": 4,
        "width": 256,
        "height": 304,
        "content": "## Retrieve Newsletter from Gmail\n\nFetches the sent newsletter back from Gmail to validate the actual rendered output.\n\nThis round-trip ensures we catch any Gmail-specific rendering issues (CSS stripping, image blocking, etc.) before sending to customers. Cost: negligible."
      },
      "typeVersion": 1
    },
    {
      "id": "84ea1224-6703-4c00-8482-f83afe90fd4b",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3728,
        448
      ],
      "parameters": {
        "width": 320,
        "height": 208,
        "content": "## Judge Output Format\n\nReturns: \n- `status` (PASS/BLOCK)\n- `confidence` (0-100), `issues_found` (array), \n- `recommendation` (action to take)"
      },
      "typeVersion": 1
    },
    {
      "id": "09f9ea2a-7f33-4b8c-89d8-d81ebc98b226",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3520,
        -352
      ],
      "parameters": {
        "color": 4,
        "width": 416,
        "height": 304,
        "content": "## Do Newsletter Quality Assurance\n\nLLM Judge validates the newsletter for completeness and quality:\n- Checks all 6 products have images, prices, and descriptions\n- Verifies layout integrity and date range formatting\n- Detects broken images or unprocessed template variables\n\nReturns: `PASS` to proceed or `BLOCK` with detailed issue report."
      },
      "typeVersion": 1
    },
    {
      "id": "021816b7-15e8-422b-bb59-cc7e3cebbde1",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4400,
        -560
      ],
      "parameters": {
        "color": 4,
        "width": 416,
        "height": 224,
        "content": "## Decision Gate - Send or Alert\n\nBased on the Judge's decision:\n\n\u2705 **PASS**: Returns success to parent workflow \u2192 newsletter gets sent to customers\n\n\ud83d\udea8 **BLOCK**: Sends alert email to admin with issue details \u2192 manual review required before sending"
      },
      "typeVersion": 1
    },
    {
      "id": "042c40a9-17b1-46bf-9392-26c688146073",
      "name": "Ask Human to review Newsletter",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4544,
        -16
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ JSON.stringify($json.output, null, 2) }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "Newsletter is not approved - view email to see why",
        "operation": "sendAndWait",
        "responseType": "freeText"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "ceef4768-ba2f-41e3-991b-66d59755ff2d",
      "name": "Executed from Newsletter Creation workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        2144,
        -16
      ],
      "parameters": {
        "inputSource": "passthrough"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "30e85b9e-5019-4aae-8308-076f5e02ba5e",
  "connections": {
    "Judge happy ?": {
      "main": [
        [
          {
            "node": "Send approval to parent workflow",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ask Human to review Newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expected Format": {
      "ai_outputParser": [
        [
          {
            "node": "Judge \ud83d\udc69\u200d\u2696\ufe0f",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Get the newsletter": {
      "main": [
        [
          {
            "node": "Simplify output before giving to Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Judge \ud83d\udc69\u200d\u2696\ufe0f": {
      "main": [
        [
          {
            "node": "Judge happy ?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Inform human that there are errors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send newsletter to LLM Judge": {
      "main": [
        [
          {
            "node": "Give the email some time to arrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Give the email some time to arrive": {
      "main": [
        [
          {
            "node": "Get the newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT5 as the input to review is heavy": {
      "ai_languageModel": [
        [
          {
            "node": "Judge \ud83d\udc69\u200d\u2696\ufe0f",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Simplify output before giving to Agent": {
      "main": [
        [
          {
            "node": "Judge \ud83d\udc69\u200d\u2696\ufe0f",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Executed from Newsletter Creation workflow": {
      "main": [
        [
          {
            "node": "Send newsletter to LLM Judge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This sub-workflow validates newsletter quality before sending to customers. It's triggered by the main newsletter workflow and acts as an automated quality gate to catch data issues, broken layouts, or missing content.

Source: https://n8n.io/workflows/10734/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Typeform IA - YT. Uses typeformTrigger, agent, lmChatOpenAi, toolWorkflow. Event-driven trigger; 75 nodes.

Typeform Trigger, Agent, OpenAI Chat +7
AI & RAG

Automated Research Report Generation with OpenAI, Wikipedia, Google Search, and Gmail/Telegram. Uses lmChatOpenAi, memoryBufferWindow, toolHttpRequest, agent. Event-driven trigger; 26 nodes.

OpenAI Chat, Memory Buffer Window, Tool Http Request +8
AI & RAG

This workflow automates the process of generating professional research reports for researchers, students, and professionals. It eliminates manual research and report formatting by aggregating data, g

OpenAI Chat, Memory Buffer Window, Tool Http Request +8
AI & RAG

Suggest Meeting Slots Using Ai. Uses gmailTrigger, lmChatOpenAi, toolWorkflow, stickyNote. Event-driven trigger; 21 nodes.

Gmail Trigger, OpenAI Chat, Tool Workflow +7
AI & RAG

Calendar_scheduling. Uses gmailTrigger, lmChatOpenAi, toolWorkflow, stickyNote. Event-driven trigger; 21 nodes.

Gmail Trigger, OpenAI Chat, Tool Workflow +7