Skip to content

Instantly share code, notes, and snippets.

@wodin
Last active January 23, 2026 13:59
Show Gist options
  • Select an option

  • Save wodin/f360f89fc87f3d1a848f9132cef91850 to your computer and use it in GitHub Desktop.

Select an option

Save wodin/f360f89fc87f3d1a848f9132cef91850 to your computer and use it in GitHub Desktop.
Google Workspace Inbound Gateway Setup (keep your own MX)

Google Workspace Account Conversion Warning

The Problem

When setting up Google Workspace, if the admin is signed into a personal @gmail.com account, Google may convert that personal account into the Workspace account rather than creating a fresh mailbox.

Symptoms

  • New Workspace user (e.g., user@yourdomain.com) shows years of old mail
  • Storage is already full (inherited from personal account)
  • "Contact information" during setup showed the personal Gmail address

What Happened

Google "upgraded" the personal account to become a managed Workspace account:

  • Personal account was renamed to the Workspace address
  • All mail, contacts, Drive files came with it
  • The original @gmail.com address may no longer exist

How to Verify

Check 1: Try Signing Into the Original Gmail

  1. Open incognito window
  2. Go to gmail.com
  3. Try signing in with the original @gmail.com address
  4. If it fails or redirects → conversion happened

Check 2: Admin Console User Details

  1. Go to admin.google.com
  2. Directory → Users → Click the user
  3. Look at "Account created" date
  4. If it's years old (not recent) → conversion happened

DO NOT Delete the User

If conversion happened, deleting the user in Admin console will delete years of personal email. Do not do this.

Safe Path Forward

  1. Leave the converted account alone for now
  2. Create a fresh test user (e.g., test@yourdomain.com) for mail forwarding experiments
  3. Research before acting on the converted account

Options for the Converted Account

Option A: Create Second User, Ignore the Problem

  • Use a fresh user for your actual needs
  • Converted account sits there, maybe used for admin only
  • Low risk, easy

Option B: Unmanage the Account

  • Google may allow "releasing" a converted account back to personal
  • Check Admin console for this option
  • Research current Google documentation first

Option C: Cancel Workspace Trial

  • RISKY - behavior unclear
  • Might convert account back to personal
  • Might delete everything
  • Contact Google support before trying this

Option D: Contact Google Support

  • Workspace trials include support
  • They can clarify exactly what will happen
  • Recommended before any destructive action

Lessons Learned

When setting up Google Workspace:

  1. Use an incognito window or sign out of all Google accounts first
  2. Don't use a personal Gmail as the initial admin if you want to keep it separate
  3. Create the admin account fresh with the new domain address

References

Google Workspace Day 1 Warning: What We Learned the Hard Way

Date: January 2026

TL;DR

Setting up a Google Workspace trial nearly destroyed a personal Gmail account with 211,736 emails. Everything was recoverable, but the experience was terrifying and poorly documented.

What Went Wrong

The Account Conversion Trap

When setting up Google Workspace while signed into a personal Gmail account, Google may silently convert that personal account into a Workspace-managed account. This happens without clear warning.

Symptoms:

  • Personal Gmail suddenly shows "Managed by yourdomain.com"
  • Years of personal email appears under a new Workspace address
  • Storage shows as full (inherited from personal account)
  • "Important changes to your account" message on login

The Documentation Problem

Google's docs say things like:

"After you cancel your Google Workspace subscription, your users' Google Workspace data will be deleted and can't be restored."

This is terrifying and wrong for converted personal accounts. The actual behavior:

  • Personal account data is preserved
  • Account converts back to personal/consumer status
  • Everything works fine

But you won't know that from reading the docs.

The Support Experience

  • Chatbot: Gave contradictory answers, didn't understand the scenario
  • First support agent: Suggested deleting the user (would have deleted all data!)
  • Specialist team: Finally gave correct instructions
  • Ticket number: Essential for documentation - get one

How to Avoid This

