How to prevent duplicate emails in AI Agent workflow when ready_to_email stays true

Describe the problem/error/question

Hi everyone,
I’ve built a recruitment chatbot using the AI Agent node with Google Gemini, Telegram Trigger, Postgres Chat Memory, and Structured Output Parser. The bot interviews users in Algerian Darija and collects structured profile data (name, study field, skills, etc.).
The workflow:
Telegram Trigger → AI Agent → Postgres Memory → Structured Output Parser → IF node (checks ready_to_email === true) → Mailtrap (sends report to VP) → Send Message back to user
The problem:
Once the profile is complete, the AI sets ready_to_email: true and sends the email. But on the next user message, the AI still outputs ready_to_email: true (because the profile stays complete), so the IF node fires again and another duplicate email gets sent. This happens on every subsequent message.
What I’ve tried:
Added an email_sent boolean flag to the JSON schema and told the AI to set it to true after the first email, and never set ready_to_email: true again after that
Updated the IF node to check both ready_to_email === true AND email_sent === false
The AI sometimes forgets to set email_sent or still outputs ready_to_email: true on later turns
My question:
Is there a better way to make sure the email node only fires once per conversation? I thought about using a database to store a flag, or maybe there’s a built-in n8n node that acts as a one-time gate? I found the “Execute Once” option but I believe that only works within a single workflow execution, not across multiple messages.
Any advice on the cleanest way to handle this?
Thanks!

What is the error message (if any)?

Please share your workflow

Share the output returned by the last node

Information on your n8n setup

  • n8n version: 2.22.4
  • Database (default: SQLite): Postgresql (supabase)
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):n8n cloud
  • Operating system: windows

@Hadiluno cleanest fix is dont trust the LLM to remember email-sent state — externalize it. since u already have Supabase Postgres connected, add a 1-column table and gate the email node on it:

CREATE TABLE sent_emails (
  session_id TEXT PRIMARY KEY,
  sent_at TIMESTAMP DEFAULT NOW()
);

then drop these 2 nodes into ur workflow — one before the IF, one after Mailtrap:

then in ur existing IF node, change the condition to AND together:

  • {{ $('Structured Output Parser').item.json.ready_to_email }} === true
  • {{ $('Check Email Sent').all().length }} === 0

now the LLM can flip ready_to_email a hundred times — the gate lives in the DB, not the model. quick Q: ur using telegram chat.id as the session key for the Postgres Chat Memory right? if so the same id works as the email-sent key, no extra plumbing.

Hi @Hadiluno

The problem is that you are asking the AI to act as a database. AI models are great at conversation, but they are “probabilistic,” meaning they can forget or ignore specific instructions over time. When you ask it to remember that an email was already sent, it eventually slips up and triggers the “ready” flag again because the user’s profile is still technically complete.

The solution is to move the “Email Sent” flag out of the AI’s head and into a real database table. Instead of trusting the AI to track the status, you create a simple list in Postgres that tracks each user’s ID and whether their email has been sent. This creates a “single source of truth” that never forgets.

In your workflow, you would add a step at the beginning to check this database for the user’s status. Then, your IF node will only send the email if two things are true: the AI says the profile is ready AND the database says the email hasn’t been sent yet. Once the email is sent, the workflow immediately updates the database to “true,” effectively locking the gate.

This approach is much cleaner because it simplifies the AI’s job. You can remove all the confusing “email_sent” instructions from your prompt, allowing the AI to focus entirely on the interview. This makes the bot more reliable, reduces errors, and ensures that your VP only receives one professional report per candidate.

To implement this, you first need to run this SQL command in your Postgres database to create the status tracking table:

CREATE TABLE user_status (
    chat_id TEXT PRIMARY KEY,
    email_sent BOOLEAN DEFAULT FALSE
);

Here is the workflow

Note: You will need to re-select your Postgres credentials after importing.

THANK YOU GUYS SO MUCH FOR THE HELP, IT’S WORKING NOW!!!, this issue was pissing me off for days. :yellow_heart::yellow_heart:

@Hadiluno please mark relevant answer as the solution, thank you!!!

Your welcome! Happy me and @kjooleng could help!