# Runbook: Google Workspace (Gmail) → Microsoft 365 mail migration

Reusable ACG process for moving a client's mail (and optionally calendar/contacts) from Google
Workspace to Exchange Online. Covers method selection, prerequisites, the native Microsoft path
end-to-end, the MX cutover, and gotchas. Author: 2026-06-25 (first written while scoping Birth
Biologic). Status: working draft — refine after the first real run.

---

## 1. Pick the method

| Method | Moves | Cost | When to use |
|---|---|---|---|
| **MS native "Migration from Google Workspace"** (EAC) | mail + calendar + contacts, with incremental/delta sync | **Free** | **Default.** Need Google super-admin to set up a service account + domain-wide delegation on the source tenant. |
| **IMAP migration** (EAC) | **mail only** | Free | Fallback when you can't get domain-wide delegation. Gmail labels→folders get messy; no calendar/contacts. |
| **BitTitan MigrationWiz** (3rd-party) | mail + cal + contacts + Drive, coexistence, best throttling/reporting | ~$12–15/mailbox | Larger/complex jobs, strict cutover windows, or to make this a repeatable productized service. **ACG has no BitTitan account yet.** |
| **Takeout MBOX / Gmail→PST → 365 PST Import** | mail only, one-shot | Free | Last resort / archival only. Manual, per-mailbox, no delta, fidelity loss. Avoid for live cutovers. |

Default to the **MS native** path below.

---

## 2. Prerequisites (all methods)

- **Google super-admin** on the SOURCE Workspace tenant (for the SA + domain-wide delegation, or an app password for IMAP). **This is the #1 gate — confirm it before quoting timelines.**
- **M365 target tenant** with the client's domain **added and verified** (do NOT cut MX yet), and **licensed mailboxes provisioned** for every user being migrated.
- **DNS control** for the MX/autodiscover/SPF/DKIM cutover — know the registrar AND the DNS host (often different). Get edit access ahead of time.
- **Inventory** (from the Google Admin console): user list + mailbox sizes, shared mailboxes / delegated mailboxes, groups/distribution lists, aliases, calendars/resources, and who's actually active.
- **Vault** the source Google admin creds / SA per the ACG pattern (see §6).

---

## 3. ACG reusable asset — the Google domain-wide-delegation SA

ACG already has a Google service account for Workspace access:
`msp-tools/acg-msp-access-google-workspace.sops.yaml` →
`acg-msp-access@acg-msp-access.iam.gserviceaccount.com` (GCP project `acg-msp-access`).
- Currently scoped for the **security-assessment** read path (`gmail.readonly`, `drive.readonly`,
  `admin.directory.user`, `reports.audit`, etc.) and **onboarded only to lonestarelectrical.net**.
- For a **migration** you can reuse this SA (or create a per-job one) — but the migration needs the
  **migration scopes** added to the SA's **domain-wide delegation in the SOURCE client's Google Admin
  console**, not just ACG's project. Microsoft specifies the exact scopes (see §4 step 1).
- Reusing the same SA across clients is fine; each source tenant separately authorizes the SA's
  `client_id` in *their* Admin console. Keep the key in the vault; rotate per the entry's notes.

---

## 4. MS native migration — end to end

**Step 1 — Source (Google) prep**
1. In **GCP** (project `acg-msp-access` or a new one): ensure the service account exists and a JSON key is in the vault. Enable APIs: **Gmail API, Google Calendar API, People API**. (The legacy *Contacts API* was retired by Google in 2022 and **cannot be enabled** — the `m8/feeds` contacts scope is now an alias served by the People API, so People API enablement covers it. Enabling the APIs in `acg-msp-access` requires being signed in as the **ACG owner** of that project — a *client* super-admin has no rights to ACG's GCP project.)
2. In the SOURCE **Google Admin console** → Security → API controls → **Domain-wide delegation** → add the SA's **OAuth2 Client ID** (the SA's numeric "Unique ID", NOT the app client_id) with the **exact 5-scope string below, comma-separated, no spaces**. Google rejects the migration token request **all-or-nothing** — if even one scope is missing the endpoint fails later with `unauthorized_client … not authorized for any of the scopes requested`. Verified current 2026-06 (MS Learn `manually-configuring-gsuite-for-migration` + Grok live cross-check):

   ```
   https://mail.google.com/,https://www.googleapis.com/auth/calendar,https://www.google.com/m8/feeds/,https://www.googleapis.com/auth/gmail.settings.sharing,https://www.googleapis.com/auth/contacts
   ```

   Propagation can take 15 min–24 h (usually minutes). Do NOT rely on a smaller "mail+calendar+contacts" set — `m8/feeds` and `gmail.settings.sharing` are both required by the MS endpoint.
