See llms.txt for all machine-readable content.

Back to Templates

Require two-person email approvals for Airtable change requests with Gmail

Created by

Created by: Melbin Francis || francime
Melbin Francis

Last update

Last update 12 hours ago

Categories

Share


Quick overview

This workflow uses n8n Forms, Data Tables, and Discord webhooks to manage change requests that require two different approvers within a time window, recording every approval attempt and automatically marking each request as approved or expired when the window closes.

How it works

  1. Runs a one-time manual setup that creates three n8n Data Tables (approvers, requests, and approval events), seeds two demo approvers, runs a self-test suite, and posts the results to Discord.
  2. Receives a new change request from an n8n Form, applies the approval policy (expiry window and required approver count), and stores the request as “pending” in the requests Data Table.
  3. Sends a Discord message to notify approvers with the request ID and details, then waits for the configured approval window to elapse.
  4. Receives an approval or decline submission from a second n8n Form, validates the approver secret against the approvers table, and enforces rules like “requester cannot approve” and “same approver cannot approve twice.”
  5. Records each decision attempt in the approval events Data Table and returns a form completion message either confirming it was recorded or explaining why it was refused.
  6. When the approval window closes, counts distinct approvers from recorded approval events, updates the request status to “approved” or “expired,” and shows the final outcome to the requester via a form completion page.

Setup

  1. Create and select a Discord webhook credential, then update the Discord message nodes to post into your target channel.
  2. Run the manual “Set Up And Self Test” path once to create the required n8n Data Tables and verify the approval rules.
  3. Replace the seeded “DEMO ONLY” rows in the approvers table with real approvers and unique secrets, and remove the demo entries.
  4. Set the approval window duration (in minutes) and whether two different approvers are required in the approval policy step.
  5. Share the n8n Form URLs for raising requests and for approvers responding, and securely distribute each approver’s secret out of band.

Requirements

  • An n8n instance running 2.38 or newer, because the workflow uses the built-in Data Table node. It creates its own three tables when you run Set Up And Self Test once. There is no outside database to provision.
  • No API key and no model credential. Every decision is a rule against rows in a table, which is what makes the outcome auditable: you can read why a request was approved without asking a model what it was thinking.
  • A Discord webhook, used twice, to post the self-test result and to tell approvers a request is waiting. Swap both nodes for Slack, Gmail or nothing at all. Neither sits on the path that decides anything.
  • The workflow must be published for the two forms to accept submissions, because both entry points are Form triggers. Nothing else needs to be reachable from outside.

Customization

  • Set The Approval Policy holds the approval window in minutes and whether two different people are required. Both are stamped onto each request when it is raised, so a request read a year later can be judged against the rule that actually applied to it rather than today's policy.
  • approval_people is the list of who may approve. A row is a person, a secret and an active flag. Set active to no to suspend somebody without losing their history, or delete the row outright. There is no expiry on secrets, deliberately: an expiry that fired quietly would look like the control failing.
  • Whoever holds a secret is treated as that approver. This authenticates the secret, not the person, and that sentence is in the refusal text a user sees as well as in the documentation. If you need stronger identity, put the approval form behind your own single sign-on and pass the verified name through instead of a secret.
  • The approval window is a Wait node. A request that nobody finishes reviewing closes as expired, not rejected, and the outcome text says so. Raising it again is the intended path, because an expired request usually means nobody was around, not that the change was refused.
  • approval_event records every attempt, including refusals, with the rule that was broken. That table is the audit trail. Do not prune the refusals: an approval control with no record of what it stopped cannot demonstrate it was working.
  • The two demo approvers are prefixed DEMO ONLY and their secrets DEMO-ONLY-, so they are obvious in the table. They are still two working approvers until you delete them.
  • The requester's browser waits on a page until the window closes and then shows the outcome, which means a requester who closes the tab never sees it. The outcome is always written to approval_request either way. If your requesters will not sit and wait, add a Discord or Gmail node after Close The Request and tell them there. The node that tells the approvers is already there to copy.

Additional info

This is a control, not a notification. The difference matters: plenty of approval templates send a message and record whichever reply arrives first. This one refuses. If the same person responds twice, the second response is rejected and the reason is written to the audit table. If the person who raised the request tries to approve it, that is rejected too. Those two refusals are the entire point, and they are probes three and four of the self test that ships with the template.

The part worth reading twice is what happens when it cannot do its job. An empty approver table refuses everything, and the refusal says an empty list is not permission, it is a missing setup step. An unknown secret is refused. A request id that does not exist is refused. A request that has already closed cannot be reopened by approving it late. In every one of those cases the attempt is still written to the audit table, because a control that silently drops what it rejected cannot show anybody that it was working.

Counting is done on distinct people, never on rows. Three approval rows from one person is one approval, and there is a test that fails if that ever changes. The same applies to the window: the deadline is stamped on the request when it is raised, so changing the policy later does not retroactively reopen or close anything that was already in flight.

What it does not do is authenticate people. Whoever holds a secret is treated as that approver, and a forwarded secret is that person approving. That sentence appears in the refusal a user sees, on the canvas and here, because a control that quietly overstates what it proves is worse than no control at all. If you need real identity, put the approval form behind your own sign-on and pass the verified name through.

One thing about the requester's experience is worth knowing before you install it. Because the request path contains a Wait node, n8n parks the requester's browser on a waiting page and shows the outcome there when the window closes. A requester who closes the tab never sees that page. The outcome is always recorded in the request table, and the approvers are told on Discord, but the requester is not notified unless you add a node to do it. That is one node after Close The Request, and the node that messages the approvers is already in the workflow to copy.

Two more limits, stated plainly. The subject and detail of a request are stored as written and never redacted, so if they will contain personal data, run them through a redaction step first. And the check reads the audit table and then writes to it with no lock in between, so two submissions from the same person landing in the same instant could in principle both record. On a form a human clicks that is not a realistic path, but a script could do it, and it is the first thing I would fix if this were load-bearing.

Run Set Up And Self Test once and it does three things: creates the three tables with typed columns, loads the two demo approvers, and pushes eight known cases through the same rules a real approver hits. Six of those cases are refusals that must be refused. If someone later loosens a rule, the self test says so rather than the change being noticed the first time a change goes through on one signature.