v2 · Free · Open Source · Google Apps Script

Gmail Gatekeeper

A free script that filters unknown senders out of your inbox and lets you charge them to get through. Paid mail auto-delivers to your inbox with a star. Optional AI classifier for edge cases. Set up in 10 minutes. Runs forever on Google's servers.

What it does

Four layers. Use any combination.

1

Scored Filter

Every 15 minutes, each new sender is scored against your contacts, thread history, sender reputation, bulk-mail headers, calendar invites, role addresses, and transactional subject patterns. Three strictness presets — Strict, Balanced, Loose — decide the cutoff. Clearly-not-a-person mail (info@, order receipts, shipping, calendar invites) passes through automatically.

2

Smart Passthrough

Newsletters with unsubscribe headers and mail from 80+ common services (Amazon, FedEx, LinkedIn, your bank) pass straight through. The script also learns from sender-domain history — mail from a domain you've seen repeatedly gets trust over time, even if the sender isn't in your contacts.

3

Pay-to-Reach + Auto-Deliver

When a filtered sender pays your Stripe link, a webhook moves their message back to your inbox with a ⭐ star and adds them to a permanent allowlist. No manual dashboard-checking — the receipt reaches you automatically.

4

AI Classifier OPTIONAL

For messages right on the edge, the script can call Claude Haiku (bring your own Anthropic API key) to decide: real person or automated mail? Roughly $0.0001 per grey-zone email. Fully optional — flip the toggle in the configurator to enable.

Setup guide

12 steps, ~10 minutes. No coding knowledge needed.

1
Create a Stripe Payment Link

Skip if you only want the filter without the pay-to-reach feature.

  • Go to stripe.com and click Start now. Creating an account is free — Stripe just takes a small cut of each payment.
  • In the dashboard, search for Payment Links (or click More + → Payment Links).
  • Click + New → Products or subscriptions.
  • Click Add new product. Name it "Email Delivery Fee," "Coffee for My Inbox," whatever. Set any price — $5 is common. Choose One time.
  • Under options, turn on "Collect email address" — this is how the script matches a payment to a sender.
  • Click Create link and copy the URL (it looks like https://buy.stripe.com/abc123...).
2
Customize and download the script

Open the configurator. Pick a strictness preset (Balanced is a good default), paste your Stripe link, add your name and any trusted domains, then click Download customized script. This saves a gmail-gatekeeper.js file with your settings already baked in.

Prefer to edit code by hand? Download the raw script and change the constants at the top yourself.
3
Open Google Apps Script

Go to script.google.com and click New Project. Make sure you're signed into the Google account whose Gmail you want to filter.

4
Paste the script

Select all the default code (Cmd+A or Ctrl+A), delete it, and paste in the entire contents of your downloaded gmail-gatekeeper.js. Hit Cmd+S / Ctrl+S to save. Name the project whatever you like — "Gmail Gatekeeper" is fine.

5
Add the People API and Gmail API

In the left sidebar, click the + next to Services. Add People API (lets the script read your contacts) and Gmail API (lets it detect your send-as aliases so auto-replies come from the right address).

6
Run setup and authorize

In the toolbar, select setup from the function dropdown and click ▶ Run. Google will ask you to authorize — click through the permissions flow.

You'll see a scary "Google hasn't verified this app" screen. Click Advanced → "Go to [project name] (unsafe)". This is normal for personal scripts.
7
Verify the trigger

Click the ⏰ clock icon in the left sidebar. You should see a trigger for processNewMessages, set to run every 15 minutes. If it's there, the filter is live.

8
Deploy as a web app FOR AUTO-DELIVER

Skip if you don't want paid mail to auto-deliver back to your inbox.

  • Click Deploy (top right) → New deployment.
  • Click the gear icon → pick Web app.
  • Execute as: Me
  • Who has access: Anyone
  • Click Deploy. Copy the URL it gives you — you'll need it next.
"Anyone" sounds scary but it only means Stripe's servers can reach the webhook endpoint. The endpoint itself requires a secret token before it does anything, and it can only unarchive messages you've already received.
9
Set up the Stripe webhook FOR AUTO-DELIVER

In Apps Script, select getWebhookSetup from the function dropdown and run it. Open the Executions tab (left sidebar) to see the log — it prints a URL like:

https://script.google.com/macros/s/AKfycbx.../exec?t=RANDOM_TOKEN

Copy that whole URL. Then:

  • In Stripe, go to Developers → Webhooks → Add endpoint.
  • Paste the URL as the Endpoint URL.
  • Under Events to send, pick checkout.session.completed.
  • Click Add endpoint.

Test it: in the Stripe webhook's page, click Send test webhook → checkout.session.completed. Then check the Executions tab in Apps Script — you should see the request come through.

10
Add your Anthropic API key FOR AI FALLBACK

Skip if you didn't enable the AI fallback in the configurator.

  • Get an API key from console.anthropic.com. Add a small amount of credit ($5 is plenty — grey-zone emails cost roughly $0.0001 each).
  • In Apps Script, click ⚙ Project Settings (gear icon in left sidebar) → scroll to Script Properties → Add script property.
  • Property: ANTHROPIC_API_KEY. Value: your key (starts with sk-ant-). Click Save.
  • Back in the editor, select testAIClassifier from the dropdown and run it. Check the Executions tab — you should see two JSON results, one saying isPerson: true for the cold-outreach sample and isPerson: false for the no-reply sample.
11
Test with a friend

Before flipping auto-reply on for everyone:

  • Have a friend (whose address is not in your contacts and not on a whitelisted domain) email you.
  • Select testRun from the dropdown and run it. Their email should get the "Unknown Senders" label and archive. The Executions log shows exactly which signals fired and what the score was — useful for tuning.
  • Select testAutoReply and run it. Your friend receives the auto-reply with your Stripe link.
  • Ask your friend to complete the payment. Within seconds their original email should reappear in your inbox with a ⭐ star.
12
Go live

Happy with the test? In the configurator, toggle Send auto-reply with Stripe link on, download the script again, and paste the new version into Apps Script (saving overwrites the old config). Every filtered sender now gets the auto-reply. The filter itself runs whether auto-reply is on or not.

How it works

The scoring engine

Every 15 minutes, each recent sender earns "pass points" from a set of signals. If the total clears the strictness threshold, the message passes through untouched. If not, it's labeled Unknown Senders and archived out of your inbox. The label shows unread counts so you always know when something's there.

Signals and points:

Thresholds: Strict needs 50 points to pass, Balanced needs 30, Loose needs 15.

Every decision logs exactly which signals fired and the resulting score in the Apps Script Executions tab — so when something gets filtered that shouldn't, you can see why.

AI fallback for close calls

When enabled, any message with a score between 10 and the threshold (i.e., some safe signals but not enough to pass) gets sent to Claude Haiku with a prompt asking: is this a real person trying to initiate contact, or automated mail? The model returns structured JSON with a verdict, confidence, and reason. Results are cached per-sender so the same conversation never gets classified twice.

Typical cost is roughly $0.0001 per grey-zone email — most users spend well under $1/month. Requires an Anthropic API key stored in Script Properties (never in the script itself).

Common services whitelist

The script ships with 80+ pre-configured domains for services most people get email from. It matches subdomains automatically — so amazon.com in the list also covers email.amazon.com, shipment-tracking.amazon.com, and any other subdomain:

amazon.com fedex.com ups.com usps.com linkedin.com paypal.com stripe.com chase.com delta.com airbnb.com uber.com netflix.com spotify.com apple.com github.com substack.com zoom.us slack.com docusign.com + 60 more

You can add or remove domains from the COMMON_SERVICES array in the script at any time.

The auto-reply

When enabled, the script sends a single reply to each unknown sender with your Stripe link. It's smart about it:

You can fully customize the auto-reply by editing AUTO_REPLY_SUBJECT and AUTO_REPLY_BODY near the top of the script. Change the wording, the tone, the price — whatever you want. The ${STRIPE_LINK} variable automatically inserts your payment link. Use ${YOUR_NAME} to insert your name. Test any changes by running testAutoReply to send it to a friend before going live.

What to charge (and where the money goes)

The $5 default is just a suggestion. You can set any amount in Stripe and frame it however fits your style:

Tweak the auto-reply text to match whatever you choose. If you're donating the proceeds, say so — it makes the whole thing feel less like a toll booth and more like a feature.

When someone pays

If you deployed the script as a web app and added the Stripe webhook (steps 8 and 9), paid mail auto-delivers. Stripe fires a checkout.session.completed event the moment a payment succeeds; the webhook hits your script, which looks up the payer's email in the Unknown Senders label and moves the matching thread back to your inbox with a ⭐ star. The sender also gets added to a permanent allowlist so they never get filtered again.

End-to-end delivery is usually within a few seconds of payment.

If you didn't set up the webhook, delivery is manual: check your Stripe dashboard, see who paid, and search the Unknown Senders label for their address to rescue the thread yourself.

Adding legitimate senders

If a real person gets filtered, just add them to your Google Contacts. Next time the script runs, they'll pass right through. In Gmail, you can hover over a sender's name and click "Add to contacts" — takes two seconds. For an entire domain, add it to WHITELISTED_DOMAINS or COMMON_SERVICES.

Trigger frequency

The script defaults to running every 15 minutes, which is the sweet spot for most people. You can change this by editing the setup function — look for .everyMinutes(15) and change it to .everyMinutes(5) or .everyMinutes(10). After editing, run setup again to replace the old trigger.

Don't go lower than every 5 minutes. Google Apps Script has a daily execution quota — 90 minutes for regular Gmail accounts, 6 hours for Google Workspace. Each run hits both the Gmail and People API, so at very high frequencies you'll burn through the quota and the script will stop running partway through the day. At 15 minutes (96 runs/day), you're well within limits. At 5 minutes (288 runs/day), you're still fine but closer to the edge.

Apps Script's minimum interval is 1 minute, but at that frequency (1,440 runs/day) you'll almost certainly exceed your quota. Stick to 5–15 minutes.

Security & permissions

Core filtering runs entirely on Google's servers — no data leaves unless you opt in to the AI fallback or Stripe webhook.

Permission What it does Risk
Gmail (full) Search inbox, add/remove labels, archive, star, send replies Medium
People API Read-only access to your contacts and other-contacts Low
Gmail API Read your send-as aliases (read-only) Low
Script triggers Create the 15-minute recurring timer Low
Web app deploy Expose the Stripe webhook endpoint — only if auto-deliver is used Low
External fetch Call Claude Haiku API — only if AI fallback is enabled Low
The Gmail "full access" scope is broad because Google doesn't offer fine-grained permissions for Apps Script. The script only reads, labels, archives, stars, and sends — it never deletes anything. Keep the project private and don't share editor access.
The Stripe webhook URL contains a 32-character random token. Only requests with the correct token are processed, and the endpoint will only unarchive messages already sitting in your Unknown Senders label — it cannot be tricked into moving arbitrary mail into your inbox.
The AI fallback sends only the grey-zone message's sender, subject, and first 1,500 characters of body to the Anthropic API. Your contacts list and other mail are never transmitted. Turning the fallback off stops all external requests.

Common problems

Most issues happen during setup. Here's what to look for.

"Google hasn't verified this app" warning

This is normal for personal Apps Scripts. Click Advanced at the bottom left, then click "Go to [project name] (unsafe)". Google shows this because you haven't submitted the script for their app review process, which is only needed for public apps.

"Something went wrong" after clicking Advanced

Usually a multi-account session issue. Your browser is confused about which Google account to authorize. Fix: open an incognito window, sign into only the Google account you want, go to script.google.com, and try again from there.

"ReferenceError: People is not defined"

You haven't added the People API as a service. In the left sidebar, click the + next to Services, find People API, and click Add. Then run the function again.

Function dropdown only shows "myFunction"

The script didn't get pasted in correctly. Go back to the code editor, select all (Cmd+A), delete, and paste the full script again. Save. The dropdown should now show setup, testRun, testAutoReply, and others.

No trigger appears after running setup

Make sure you selected setup from the function dropdown before clicking Run. If you ran a different function, the trigger won't be created. Select setup, run it, then check the Triggers tab (clock icon in the left sidebar).

Script shows the wrong developer email in authorization

If you're logged into multiple Google accounts, the OAuth flow can pick the wrong one. The developer email shown doesn't affect functionality — the script runs under whichever account owns the Apps Script project. If it bothers you, use an incognito window with only one account signed in.

Emails from known senders are getting filtered

The sender isn't in your Google Contacts or "Other Contacts." Add them to your contacts and they'll be whitelisted on the next run. If it's an entire domain (like a company), add it to the WHITELISTED_DOMAINS or COMMON_SERVICES array in the script.

Newsletters are getting filtered

The newsletter probably doesn't include a List-Unsubscribe header (some small newsletters don't). Options: add the sender's domain to the Trusted domains list in the configurator, or lower the strictness preset. The Executions log will show exactly which signals did and didn't fire for that sender.