3. Confirm a Google super-admin mailbox exists for the migration to impersonate.

**Step 2 — Target (M365) prep**
1. Add + **verify the client domain** in M365 (TXT). Do **not** change MX yet.
2. **Provision + license** every target mailbox (match Google addresses). Create any missing ones.
3. (Optional) pre-create shared mailboxes / distribution groups to mirror Google.

**Step 3 — Create + run the migration batch**
1. EAC → **Migration → Add migration batch → "Migration from Google Workspace."**
2. Provide the **SA JSON key** + the super-admin to impersonate; upload a **CSV** of mailboxes
   (`EmailAddress` of each source/target — they match on the verified domain).
3. Start the batch → **initial sync**, then it keeps doing **incremental/delta** passes. Leave it
   running (days for large mailboxes). Monitor the batch report for skipped items.

**Step 4 — Validate**
- Spot-check a few migrated mailboxes (mail counts, folders, calendar, contacts). Resolve large
  skipped-item counts before cutover.

**Step 5 — MX cutover (the go-live)**
1. **Lower TTL** on MX (and autodiscover) at the DNS host 24–48h ahead.
2. Flip **MX** → M365 (`<tenant>-com.mail.protection.outlook.com`), update **SPF**
   (`include:spf.protection.outlook.com`), republish **DKIM** (enable in Defender, add the 2 CNAMEs),
   set **autodiscover** CNAME → `autodiscover.outlook.com`, review **DMARC**.
3. Run a final **incremental** sync after cutover to catch mail delivered to Google during propagation.
4. **Complete/finalize** the batch.

**Step 6 — Decommission**
- After a validation window: reconfigure clients (Outlook/mobile to M365), remove Google licenses,
  remove the SA's domain-wide delegation from the client's Admin console, cancel Workspace.

---

## 5. Gotchas

- **Labels → folders:** Gmail labels become folders; a message with multiple labels can duplicate. The
  native tool handles the primary label; expect minor structure differences.
- **Shared / delegated mailboxes, groups, aliases, calendars/resources** don't all come across as
  "mailboxes" — inventory and recreate them in M365 deliberately.
- **Throttling:** Google + EXO both throttle; large tenants take days. Don't promise same-day.
- **SPF/DKIM/DMARC:** must be updated at cutover or outbound mail fails auth / inbound double-delivers.
- **Don't cut MX early** — mail in flight to Google after an early MX flip is missed until the final delta.
- **Calendar/contacts** only migrate via the native tool or BitTitan, NOT via IMAP.
- **Retention/litigation hold:** if the client needs Google data retained, export before decommission.

## 6. Credentials & vault

- Source Google admin / SA: vault under `clients/<slug>/google-workspace.sops.yaml` (see
  `clients/lonestar-electrical/google-workspace.sops.yaml` for the shape) or reuse
  `msp-tools/acg-msp-access-google-workspace.sops.yaml` (note which clients it's delegated to).
- **Never echo the SA JSON / private key in chat** — read named fields with `vault.sh get-field`,
  not a regex over the whole blob (a line-redaction missed a `private_key` once — errorlog 2026-06-25).
- Target M365: the client's tenant-admin path (CIPP / remediation-tool / GA).

## 7. Make it a service (future)

If Google→365 jobs recur, decide: standardize on the **native** path (free, this runbook) vs. buy
**BitTitan** (productized, per-mailbox, coexistence). Promote this runbook to a wiki pattern via
`/wiki-compile` once proven on the first real migration.
