Most migration guides describe the happy path. This isn’t that. This is what you actually find when you inherit a migration in progress and start pulling on threads.

The Setup

Mid-flight GWS to M365 migration. Test batches already running. Domain setup incomplete. Multiple migration endpoints in Exchange Online pointing at a GCP service account that nobody can confirm ownership of. This is a fairly normal state for an org-scale migration that started before all the architecture decisions were locked in.

Here’s what the audit surfaced and what it means operationally.

Migration Endpoints Without Domain Completion

The first thing that jumps out: test migration batches were executing against Exchange Online before the custom domain was fully verified in M365 admin. The domain showed Incomplete setup, a TXT ownership record either not propagated yet or not matching the value M365 expected.

This isn’t a blocker for data movement. The migration pipeline doesn’t care about your domain verification state, it’s moving mailbox content from Google’s API into Exchange Online mailboxes that exist on the default *.onmicrosoft.com domain. The test batches ran fine.

What it means is that the destination mailboxes were receiving data under the wrong primary address. When you eventually verify the custom domain and assign aliases, the mail that landed during test migration is already there, but the mailbox identity it landed in may not have the right SMTP addresses attached yet.

Before starting production batches, verify:

Get-Mailbox -Identity "[email protected]" | Select-Object EmailAddresses

Confirm the custom domain alias is present on every destination mailbox before the batch runs, not after.

Auditing Existing Migration Endpoints

To see what service account your migration endpoints are using:

Connect-ExchangeOnline -UserPrincipalName [email protected]
Get-MigrationEndpoint | Format-List

The output for a Gmail endpoint type will include a Username field which is your GCP service account. If RemoteServer is blank and EndpointType is Gmail, you’re looking at a Google Workspace migration endpoint using a service account JSON key.

What the output won’t tell you: who created the GCP project, whether that service account still has valid keys, or whether the project is under your org’s Google Workspace tenant or someone’s personal Gmail account.

To determine org ownership of the GCP project, log into https://console.cloud.google.com/cloud-resource-manager with a Google Workspace super admin account. If the project was created under your org, it appears there. If it doesn’t appear, the project lives under a personal account and you don’t control it.

The Service Account Key Problem

Domain-wide delegation on a GCP service account means that account has impersonation access to every mailbox in your Google Workspace org. Every mailbox. That’s the permission model that makes migration work and it’s also a significant credential if it leaks.

If you inherited a migration setup and can’t confirm who created the service account or where the JSON key has been:

  1. Go to https://console.cloud.google.com/iam-admin/serviceaccounts – select the project, click the service account, open the Keys tab.
  2. Note how many keys exist and when they were created.
  3. Create a new key, update the migration endpoint in Exchange Online with it, run a test migration to confirm it works.
  4. Delete the old key.

Leaving orphaned keys on an account with domain-wide delegation open is not a theoretical risk it’s a live credential with org-wide read access to email.

To update an existing migration endpoint with a new service account key:

$credential = Get-Credential
Set-MigrationEndpoint -Identity "YourEndpointName" -Credentials $credential

The credential prompt is where you provide the new JSON key content. In practice most teams delete and recreate the endpoint rather than update it in place.

Test Batches and Deduplication Risk

If test batches ran before the domain and alias setup was complete, you have mailboxes in Exchange Online that already received data. When you run production batches against the same mailboxes, M365 attempts deduplication, the items already present in the mailbox won’t be re-imported if they match on message ID.

The risk is items modified in Google between the test sync and the production sync. Modified items won’t deduplicate cleanly against their earlier versions. Depending on your org’s email activity level during migration, this could be negligible or it could create duplicate conversations in high-volume mailboxes.

The conservative path: for any mailbox that received data in a test batch that you intend to migrate for real, decide explicitly whether to accept the dedup behavior or wipe the Exchange Online mailbox and re-migrate clean. There’s no automated way to retroactively separate test-migrated content from production-migrated content once it’s in the mailbox.

The Data Consistency Score

M365 migration batches report a data consistency score per mailbox. The values are:

  • Perfect all items synced, nothing skipped
  • Good minor skips within acceptable threshold
  • Investigate skip count warrants review
  • Poor significant data loss

An Investigate score with several hundred skipped items on a single mailbox is worth pulling the skip report before you scale to the full org. The skip report categorizes why items didn’t migrate, and common causes are items exceeding the 150MB size limit, corrupted items in the source mailbox, or item types Exchange Online doesn’t support.

If the skip rate holds at that level across your full mailbox population, the math gets significant fast. Pull the report on your test mailbox now and characterize what’s being skipped before you’re looking at that problem across thousands of mailboxes post-cutover.

Get-MigrationUserStatistics -Identity "[email protected]" -IncludeSkippedItems | 
  Select-Object -ExpandProperty SkippedItems

Domain Verification: Forcing a Recheck

The M365 admin center doesn’t continuously poll DNS. To force a recheck after you’ve added your TXT verification record:

  • Navigate to the domain in M365 admin > Continue setup – the wizard performs a live DNS check at that step.
  • Or use the Refresh button on the domain overview page to trigger a recheck without stepping through the full wizard.

Before hitting either button, confirm your records are actually live:

nslookup -type=TXT yourdomain.com
# or
dig TXT yourdomain.com

If nslookup or dig shows the record, M365 will see it. If it doesn’t show up there yet, M365 definitely won’t see it – save yourself the click.

What Gets Inherited

The pattern here is common: a migration starts before all the infrastructure decisions are final, test batches run under suboptimal conditions, credentials get created by whoever was available at the time, and the engineer who has to execute the production cutover inherits all of it.

The audit checklist before production cutover:

  1. Custom domain verified in M365, aliases assigned to all destination mailboxes before batch creation
  2. GCP project ownership confirmed under org tenant, not personal account
  3. Service account key inventory reviewed, orphaned keys removed
  4. Domain-wide delegation scopes verified in Google Workspace admin
  5. Test batch skip reports reviewed and skip rate extrapolated to full mailbox population
  6. Decision made on test-migrated mailboxes: accept dedup behavior or wipe and re-migrate clean
  7. Migration endpoint credentials confirmed working against a fresh test batch post-key-rotation

None of this is complicated. All of it gets skipped when migrations are treated as a data movement problem instead of an identity and credential problem with data movement attached.