Email Migration Scripts: How to Move Messages, Filters and Contacts Off Google (with Examples)
Concrete scripts and manifests to export mail, convert Gmail filters to Sieve, and import contacts — with imapsync, Python examples and 2026 migration guidance.
Stop paying in surprise — move your mailchain off Gmail with reproducible scripts
Hook: If new Google policies in 2026 have you re-evaluating where your email lives, this guide gives you the exact scripts, manifests and conversion recipes to export messages, contacts and filters — and import them into common alternatives — with a clear plan for verification and pitfalls to avoid.
Executive summary (most important first)
This guide shows three practical, production-ready migration paths and the utility scripts that make them repeatable and automatable for teams and power users:
- Direct IMAP-to-IMAP transfer (recommended): imapsync examples with OAuth2 and Docker for large mailboxes.
- Archive-first migration: export via Google Takeout or Gmail API to mbox/maildir, then sync to your target via mbsync or direct IMAP inject.
- Filters & contacts migration: a small Python toolset to export Gmail filters via the Gmail API, convert to Sieve (best-effort), and export contacts to vCard/CSV using the People API.
You'll get YAML manifests, sample scripts, and mapping rules so you can automate and audit the whole transition.
Why move now (2026 context)
Late 2025 — early 2026 saw major email platform shifts: Google announced changes to Gmail personalization and address policies that accelerated migrations for privacy-conscious teams and companies. Meanwhile, demand for privacy-first or self-hosted mail (Fastmail, Proton, Dovecot) rose, and IMAP/Sieve-based toolchains remain the most portable choice for developers and admins.
In short: if you need to reduce vendor lock-in, avoid surprise product changes, or host mail on infrastructure you control, migrating now with repeatable scripts is the right move.
Before you start: checklist
- Inventory accounts, labels, total message counts, aliases, and forwarding rules.
- Credentials — obtain OAuth2 client credentials for the Gmail API or an app-password/OAuth token for IMAP access. For Workspace, consider a service account with domain-wide delegation.
- Backups — always create a full MBOX export via Google Takeout or the Gmail API.
- Test account — perform a complete dry-run using a representative mailbox before migrating production accounts.
- Rate limits & throttling — plan for API/IMAP limits; split large mailboxes into time windows.
1) Direct IMAP migration (imapsync) — the fastest repeatable path
imapsync is the industry-standard tool to copy mail from one IMAP server to another while preserving flags and internal dates. Use it when you want minimal downtime and a streaming transfer.
Recommended: run imapsync in Docker with OAuth2 tokens
Since 2022+, Google prefers OAuth2 for IMAP access. The command below shows a typical Docker-run imapsync invocation using credentials stored as environment variables (replace placeholders):
<!-- language: text -->
docker run --rm -it --name imapsync \
-e IMAPSYNC_OAUTH1_CLIENT_ID='YOUR_OAUTH_CLIENT_ID' \
-e IMAPSYNC_OAUTH1_CLIENT_SECRET='YOUR_OAUTH_CLIENT_SECRET' \
-v $PWD/logs:/var/log/imapsync \
ghcr.io/imapsync/imapsync:latest \
--host1 imap.gmail.com --user1 user@gmail.com --authmech1 XOAUTH2 --oauth2_token1 "$GMAIL_OAUTH_TOKEN" --ssl1 \
--host2 imap.target.com --user2 user@yourdomain.com --password2 'TARGET_PASSWORD' --ssl2 \
--syncinternaldates --skipheader 'X-GM-RAW' --addheader --subscribeall --folderrec "INBOX" --folderrec "Label*"
Key flags explained:
- --authmech1 XOAUTH2: Use OAuth2 for Gmail IMAP.
- --syncinternaldates: Preserve original message dates.
- --addheader: Add a header that shows original UID if you want to audit duplicates.
- --subscribeall: Ensure folders on the destination are subscribed.
For very large mailboxes: parallelize and checkpoint
Split by top-level labels or date ranges and run multiple imapsync jobs. Example wrapper to split by year:
<!-- language: bash -->
for year in 2018 2019 2020 2021 2022 2023 2024 2025; do
docker run --rm ghcr.io/imapsync/imapsync:latest \
--host1 imap.gmail.com --user1 user@gmail.com --authmech1 XOAUTH2 --oauth2_token1 "$GMAIL_OAUTH_TOKEN" --ssl1 \
--host2 imap.target.com --user2 user@yourdomain.com --password2 'TARGET_PASSWORD' --ssl2 \
--search 'after ${year}-01-01 before ${year}-12-31' --syncinternaldates --addheader &
done
wait
Pitfalls (IMAP transfer)
- Gmail labels map to IMAP folders. Messages with multiple labels will be copied into multiple folders — this is expected. You can dedupe by UID after transfer if necessary.
- Forwarding and filters are not migrated by imapsync; handle them separately.
- OAuth2 tokens expire — implement refresh flows or use long-lived service-account tokens for Workspace.
2) Archive-first: Google Takeout / Gmail API -> maildir -> mbsync
If you prefer a file-based canonical archive or need to keep a local copy, export to MBOX (Google Takeout) or fetch raw messages via the Gmail API and convert to Maildir for import.
Takeout route
- Request Mail export from Google Takeout (produces one or several MBOX files).
- Convert MBOX to Maildir: use mb2md or python's mailbox module.
- Push maildir to IMAP destination using mbsync/ isync with a short config.
<!-- example mbsync config --> IMAPAccount target Host imap.target.com User user@yourdomain.com PassCmd "gpg --quiet --for-your-eyes-only --no-tty -d ~/.mail/pass.gpg" IMAPStore target-remote Account target MaildirStore local-maildir Path ~/mail/user/\ Inbox ~/mail/user/INBOX Channel user-sync Master :local-maildir: Slave :target-remote: Create Slave Sync Pull
Gmail API route (for automation & audit)
Use the Gmail API to list messages then fetch the raw RFC822 payload. The snippet below uses Python and google-auth to download message drafts to mbox or maildir.
<!-- language: python -->
# gmail_export.py (abridged)
from google.oauth2 import service_account
from googleapiclient.discovery import build
import base64, email, mailbox
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
creds = service_account.Credentials.from_service_account_file('sa.json', scopes=SCOPES)
delegated = creds.with_subject('user@domain.com')
service = build('gmail','v1',credentials=delegated)
mbox = mailbox.mbox('export.mbox')
res = service.users().messages().list(userId='me', q=None).execute()
for m in res.get('messages', []):
msg = service.users().messages().get(userId='me', id=m['id'], format='raw').execute()
raw = base64.urlsafe_b64decode(msg['raw'].encode('ASCII'))
mbox.add(email.message_from_bytes(raw))
mbox.flush()
Note: For consumer Gmail accounts you must use OAuth consent; for Workspace, service accounts with delegation simplify automation.
3) Filters: export, convert and import (Gmail -> Sieve)
Gmail filters are powerful and use Gmail-specific operators (has:attachment, category, query syntax). There is no perfect mapping to Sieve, but we can provide a best-effort converter that handles the common cases: header matches, from/to/subject, size, and attachments (best-effort).
Export Gmail filters
Two practical ways:
- Gmail web UI: Settings → Filters and Blocked Addresses → Export — produces an XML file per filter.
- Gmail API:
users.settings.filtersendpoint to list filters programmatically.
Example: Python converter from Gmail filters (API) to Sieve rules
<!-- language: python -->
# gmail_filters_to_sieve.py (concept)
import json
def gmail_to_sieve(filter_obj):
# filter_obj is the Gmail API representation
sieves = []
crit = filter_obj.get('criteria', {})
act = filter_obj.get('action', {})
tests = []
if 'from' in crit:
tests.append('header :is "From" "%s"' % crit['from'])
if 'to' in crit:
tests.append('header :contains "To" "%s"' % crit['to'])
if 'subject' in crit:
tests.append('header :contains "Subject" "%s"' % crit['subject'])
if 'query' in crit:
# best-effort: map simple "has:attachment" or "larger:"
q = crit['query']
if 'has:attachment' in q:
tests.append('exists "Content-Disposition"')
action_lines = []
if act.get('removeLabelIds'):
# map label removals to not moving into folder
pass
if act.get('addLabelIds'):
# map Gmail label to fileinto folder of same name
for lbl in act['addLabelIds']:
action_lines.append('fileinto "%s";' % lbl)
if act.get('forward'):
action_lines.append('redirect "%s";' % act['forward'])
sieve = 'if allof(%s) {\n %s\n}\n' % (', '.join(tests), '\n '.join(action_lines))
return sieve
# usage: iterate over filters JSON and write to a .sieve file
Mapping rules and limitations
- Labels → folders: Gmail labels that are not exclusive will create duplicates when mapped to IMAP folders. Consider deduplication steps post-import.
- has:attachment mapping to Sieve is heuristic: check Content-Disposition or Content-Type headers; false negatives are possible for inline attachments.
- Complex queries (OR/NOT with nested operators) may not translate; log and review those filters manually.
- Actions not supported: Gmail's “Mark as important” or category-based actions don't exist in Sieve; use flags or custom headers instead.
Contacts: export and import scripts
Google Contacts export via Takeout provides vCard/CSV; for automation use the People API to extract, normalize and emit a vCard/CSV file for target providers.
Python example: export contacts to vCard using People API
<!-- language: python -->
# contacts_export.py (abridged)
from google.oauth2 import service_account
from googleapiclient.discovery import build
import vobject
SCOPES = ['https://www.googleapis.com/auth/contacts.readonly']
creds = service_account.Credentials.from_service_account_file('sa.json', scopes=SCOPES).with_subject('user@domain.com')
svc = build('people','v1',credentials=creds)
results = svc.people().connections().list(resourceName='people/me', pageSize=1000, personFields='names,emailAddresses,phoneNumbers').execute()
conns = results.get('connections', [])
with open('contacts.vcf','w') as f:
for p in conns:
v = vobject.vCard()
name = p.get('names',[{}])[0].get('displayName')
if name: v.add('fn').value = name
for e in p.get('emailAddresses',[]):
v.add('email').value = e['value']
f.write(v.serialize())
Import targets
- Fastmail / most providers: import vCard/CSV directly via web UI or API.
- Office365/Exchange: flatten to CSV matching their schema.
- Self-hosted CardDAV: use vdirsyncer or cadaver to push contacts to the server.
Migration manifest: yaml example for automation and audit
Keep a manifest per account so migrations are repeatable and auditable. Example:
<!-- language: yaml -->
account: user@domain.com
source:
type: gmail
imap_host: imap.gmail.com
auth: oauth2
oauth_client_id: xxxxx
target:
type: imap
imap_host: imap.target.com
user: user@domain.com
tasks:
- export: takeout
path: /archives/user-takeout/
- transfer: imapsync
options:
syncinternaldates: true
addheader: true
- filters: export_api
convert: sieve
dest: /manifests/user.sieve
- contacts: export_people
output: /manifests/user.vcf
verification:
- check_counts: true
- sample_messages: 50
Verification and cutover checklist
- Compare message counts per folder/label (source vs destination).
- Randomly verify message integrity (raw headers, dates, attachments).
- Test filters by sending sample messages that should trigger each rule.
- Verify contacts (searching for key contacts, email format, phone numbers).
- Set up a forwarding window: keep original account receiving mail and forward to new inbox for 14–30 days to catch missings.
Common pitfalls and how to avoid them
- Missing messages: usually caused by search filters in imapsync or API list pagination — always iterate through pages and confirm totals.
- Label explosion: mapping multi-labeled messages to multiple folders can bloat the destination. Option: pre-process to collapse rarely used labels into an "Archive" folder.
- Forwarding verifications: destination providers often require confirmation for auto-forwarding; update filters to only tag and not forward until verified.
- Privacy & scanning: some providers scan mail for AI features. If privacy is the goal, choose providers that explicitly avoid content scanning.
Tip: keep a read-only canonical MBOX archive before you start destructive operations. That gives you a forensic copy you can re-import or inspect if something goes wrong.
Advanced strategies and 2026 trends
2026 trends to account for in your migration strategy:
- AI-enabled scanning: Platforms are offering AI features that require content access; choose providers with clear data-use contracts if you want to avoid this.
- Server-side filters (Sieve) are back: Many modern providers support Sieve and ManageSieve; building a Sieve repo from Gmail filters future-proofs server-side behavior.
- Containerized migration pipelines: Use Docker + Compose to bundle imapsync, mbsync, and API scripts for repeatable runs across accounts.
Practical migration playbook (step-by-step)
- Inventory and manifest: create the YAML manifest for the account.
- Archive: perform Google Takeout (or run gmail_export.py) and store MBOX in immutable storage.
- Contacts: run contacts_export.py and import into target; verify key contacts.
- Filters: export via API or UI, convert to Sieve, review converted rules manually for complex queries.
- Transfer mail: run imapsync in Docker. Start small (INBOX + recent 12 months), validate, then run full migration.
- Verification: run the checks in the manifest. Sample and edge-case testing is mandatory.
- Cutover: update DNS/aliases or create forwarding and keep the old account forwarding for 14–30 days. Disable auto-forwarding in filters only after confirmation.
Appendix: quick reference commands
imapsync minimal
imapsync --host1 imap.gmail.com --user1 user@gmail.com --authmech1 XOAUTH2 --oauth2_token1 "$TOKEN" --ssl1 \ --host2 imap.target.com --user2 user@domain.com --password2 'PWD' --ssl2 --syncinternaldates --addheader
mbox -> maildir (python)
python -c "import mailbox,maildir,sys; m=mailbox.mbox('export.mbox'); md=maildir.Maildir('Maildir',create=True); [md.add(m[i]) for i in range(len(m))]"
Final notes
Migrating email off Gmail in 2026 requires both technical steps and policy awareness. Use OAuth2 and service accounts for automation, keep immutable archives, and always verify filters and forwarding behavior after the move. The scripts here are practical starting points — customize for your provider and compliance needs.
Actionable takeaways (quick)
- Use imapsync with OAuth2 for direct IMAP transfers and preserve metadata.
- Export filters via API and convert to Sieve for server-side continuity.
- Automate with manifests (YAML) so migrations are repeatable and auditable.
Call to action
If you want a migration manifest template or a tested Docker Compose pipeline that bundles imapsync, mbsync and the export scripts above for your team, download our free repo starter or contact us for a migration audit. Start with the manifest — it turns a risky manual process into a repeatable, reviewable workflow.
Related Reading
- Smart Routines 101: Automating Your Diffuser with Home Assistants and Chargers
- Consolidation Playbook: How to Replace Five Underused Payroll Tools with One Core System
- Geopolitics, Metals and Fed Independence: Building an Alert System for Macro Risk
- How to Land a Real Estate Internship and Stand Out to Brokerages Like Century 21
- Cross-Platform Playbook: Using Bluesky, Digg, and Niche Forums to Diversify Your Audience
Related Topics
frees
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
Cloud Skills in the Age of AI Analytics: The Specializations Tech Teams Need Next
Embracing Depth Over Buzz: Enhancing User Engagement in Tech Solutions
Cloud Cost Lessons from Volatile Supply Chains: Building Resilient Analytics for Food and Ag Operations
Beyond Hardware: Addressing RAM Limitations in Cloud Applications
Deploy a Low-Cost Livestock Monitoring Fleet on Free Cloud Tiers
From Our Network
Trending stories across our publication group