A legit person is getting filtered but I don't want to add them to contacts

Reply to them once — Gmail will automatically add their address to "Other Contacts," which the filter treats as a strong pass signal. Or add their specific address to Trusted email addresses in the configurator. Or have them pay the Stripe link — they'll get permanently allowlisted.

Stripe webhook says "unauthorized" in the Executions log

The URL you pasted into Stripe is missing the ?t=RANDOM_TOKEN query string. Run getWebhookSetup in Apps Script and copy the full URL it prints — the token is what authenticates Stripe to your script.

Stripe test webhook succeeds but no thread moves to inbox

Expected for test events — Stripe's test payload uses a dummy email ([email protected]) that won't match anything in your Unknown Senders label. To verify end-to-end, have a real friend pay $1 through your Payment Link using an address that's currently filtered. The thread should reappear in your inbox within seconds.

"LLM error 401" or "ANTHROPIC_API_KEY is not set"

Open Apps Script → ⚙ Project Settings (gear icon in left sidebar) → scroll to Script Properties. Add a property named exactly ANTHROPIC_API_KEY with your key as the value. Keys start with sk-ant-. Save, then run testAIClassifier to confirm it works.

I changed my config — do I need to redeploy the web app?

No. Config changes (toggles, strictness, whitelists, auto-reply text) only need a Save. Redeploying the web app is only needed if you want a new URL, which you almost never do.

Auto-reply isn't being sent

Check a few things: (1) Is AUTO_REPLY set to true? (2) Has the script already replied to that sender before? Each sender only gets one reply. (3) Is it a no-reply address or newsletter? The script skips those. (4) Check the Executions tab (left sidebar) for error logs.

Google Workspace admin blocks the script

If you're on Google Workspace (not regular Gmail), your admin may need to allow internal apps. Go to admin.google.com → Security → Access and data control → API controls → Settings. Make sure "Internal apps" is set to "Trust internal apps" and "Unconfigured third-party apps" is set to "Allow."

I want to reset and re-send auto-replies to everyone

Select resetRepliedSenders from the function dropdown and run it. This clears the list of senders who've already received an auto-reply, so the next time they email, they'll get one again.

Will it run if my computer is off?

Yes. Google Apps Script runs on Google's servers, not your computer. Once the trigger is set, it runs 24/7 regardless of whether your computer is on, off, or disconnected.

Will it work with email clients like Apple Mail or Outlook?

Yes. The script runs server-side on Gmail before your client syncs. Labels show up as folders in IMAP clients. You may need to subscribe to the "Unknown Senders" folder in your client's settings for it to appear in the sidebar.