Before Starting a Workspace Trial

  1. Use an incognito/private window
  2. Sign out of ALL Google accounts first
  3. Never set up Workspace while signed into a personal Gmail
  4. Create only fresh users - never convert existing accounts

If You Already Converted an Account

See: google-workspace-unmerge-confirmed-process.md in this gist

Short version:

  1. Do a Google Takeout backup first
  2. Create a new Super Admin user
  3. Remove the converted account from Workspace (choose "Do not Transfer Data")
  4. Account reverts to personal with all data intact

The "Activate Gmail" Mystery

After recovery, Gmail may show an "Activate Gmail" or "Set up Gmail to receive emails" banner.

What it does: Nothing useful. Clicking it tries to open the Workspace Admin console (which doesn't apply to a personal account).

What to do: Click it to make the banner go away, then close any admin.google.com tabs it opens. Or just click Dismiss.

Email already works - the banner is just stale UI from the conversion.

Timeline of Our Experience

Time Event
0:00 Started Workspace trial
0:05 Noticed personal Gmail was "converted"
0:10 Panic - 211,736 emails at risk
0:30 Chatbot giving mixed messages
1:00 Started Google Takeout (60GB backup)
1:30 Contacted human support
1:45 Escalated to specialist team
2:00 Got confirmed process from specialist
2:15 Successfully removed account from Workspace
2:20 All data preserved, email working
2:30 Dismissed mysterious "Activate Gmail" banner

Key Lessons

  1. Google's Workspace onboarding can silently convert personal accounts - This is a known issue ("I can't count how many customers contacting us with this issue" - Google Support)

  2. The documentation is wrong/misleading - Don't trust warnings about "permanent deletion" for converted accounts

  3. Always do Takeout backup first - Even when support says you don't need it

  4. Get a human specialist - The chatbot and first-tier support may not understand converted accounts

  5. Get a ticket number - Document everything in case you need to escalate

  6. Day 1 of Workspace trial may be spent recovering from Workspace - Budget your time accordingly

References

Google Workspace Inbound Gateway Setup

Goal

Set up Google Workspace to receive forwarded mail from our mail server WITHOUT changing MX records.

Prerequisites

  • Google Workspace account created for intelms.com
  • Domain verified in Google Workspace
  • Test user created (e.g., filter@intelms.com)

Steps in Google Admin Console

1. Access Gmail Settings

admin.google.com → Apps → Google Workspace → Gmail

2. Configure Inbound Gateway

Gmail → Spam, Phishing and Malware → Inbound gateway

Click "Configure" or "Add another" and set:

  • Gateway IPs: Add our mail server's public IP address
  • Automatically detect external IP: Enable this (recommended)
  • Reject all mail not from gateway IPs: Leave DISABLED for testing
  • Require TLS for connections from the email gateways: Enable if our server supports it
  • Message is spam if the following header regexp matches: Leave empty

3. Save and Wait

Changes can take up to 24 hours to propagate, but usually faster.

What This Does

  • Tells Gmail our server is a trusted forwarder
  • Gmail checks SPF/DKIM against the ORIGINAL sender, not our server
  • User filters trigger normally (because it's real SMTP delivery)
  • Spam filtering works against original sender

Testing

  1. Send test email from external address to user@intelms.com
  2. Check Gmail inbox (not spam folder)
  3. Verify headers show correct SPF/DKIM results

Troubleshooting

  • If mail goes to spam: Check inbound gateway IP is correct
  • If mail rejected: Ensure Google Workspace accepts mail for the domain
  • Check headers for X-Gm-Spam and X-Gm-Phishy values

References

Google Workspace Safe Cancellation Process

For Domain-Verified Subscriptions (with converted personal accounts)

If you set up Google Workspace and a personal Google account got converted to a managed account, follow this two-step process to safely revert it.

How to Know If This Applies

  • You verified domain ownership via DNS TXT record
  • A personal account now shows as "Managed by yourdomain.com"
  • You want to restore the account to a personal, unmanaged state

The Two-Step Process

Step 1: Cancel the Subscription

  1. Sign in to Admin console: admin.google.com
  2. Go to Menu → Billing → Subscriptions
  3. Click your subscription → More → Cancel Subscription
  4. Follow prompts

After this step: User accounts still exist, paid services are cancelled.

Step 2: Delete the Organization's Google Account

This is the critical step that converts managed accounts back to personal.

  1. Go to Menu → Account → Account settings → Account management
  2. Click Delete Account
  3. Follow prompts to confirm

After this step: User account is converted back to a standard, unmanaged Google Account with all data retained.

Important Notes

  • The "data will be deleted" warning in Step 1 is about Workspace-specific data
  • The organization deletion in Step 2 is what actually "releases" accounts
  • Personal account data is preserved through this process

Official Documentation

Recommendation

Test this process with a throwaway account first before doing it with important data.

Confirmed Process: Unmerge Personal Gmail from Google Workspace

Source: Google Workspace Support (Ticket #67121438, January 2026)

Status: CONFIRMED WORKING

The Problem

When setting up Google Workspace, a personal Gmail account can get "converted" to a managed Workspace account. This document describes the confirmed process to restore it to a personal account.

Symptoms of a Converted Account

  • Personal Gmail shows as "Managed by yourdomain.com"
  • Old personal emails appear in the Workspace account
  • Profile shows new Workspace address (e.g., user@yourdomain.com) but contains years of old mail
  • "Important changes to your account" message appeared during login

Confirmed Solution (from Google Support)

Prerequisites

  1. Complete a Google Takeout backup first - Recommended even though support says it's not needed
  2. Note your ticket number if you contact support (for documentation if something goes wrong)

Step 1: Create a New Super Admin

  1. In Admin console (admin.google.com), create a new user
  2. Grant Super Admin privileges to this new user
  3. Sign out and sign back in as the new Super Admin

Step 2: Remove Admin Role from Converted Account

  1. Go to Directory → Users
  2. Find the converted account (e.g., filter@yourdomain.com)
  3. Remove the Super Admin role from this account

Step 3: Remove the User from Workspace

  1. Click on the converted user account
  2. Click REMOVE USER
  3. Choose "Do not Transfer Data"

Important: Support confirmed: "All data from personal emails are safe and secured."

Step 4: Complete the Conversion

After removal, when logging into the original Gmail address, you'll see:

"Your account has been removed from an organisation" "Your admin has converted your managed Google Account to a consumer account."

If you used Google Workspace with Gmail: You may see a prompt to "add Gmail" to your account within 30 days. Click "Next", agree to terms and conditions.

Step 5: Verify

  1. Sign into the original personal Gmail address (e.g., bluebirdsupport@gmail.com)
  2. Follow any prompts to re-enable Gmail / agree to terms
  3. Verify all emails and data are present
  4. Test sending an email TO the account to confirm receiving works

Confirmed Outcome

Result: 211,736 emails preserved. Sending and receiving works immediately.

You may still see an "Activate Gmail" / "Set up Gmail to receive emails" banner. This can be ignored or dismissed - email works without clicking it.

Safety Net: 20-Day Restore Window

Support confirmed: "Once you deleted the user, you have 20 days to restore the user and its data."

If something goes wrong, you can restore within this window from Admin console: Directory → Users → "Add a filter" → Show deleted users

What Support Said

"I can't count how many customers contacting us with this issue."

The documentation is misleading. The warnings about "permanent deletion" refer to Workspace-specific data, not the original personal account data.

Warnings

  • Always do a Takeout backup first - Even with support confirmation, protect yourself
  • The docs are scary and wrong - They warn about permanent deletion but that's not what happens for converted personal accounts
  • Get a ticket number - If you contact support, document the conversation
  • Ignore "Activate Gmail" banner - Email works without it

Summary

  1. Personal Gmail got converted to Workspace-managed account during trial setup
  2. Created new Super Admin, removed converted account from Workspace with "Do not Transfer Data"
  3. Account converted back to personal consumer account
  4. All data preserved, email working immediately

The support agent was right. The documentation was terrifying and wrong.

References

Postfix Configuration for Google Workspace Forwarding

Forward specific users' mail to Google Workspace while keeping your server as the MX.

Scenario

  • Your server is the primary MX for the domain
  • Some addresses are local mailboxes (LDAP-based virtual mailboxes)
  • Some addresses are aliases to local mailboxes (e.g., user@intelms.comuser@bluebird.co.za)
  • Some addresses should forward to Google Workspace

Key Insight: Aliases Flow Through

If you have:

  • user@intelms.com aliased to user@bluebird.co.za
  • user@bluebird.co.za in the transport map → Google

Then mail to user@intelms.com will:

  1. Be rewritten to user@bluebird.co.za (alias)
  2. Hit the transport map
  3. Go to Google Workspace

You only need to add bluebird.co.za addresses to the transport map — aliases automatically follow.

Configuration

1. For domains with ONLY aliases (e.g., intelms.com)

If you have addresses that exist ONLY in Google Workspace (no local alias), move the domain to relay_domains.

In /etc/postfix/main.cf:

# Remove intelms.com from virtual_mailbox_domains
virtual_mailbox_domains = bluebird.co.za radiology.co.za intelms.com.au intelms.ca

# Add it to relay_domains instead
relay_domains = intelms.com

Configure recipient validation to prevent accepting garbage:

relay_recipient_maps = hash:/etc/postfix/intelms.com hash:/etc/postfix/relay_recipients
  • First file: existing aliases (validates aliased addresses)
  • Second file: Workspace-only addresses with no local alias

Create /etc/postfix/relay_recipients:

# Addresses that forward to Google Workspace (no local alias)
bbfilter@intelms.com    OK

2. For domains with LDAP mailboxes (e.g., bluebird.co.za)

No changes needed to domain configuration. Addresses already exist in LDAP for recipient validation. Just add them to the transport map.

3. Configure transport map (IMPORTANT: Order matters!)

The transport map must be FIRST in the list so it's checked before LDAP transport lookups.

In /etc/postfix/main.cf:

transport_maps = hash:/etc/postfix/transport
        proxy:ldap:/etc/postfix/ldap-transport-exceptions.cf
        proxy:ldap:/etc/postfix/ldap-transport-reply.cf
        proxy:ldap:/etc/postfix/ldap-transport.cf

In /etc/postfix/transport:

# Forward to Google Workspace
# Only need bluebird.co.za addresses - aliases follow automatically
user@bluebird.co.za         smtp:[aspmx.l.google.com]:25
anotheruser@bluebird.co.za  smtp:[aspmx.l.google.com]:25

# For Workspace-only addresses (no local alias)
bbfilter@intelms.com        smtp:[aspmx.l.google.com]:25

# Existing entries for other routing...
hhsys.org       smtp:oregon.intelms.com
.hhsys.org      smtp:oregon.intelms.com
# ... etc

# NO CATCHALL! (see below)

IMPORTANT: Remove any * : catchall entry!

If you previously had * : at the end of the transport file, remove it. With the hash file now FIRST in transport_maps, a catchall would match everything and prevent LDAP lookups from ever being consulted.

Without catchall:

  1. Hash file checked → specific matches used
  2. No match → falls through to LDAP
  3. LDAP determines local delivery (dovecot:, bbprocmail:, error:, etc.)

4. Build hash files and reload

postmap /etc/postfix/relay_recipients
postmap /etc/postfix/transport
postfix reload

How It Works

Address Flow
user@intelms.com (has alias) Alias → user@bluebird.co.za → transport map → Google
bbfilter@intelms.com (no alias) relay_recipient_maps OK → transport map → Google
random@intelms.com (neither) Rejected
user@bluebird.co.za (in transport) LDAP validates → transport map → Google
localuser@bluebird.co.za (not in transport) LDAP validates → LDAP transport → local delivery

Why relay_domains for intelms.com?

With virtual_mailbox_domains, Postfix requires every recipient to exist in either:

  • virtual_alias_maps (rewritten to another address)
  • virtual_mailbox_maps (local mailbox)

Addresses like bbfilter@intelms.com (Workspace-only, no local alias) have neither, so they'd be rejected.

With relay_domains, Postfix accepts addresses validated by relay_recipient_maps and routes them according to transport_maps.

Notes

  • Square brackets [aspmx.l.google.com] = use A record directly, skip MX lookup
  • Port 25 = standard SMTP (Google accepts server-to-server)
  • Skipping MX lookup is essential since YOUR server is the MX — without brackets, Postfix would loop
  • Transport map order matters — first match wins, so put the hash file before LDAP lookups
  • No catchall in hash file — a * : entry would prevent LDAP fallthrough

Testing

# Verify transport map entry
postmap -q "user@bluebird.co.za" hash:/etc/postfix/transport
# Should return: smtp:[aspmx.l.google.com]:25

# Verify relay recipient (for intelms.com addresses without aliases)
postmap -q "bbfilter@intelms.com" hash:/etc/postfix/relay_recipients
# Should return: OK

# Send test and watch logs
echo "Test" | mail -s "Transport test" user@bluebird.co.za
tail -f /var/log/mail.log

You should see Postfix connecting to aspmx.l.google.com instead of delivering locally.

Filtering by Original Recipient in Gmail

If multiple users' mail is forwarded to a single Workspace account (e.g., bbfilter@intelms.com), you'll need a way to filter/forward based on the original recipient.

The Problem

Gmail filters can only match on specific headers:

  • To/Cc/From/Subject
  • Message-ID
  • List-ID (mailing lists)
  • Delivered-To

The envelope recipient isn't directly filterable, and To/Cc won't contain Bcc addresses.

Solution: Add Delivered-To Header via Procmail

Gmail supports filtering on Delivered-To via deliveredto:user@example.com in filter criteria.

Use procmail to add the header before forwarding to Google:

1. Ensure procmail.sh passes RECIPIENT

The bbprocmail transport in master.cf calls a wrapper script:

bbprocmail unix -       n       n       -       16      pipe
  flags=DRqh user=virtual:virtual
  argv=/usr/local/scripts/procmail.sh ${nexthop} ${sender}

Important: The ${nexthop} is the mailbox path (e.g., bluebird.co.za/support7local), not the email address. This is because the transport map uses the path format for HOME directory lookup:

support7local@bluebird.co.za  bbprocmail:bluebird.co.za/support7local

To get the actual email address for the Delivered-To header, choose one of these approaches:

Option A: Pass ${recipient} from Postfix (cleanest)

Modify master.cf to pass the recipient as a third argument:

bbprocmail unix -       n       n       -       16      pipe
  flags=DRqh user=virtual:virtual
  argv=/usr/local/scripts/procmail.sh ${nexthop} ${sender} ${recipient}

Then in procmail.sh:

#!/bin/sh
BASE=/virtual/mail

# Accept 2 args (backward compat) or 3 args (with recipient)
[ $# -ge 2 -a $# -le 3 ] || {
    logger -p mail.err -t procmail.sh "Bad args: $@"
    exit 64
}

HOME="${BASE}/$1"

cd "${HOME}" 2>/dev/null || {
    logger -p mail.err -t procmail.sh "Cannot change to ${HOME}. sender=$2"
    exit 64
}

logger -p mail.info -t procmail.sh "HOME=$HOME, recipient=$3, sender=$2"
/usr/bin/procmail RECIPIENT="$3" -m HOME=$HOME .procmailrc "$2"
exit $?

Option B: Derive email from mailbox path (no master.cf change)

Convert bluebird.co.za/support7local to support7local@bluebird.co.za in the script:

#!/bin/sh
BASE=/virtual/mail

[ $# -eq 2 ] || {
    logger -p mail.err -t procmail.sh "Bad args: $@"
    exit 64
}

HOME="${BASE}/$1"

cd "${HOME}" 2>/dev/null || {
    logger -p mail.err -t procmail.sh "Cannot change to ${HOME}. sender=$2"
    exit 64
}

# Convert mailbox path (bluebird.co.za/support7local) to email (support7local@bluebird.co.za)
DOMAIN=$(echo "$1" | cut -d/ -f1)
USER=$(echo "$1" | cut -d/ -f2)
EMAIL_RECIPIENT="${USER}@${DOMAIN}"

logger -p mail.info -t procmail.sh "HOME=$HOME, recipient=$EMAIL_RECIPIENT, sender=$2"
/usr/bin/procmail RECIPIENT="$EMAIL_RECIPIENT" -m HOME=$HOME .procmailrc "$2"
exit $?

Note on positional arguments: The "$2" (sender) passed to procmail becomes $1 inside the .procmailrc. So:

  • $RECIPIENT = recipient email address (from variable assignment)
  • $1 = sender (from command line argument)

2. Create a procmail recipe (in the user's .procmailrc):

# Capture sender from positional arg (for envelope sender preservation)
SENDER="$1"

# Add Delivered-To header with original recipient
# Use -I to replace any existing Delivered-To header
:0 fhw
| formail -I "Delivered-To: $RECIPIENT"

# Forward to Google Workspace, preserving original envelope sender
# Using sendmail -f ensures bounces go to the original sender
:0
| /usr/sbin/sendmail -oi -f "$SENDER" bbfilter@intelms.com

Why sendmail instead of !? The -f "$SENDER" flag preserves the original envelope sender. Without this, bounces would go to virtual@camissa.bluebird.co.za instead of the original sender.

3. Update transport map to route through procmail:

# In /etc/postfix/transport
# Route staff addresses through procmail (adds Delivered-To, then forwards)
# Note: The part after bbprocmail: is the mailbox path (domain/user), not email address
user@bluebird.co.za         bbprocmail:bluebird.co.za/user
anotheruser@bluebird.co.za  bbprocmail:bluebird.co.za/anotheruser

3. Create Gmail filters in the bbfilter@intelms.com account:

  • Matches: deliveredto:user@bluebird.co.za
  • Action: Forward to user@gmail.com (or apply label, etc.)

How It Works

  1. Mail arrives for user@bluebird.co.za
  2. Transport map routes to bbprocmail:user@bluebird.co.za
  3. Procmail adds Delivered-To: user@bluebird.co.za header
  4. Procmail forwards to bbfilter@intelms.com
  5. Google receives mail with Delivered-To header intact
  6. Gmail filter matches deliveredto:user@bluebird.co.za
  7. Filter forwards to user's personal Gmail

Alternative: Direct SMTP (No Filtering Needed)

If each user has their own Workspace account, skip procmail and route directly:

user@bluebird.co.za    smtp:[aspmx.l.google.com]:25

The procmail approach is only needed when funneling multiple users through a single Workspace account.

SPF/DMARC Verification: It Works!

A common concern with forwarding setups is SPF failure. Testing confirms the inbound gateway + Gmail filter forwarding chain preserves authentication correctly.

Test Setup

Send a spoofed email through your mail server with a fake Received: header to simulate external mail. See spf_test_gist.py in this gist for the full script.

Run it from your mail server (the inbound gateway):

python2 spf_test_gist.py

The script:

  • Connects directly to aspmx.l.google.com
  • Adds a fake Received: header with a spoofed external IP
  • Adds a Delivered-To: header for Gmail filter matching
  • Uses different Header To vs envelope RCPT TO (realistic scenario)

Results

At bbfilter@intelms.com (Workspace):

Received-SPF: pass (google.com: domain of sender@example.com
              designates 192.0.2.42 as permitted sender) client-ip=192.0.2.42

The inbound gateway correctly:

  1. Trusted camissa (the connecting IP)
  2. Parsed the Received: header to find the original sender IP
  3. Checked SPF against that IP, not camissa's

At personal Gmail (after filter forwarding):

ARC-Authentication-Results: i=3; mx.google.com;
   arc=pass (i=2 spf=pass spfdomain=lantic.net);
   spf=pass (google.com: domain of bbfilter+caf_=bluebirdsup7=gmail.com@intelms.com
             designates 209.85.220.41 as permitted sender)
   dmarc=fail (p=NONE sp=NONE dis=NONE arc=pass) header.from=lantic.net

How It Works

ARC (Authenticated Received Chain) preserves authentication through forwarding:

  1. i=1: camissa → bbfilter — SPF pass for original sender (196.3.177.42)
  2. i=2: bbfilter stores original auth in ARC seal
  3. i=3: bbfilter → personal Gmail
    • SRS rewrite: Envelope sender becomes bbfilter+caf_=recipient=gmail.com@intelms.com
    • SPF passes for intelms.com against Google's IP (209.85.220.41)
    • DMARC fails (header From doesn't match envelope domain)
    • But ARC passes — the chain proves original authentication was valid
    • Message delivered because arc=pass overrides DMARC failure

Key Takeaway

The forwarding architecture is sound for authentication:

  • SPF passes at each hop (original sender, then SRS-rewritten sender)
  • DMARC may fail on final hop, but ARC preserves the trust chain
  • Gmail trusts its own ARC signatures through the forwarding path

The only remaining issue is the UX annoyance of messages arriving as "read" (see below).

Known Limitation: Forwarded Mail Arrives as "Read"

When using Gmail filter forwarding from the Workspace account (e.g., bbfilter@intelms.com) to personal Gmail accounts, messages arrive marked as read in the destination inbox.

Why This Happens

Gmail's filter "Forward to" action marks messages as processed. The forwarded copy inherits this state and arrives read at the destination. This is Gmail's design, not a configuration issue.

Why Not Forward Directly from the Mail Server?

Forwarding directly from the mail server to personal Gmail breaks SPF:

mail server → personal Gmail
     ↑
  SPF FAIL (server not authorized to send for original sender's domain)

The Workspace inbound gateway configuration tells Gmail to trust the mail server as a forwarder and check SPF against the original sender. Personal Gmail doesn't have inbound gateway settings — that's a Workspace admin feature.

So the flow must go through Workspace:

mail server → Workspace (gateway trust) → filter forward → personal Gmail
                                                ↑
                                          (arrives read)

Workarounds

Option Pros Cons
Accept it No complexity Messages arrive read
Each user gets own Workspace account Direct delivery, arrives unread ~$7.20/user/month
Label + Apps Script Free Complex setup per user (see below)
Access bbfilter via delegation Free, proper unread status Different workflow (shared inbox)

Label + Apps Script Workaround

If you want messages to appear unread in personal Gmail without paying for per-user Workspace accounts:

1. In bbfilter@intelms.com — create filter:

  • Matches: deliveredto:user@bluebird.co.za
  • Actions: Forward to user@gmail.com, Apply label pending-unread

2. In user@gmail.com — create filter:

  • Matches: messages from bbfilter (or with specific header)
  • Actions: Apply label needs-unread

3. In user@gmail.com — create Apps Script:

function markAsUnread() {
  var label = GmailApp.getUserLabelByName('needs-unread');
  if (!label) return;

  var threads = label.getThreads();
  threads.forEach(function(thread) {
    thread.markUnread();
    thread.removeLabel(label);
  });
}

4. Add trigger — run every 1-5 minutes.

Trade-offs:

  • Brief window where messages appear read (until script runs)
  • Each user must set up their own filter, label, script, and trigger
  • Script quota limits (100 emails/day consumer, 1500/day Workspace)

Recommendation

For a small number of users, "accept it" or "each user gets Workspace account" are the simplest options. The label+script approach works but requires per-user setup and maintenance.

#!/usr/bin/env python2
"""
Test SPF via Inbound Gateway by faking a Received header.
Simulates external mail flowing through your mail server to Google Workspace.
Run this FROM your mail server (the inbound gateway) to test SPF handling.
Usage:
python2 spf_test.py
"""
import smtplib
from email.mime.text import MIMEText
import socket
import uuid
from datetime import datetime
# Configuration
SMTP_SERVER = 'aspmx.l.google.com'
SMTP_PORT = 25
# Envelope sender - this is what SPF checks against
# Use an address whose SPF record includes the FAKE_EXTERNAL_IP below
ENVELOPE_FROM = 'sender@example.com'
# Header From (what recipient sees in their mail client)
HEADER_FROM = 'Sender Name <sender@example.com>'
# Header To (display only - doesn't affect routing)
HEADER_TO = 'Recipient Name <support7@bluebird.co.za>'
# Envelope recipient - where the mail actually goes
RCPT_TO = 'bbfilter@intelms.com'
# Delivered-To header - used for Gmail filter matching (deliveredto: search)
DELIVERED_TO = 'support7local@bluebird.co.za'
# Fake external IP - must be in ENVELOPE_FROM domain's SPF record for SPF to pass
# Example: For lantic.net, use an IP from Vox's SPF (196.3.177.0/24)
FAKE_EXTERNAL_IP = '192.0.2.42'
FAKE_EXTERNAL_HOST = 'mail.example.com'
# Build message
msg = MIMEText(
'SPF test - spoofing %s from IP %s\n'
'Delivered-To: %s\n'
'Should PASS SPF if IP is in sender domain\'s SPF record.'
% (ENVELOPE_FROM, FAKE_EXTERNAL_IP, DELIVERED_TO)
)
msg['Subject'] = 'SPF Forward Test - %s via %s' % (ENVELOPE_FROM, FAKE_EXTERNAL_IP)
msg['From'] = HEADER_FROM
msg['To'] = HEADER_TO
msg['Message-ID'] = '<%s@%s>' % (uuid.uuid4(), FAKE_EXTERNAL_HOST)
# Prepend headers: Delivered-To first, then fake Received
# Format matches what Postfix generates
timestamp = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S +0000 (UTC)')
fake_received = (
'from %s (%s [%s])\r\n'
'\tby camissa.bluebird.co.za (Postfix) with ESMTP id FAKE12345\r\n'
'\tfor <%s>; %s'
% (FAKE_EXTERNAL_HOST, FAKE_EXTERNAL_HOST, FAKE_EXTERNAL_IP, RCPT_TO, timestamp)
)
# Build raw message with custom headers prepended
raw_msg = 'Delivered-To: %s\r\nReceived: %s\r\n%s' % (
DELIVERED_TO, fake_received, msg.as_string()
)
print 'Connecting to %s:%d...' % (SMTP_SERVER, SMTP_PORT)
socket.setdefaulttimeout(30)
smtp = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
smtp.set_debuglevel(1)
print '\nSending EHLO as camissa...'
smtp.ehlo('camissa.bluebird.co.za')
print '\nSending mail with fake Received header...'
print ' Fake origin: %s [%s]' % (FAKE_EXTERNAL_HOST, FAKE_EXTERNAL_IP)
print ' MAIL FROM: <%s>' % ENVELOPE_FROM
print ' RCPT TO: <%s>' % RCPT_TO
print ' Delivered-To: %s' % DELIVERED_TO
print ' Header To: %s' % HEADER_TO
try:
smtp.sendmail(ENVELOPE_FROM, [RCPT_TO], raw_msg)
print '\nSent! Check Authentication-Results header in received message.'
except smtplib.SMTPRecipientsRefused, e:
print '\nRecipient refused: %s' % e
except smtplib.SMTPSenderRefused, e:
print '\nSender refused: %s' % e
smtp.quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment