Skip to content

Repository files navigation

Email Sync OAuth2

Dockerized multi-account IMAP synchronization tool with OAuth2 support for Office 365, Hotmail, Outlook.com, and Gmail.

Problem This Solves

Microsoft retired Basic Authentication for Exchange Online (IMAP/SMTP), and Google is pushing XOAUTH2 as the only authentication method. This breaks traditional IMAP sync tools that rely on username/password.

Mailcow's built-in sync job and many other tools don't support OAuth2, making it impossible to migrate emails from Hotmail/Outlook.com/Gmail into self-hosted mail servers.

This project solves that problem by combining:

  • imapsync — battle-tested IMAP migration tool
  • imapsync's OAuth2 helper — token authorization and refresh for Microsoft and Google accounts
  • Docker — portable, reproducible, easy to deploy

Features

Feature Status
Office 365 / Hotmail / Outlook.com sync ✅ OAuth2
Gmail / Google Workspace sync ✅ OAuth2
Any IMAP server (password auth)
Multiple accounts in one container ✅ Unlimited
Incremental sync (only new emails)
Folder structure preservation
Scheduled sync via interval
Healthcheck endpoint
One-shot sync mode
Dry run mode
Delete propagation (--delete2)
Custom imapsync flags per account
Persistent token storage
Automatic token refresh
SMTP sync notifications ✅ Optional
Interval or cron scheduling

Authentication Requirements

Provider Auth Method Token Lifetime Auto-Refresh
Hotmail / Outlook.com / Personal Microsoft OAuth2 90 days (hard limit) ✅ until day 90
Office 365 / Microsoft 365 (Work/School) OAuth2 90 days (configurable by admin) ✅ indefinitely
Gmail / Google Workspace OAuth2 7 days (testing) / 90 days (verified)
Other IMAP Password N/A N/A

⚠️ Personal Microsoft accounts (hotmail.com, outlook.com, live.com) have a hard 90-day refresh token limit. You must reauthorize every ~80 days. Work/school accounts can have indefinite tokens if configured by Azure AD admin.


Quick Start

1. Clone and configure

git clone https://github.com/yourusername/email-sync-oauth2.git
cd email-sync-oauth2
cp .env.example .env
nano .env   # edit your settings

2. First-time OAuth authorization (requires browser)

Run the container interactively for each account:

# For Office 365 / Hotmail (Account 1)
docker compose run --rm imapsync authorize 1
# → Paste URL in browser, log in to Microsoft, click "Allow"
# → Token saved to ./tokens/user1_hotmail.token

# For Account 2
docker compose run --rm imapsync authorize 2
# → Same process for this account

3. Start syncing

docker compose up -d

4. Monitor

docker logs -f email-sync-oauth2
tail -f logs/account1.log

Configuration

All configuration is done through environment variables. Edit .env or pass directly to docker-compose.yml.

Global Variables

Variable Default Description
NUM_ACCOUNTS 1 Number of accounts to sync
SYNC_INTERVAL 21600 (6h) Seconds between sync cycles
SYNC_CRON empty Optional five-field cron expression; overrides SYNC_INTERVAL
ONCE false Run once and exit (no daemon)
TOKEN_DIR /tokens Directory inside container for token files
LOG_DIR /var/log/imapsync Directory inside container for log files

Per-Account Variables (ACCOUNT_1_, ACCOUNT_2_, ...)

Variable Required Description
ACCOUNT_N_SOURCE_HOST Source IMAP server (e.g., outlook.office365.com)
ACCOUNT_N_SOURCE_USER Source email address
ACCOUNT_N_SOURCE_AUTH oauth2 or password
ACCOUNT_N_SOURCE_PROVIDER If OAuth2 office365, hotmail, outlook, gmail, google
ACCOUNT_N_SOURCE_TOKEN If OAuth2 Path to token file (e.g., /tokens/account1.token)
ACCOUNT_N_SOURCE_PASS If password Source password
ACCOUNT_N_DEST_HOST Destination IMAP server (e.g., mail.hoelee.com)
ACCOUNT_N_DEST_USER Destination email/username
ACCOUNT_N_DEST_PASS Destination password
ACCOUNT_N_DELETE No true = delete emails on destination not on source
ACCOUNT_N_DRY_RUN No true = log only, don't actually sync
ACCOUNT_N_EXTRA_OPTS No Additional imapsync flags (optional)

Password authentication is still supported

OAuth2 is required only for providers that require it. Any normal IMAP source can still use a password by setting ACCOUNT_N_SOURCE_AUTH=password and ACCOUNT_N_SOURCE_PASS. The destination currently uses password authentication for every provider.

ACCOUNT_3_SOURCE_HOST=imap.example.com
ACCOUNT_3_SOURCE_USER=source@example.com
ACCOUNT_3_SOURCE_AUTH=password
ACCOUNT_3_SOURCE_PASS=source_password

ACCOUNT_3_DEST_HOST=mail.example.net
ACCOUNT_3_DEST_USER=destination@example.net
ACCOUNT_3_DEST_PASS=destination_password

Scheduling

By default, a batch runs immediately when the container starts and then every six hours (SYNC_INTERVAL=21600). Set the interval in seconds to use a different fixed frequency:

SYNC_INTERVAL=3600
SYNC_CRON=

For calendar-based scheduling, set SYNC_CRON to a standard five-field cron expression. It overrides SYNC_INTERVAL.

# Every six hours, on the hour
SYNC_CRON=0 */6 * * *

# Every day at 02:30
# SYNC_CRON=30 2 * * *

All ACCOUNT_N_* variables in .env are passed to the container. To add an account, copy an account block, use the next number, and increase NUM_ACCOUNTS—there is no two-account Compose limit.

SMTP notifications

Notifications are off by default. Set NOTIFY_MODE to one of these values:

Value When an email is sent
batch_success Once after every batch where every configured account succeeds
account_success Once for each account that succeeds
failure Once after a batch where one or more accounts fail

Configure SMTP whenever notifications are enabled:

NOTIFY_MODE=batch_success
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=alerts@example.com
SMTP_PASS=your_smtp_password
SMTP_FROM=alerts@example.com
SMTP_TO=admin@example.com,ops@example.com
SMTP_TLS=starttls

SMTP_TLS accepts starttls (normally port 587), tls (implicit TLS, normally port 465), or none. If a notification cannot be delivered, the container writes the reason to logs/notification.log, logs the failure to the container output, and becomes unhealthy until a notification sends successfully.


Complete Examples

Example 1: Hotmail → Mailcow (OAuth2)

NUM_ACCOUNTS=1
SYNC_INTERVAL=21600

ACCOUNT_1_SOURCE_HOST=outlook.office365.com
ACCOUNT_1_SOURCE_USER=you@hotmail.com
ACCOUNT_1_SOURCE_AUTH=oauth2
ACCOUNT_1_SOURCE_PROVIDER=office365
ACCOUNT_1_SOURCE_TOKEN=/tokens/you_hotmail.token

ACCOUNT_1_DEST_HOST=mail.hoelee.com
ACCOUNT_1_DEST_USER=you@mailcow.com
ACCOUNT_1_DEST_PASS=your_mailcow_password

Example 2: Gmail → Mailcow (OAuth2)

ACCOUNT_1_SOURCE_HOST=imap.gmail.com
ACCOUNT_1_SOURCE_USER=you@gmail.com
ACCOUNT_1_SOURCE_AUTH=oauth2
ACCOUNT_1_SOURCE_PROVIDER=gmail
ACCOUNT_1_SOURCE_TOKEN=/tokens/you_gmail.token

Example 3: Multiple accounts

NUM_ACCOUNTS=3

# Account 1: Hotmail -> Mailcow
ACCOUNT_1_SOURCE_HOST=outlook.office365.com
ACCOUNT_1_SOURCE_USER=alice@hotmail.com
ACCOUNT_1_SOURCE_AUTH=oauth2
ACCOUNT_1_SOURCE_PROVIDER=office365
ACCOUNT_1_SOURCE_TOKEN=/tokens/alice_hotmail.token
ACCOUNT_1_DEST_HOST=mail.hoelee.com
ACCOUNT_1_DEST_USER=alice@mailcow.com
ACCOUNT_1_DEST_PASS=password1

# Account 2: Gmail -> Mailcow
ACCOUNT_2_SOURCE_HOST=imap.gmail.com
ACCOUNT_2_SOURCE_USER=bob@gmail.com
ACCOUNT_2_SOURCE_AUTH=oauth2
ACCOUNT_2_SOURCE_PROVIDER=gmail
ACCOUNT_2_SOURCE_TOKEN=/tokens/bob_gmail.token
ACCOUNT_2_DEST_HOST=mail.hoelee.com
ACCOUNT_2_DEST_USER=bob@mailcow.com
ACCOUNT_2_DEST_PASS=password2

# Account 3: Legacy IMAP -> Mailcow (password auth)
ACCOUNT_3_SOURCE_HOST=imap.oldprovider.com
ACCOUNT_3_SOURCE_USER=carol@oldprovider.com
ACCOUNT_3_SOURCE_AUTH=password
ACCOUNT_3_SOURCE_PASS=oldpassword
ACCOUNT_3_DEST_HOST=mail.hoelee.com
ACCOUNT_3_DEST_USER=carol@mailcow.com
ACCOUNT_3_DEST_PASS=password3

Example 4: One-shot sync (cron-friendly)

# .env
ONCE=true
NUM_ACCOUNTS=1

# Add to crontab:
# 0 */6 * * * cd /opt/email-sync-oauth2 && docker compose up --abort-on-container-exit

Makefile Commands (Optional)

A Makefile is included for convenience:

# Build and start
make build
make up

# Check status and health
make status
make health

# Authorize an account (requires browser)
make authorize ACCOUNT=1

# Force sync now
make sync ACCOUNT=1

# Force a batch for every configured account
make sync-all

# Follow logs
make logs

# Enter container shell
make shell

# Run once and exit (one-shot mode)
make once

# Clean up
make clean

Manual Operations

Force sync a specific account immediately

docker compose exec imapsync sync.sh sync 1

Reauthorize OAuth tokens

docker compose run --rm imapsync authorize 1

Enter container shell

docker compose exec imapsync sync.sh shell

Run imapsync manually with custom flags

docker compose exec imapsync sync.sh imapsync \
  --host1 outlook.office365.com --user1 you@hotmail.com \
  --oauthaccesstoken1 /tokens/you_hotmail.token \
  --host2 mail.hoelee.com --user2 you@mailcow.com \
  --password2 yourpassword \
  --ssl1 --ssl2 --dry

Monitoring & Healthcheck

The container includes a Docker healthcheck based on a heartbeat mechanism:

  • A background process writes a heartbeat file every 60 seconds
  • Docker checks if the heartbeat is less than 5 minutes old
  • If no heartbeat, the container is marked unhealthy and can be auto-restarted by orchestrators

It also becomes unhealthy if a configured SMTP notification fails to send. The reason is logged in logs/notification.log and in the container output.

The heartbeat confirms that the sync daemon is running; it does not mean every mailbox was able to authenticate or sync. If an OAuth refresh token expires or is revoked, the container log includes a reauthorization command, for example:

Account 1: OAuth2 authorization is expired, revoked, or rejected by office365. Reauthorize with: docker compose run --rm imapsync authorize 1

After completing the browser authorization, start that account immediately with:

docker compose exec imapsync sync.sh sync 1
# Check health status
docker inspect --format='{{.State.Health.Status}}' email-sync-oauth2

# View healthcheck logs (useful for debugging)
docker inspect --format='{{json .State.Health}}' email-sync-oauth2 | jq

# Watch live heartbeat
docker exec email-sync-oauth2 cat /var/log/imapsync/.heartbeat

Log files

./logs/
├── .heartbeat            # Healthcheck timestamp (updated every 60s)
├── account1.log          # Full imapsync output for account 1
├── account1.last_success # Unix timestamp of last successful sync
├── account1.last_failure # Unix timestamp of last failed sync
├── account2.log
├── notification.log      # SMTP notification delivery output
├── .notification_failed  # Present only while notification delivery is failing
└── ...

Check sync status

# When did account 1 last succeed?
date -d @"$(cat logs/account1.last_success)"

# When did it last fail?
date -d @"$(cat logs/account1.last_failure)"

Reauthorize reminders

For personal Microsoft accounts (90-day token limit), set a calendar reminder or monitoring check:

# Check if reauthorization is needed (token file older than 80 days)
find ./tokens -name "*.token" -mtime +80 -ls

Architecture

┌─────────────────────────────────────────────────────┐
│                   Docker Container                  │
│                                                     │
│  ┌──────────────┐     ┌──────────────────────────┐ │
│  │   sync.sh    │────▶│   imapsync (per account) │ │
│  │  (daemon)    │     └──────────┬───────────────┘ │
│  │              │                │                  │
│  │  Interval:   │     ┌──────────▼───────────────┐ │
│  │  6h default  │     │   OAuth2 token helpers   │ │
│  │              │     │  oauth2_office365_with_  │ │
│  │  Healthcheck │     │  imap / oauth2_imap     │ │
│  └──────────────┘     └──────────┬───────────────┘ │
│                                   │                  │
│                        ┌──────────▼───────────────┐ │
│                        │   /tokens/ (mounted)     │ │
│                        │   account1.token         │ │
│                        │   account2.token         │ │
│                        └──────────────────────────┘ │
│                                                     │
│  ┌────────────────────────────────────────────────┐ │
│  │ /var/log/imapsync/ (mounted)                   │ │
│  │   account1.log, account1.last_success, etc.    │ │
│  └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘

       │                                    │
       ▼                                    ▼
┌──────────────┐                    ┌──────────────┐
│  Source IMAP │                    │ Dest IMAP    │
│  (OAuth2)    │                    │ (password)   │
│  outlook.    │                    │ mail.hoelee  │
│  office365   │                    │ .com         │
└──────────────┘                    └──────────────┘

FAQ

Q: Do I need to reauthorize every 90 days? A: Only for personal Microsoft accounts (hotmail.com, outlook.com, live.com). Work/school Microsoft 365 accounts can have indefinite tokens. Gmail tokens last 7-90 days depending on app verification status.

Q: Can I use this for a one-time migration? A: Yes! Set ONCE=true in .env and run docker compose up.

Q: Is the sync incremental? A: Yes. imapsync only transfers emails that don't already exist on the destination.

Q: Can I sync from any source to any destination? A: Any IMAP-compatible server. The source needs OAuth2 if it's Microsoft/Google. The destination always uses password auth.

Q: How many accounts can I sync? A: Unlimited. Just increase NUM_ACCOUNTS and add ACCOUNT_N_* variables.

Q: Can I use this with Synology DSM? A: Yes. Point Docker volume paths to /volume1/docker/email-sync-oauth2/.

Q: What if my source is Exchange with ActiveSync? A: ActiveSync doesn't work well with mailcow (Microsoft disabled basic auth for ActiveSync). Use IMAP with OAuth2 instead.


Building from Source

docker build -t email-sync-oauth2:latest .

docker run -d \
  --name email-sync-oauth2 \
  --restart unless-stopped \
  -e NUM_ACCOUNTS=1 \
  -e SYNC_INTERVAL=21600 \
  -e ACCOUNT_1_SOURCE_HOST=outlook.office365.com \
  -e ACCOUNT_1_SOURCE_USER=you@hotmail.com \
  -e ACCOUNT_1_SOURCE_AUTH=oauth2 \
  -e ACCOUNT_1_SOURCE_PROVIDER=office365 \
  -e ACCOUNT_1_SOURCE_TOKEN=/tokens/you_hotmail.token \
  -e ACCOUNT_1_DEST_HOST=mail.hoelee.com \
  -e ACCOUNT_1_DEST_USER=you@mailcow.com \
  -e ACCOUNT_1_DEST_PASS=yourpassword \
  -v $(pwd)/tokens:/tokens \
  -v $(pwd)/logs:/var/log/imapsync \
  email-sync-oauth2:latest

License

MIT License. See LICENSE for details.

Credits


Contributing

Pull requests welcome! Please test with your own accounts before submitting.

  1. Fork the repo
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

Dockerized multi-account IMAP sync with OAuth2 for Office 365, Gmail & Google Workspace. Migrate emails from Hotmail/Outlook.com/Gmail to any IMAP server — even after Microsoft killed Basic Auth.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages