MigrationFox Docs

Mail Migration: Service-Account Setup

How to register your own Microsoft Entra ID (Azure AD) app with app-only (Application) permissions so MigrationFox can read and write mailboxes across your Microsoft 365 tenant during a bulk mail migration. Use this if your organization prefers its own app registration over admin consent to the MigrationFox Mail app.

Availability

Mail migration is in private beta. If your account shows “Coming Soon · Private Beta” on the Mail page, email hello@migrationfox.com to request access. You can prepare the app registration on this page ahead of time. Attachment files are not copied by default; see Bulk mail migration for details.

What this page covers

This is the setup guide for bulk Microsoft 365 mail migrations with your own app-only credential. The mail wizard supports Gmail and Microsoft 365 (Exchange Online). On-premises Exchange (EWS) and IMAP are not offered. Gmail uses a Google service account instead; see Bulk mail migration. By the end of this page you will have:

Which connection type to use

On the project's Connect tab, Microsoft 365 Mailboxes offers three ways to connect. The right one depends on how many mailboxes you are moving and who can grant consent.

ScenarioConnection typeWhy
Migrating your own mailbox only Just my own mailbox (sign in) You sign in and consent; the token covers your mailbox and mailboxes shared with you. No admin involvement required.
Migrating 2+ mailboxes, or any CSV / tenant-picker migration Multiple users in my tenant (admin consent) or Bring your own Azure app A delegated sign-in can't open other users' mailboxes: a request for user B's mail with a token issued to user A returns 403.
Tenant-to-tenant mailbox moves App-only on both source and destination There is no practical way to collect a sign-in from every user.
Shared mailboxes and former-employee mailboxes App-only Nobody signs in as that mailbox, so a tenant-wide app connection is the workable option.

Rule of thumb: if your list has more than one line, or you don't want each mailbox owner to sign in, use an app-only connection. Multiple users in my tenant is the quicker route (a Global Administrator approves the MigrationFox Mail app once, nothing to copy). The rest of this page covers Bring your own Azure app.

Part 1: Register the app

This registration is the identity MigrationFox uses to call Microsoft Graph in your tenant. A Global Administrator, Application Administrator, or Cloud Application Administrator can create it; granting admin consent for the permissions in Part 2 needs a Global Administrator (or Privileged Role Administrator).

  1. Sign in to the Microsoft Entra admin center.
  2. Navigate to Applications → App registrations.
  3. Click + New registration.
  4. Name: for example MigrationFox Mail Migration (any name is fine; it is only for your admins).
  5. Supported account types: Accounts in this organizational directory only (single tenant) is enough when the app is only used in your own tenant.
  6. Redirect URI: leave blank. App-only credentials never go through a browser redirect.
  7. Click Register.
  8. On the app's Overview page, copy the Application (client) ID and the Directory (tenant) ID. You will paste both into MigrationFox later.

Part 2: Grant Application permissions

App-only credentials only work through Application permissions, not Delegated. Delegated permissions scope a token to a signed-in user; Application permissions scope it to the whole tenant. MigrationFox needs two, the same two the MigrationFox Mail app uses.

PermissionTypeWhat it enables
Mail.ReadWrite Application Read folders and messages from source mailboxes; create folders and write messages into destination mailboxes.
User.Read.All Application List users in the tenant for the Pick users from my tenant picker and for Auto-match.

To grant them:

  1. From the app registration, open API permissions.
  2. Click + Add a permission → Microsoft Graph → Application permissions. (Choose Application, not Delegated. This is the most common setup mistake.)
  3. Search for Mail.ReadWrite, check it, click Add permissions.
  4. Repeat for User.Read.All.
  5. Back on the API permissions list, click Grant admin consent for <your tenant> and confirm.
  6. Check that each row's Status column reads Granted for <tenant>.

Admin consent is required

Application permissions only take effect after a Global Administrator (or Privileged Role Administrator) clicks Grant admin consent. Adding a permission without consent produces the same 403 errors as if it had never been added. If you don't have the role, add the permissions and ask an admin to grant consent.

Part 3: Create a client secret

The client secret is the password MigrationFox uses to authenticate as the app. Pick an expiry that covers your migration window, and set a reminder to rotate it.

  1. From the app registration, open Certificates & secrets.
  2. Under Client secrets, click + New client secret.
  3. Description: for example MigrationFox bulk mail.
  4. Expires: choose a period that covers the project (up to 24 months).
  5. Click Add.
  6. Copy the Value column immediately and store it somewhere secure. Entra shows the value only once; after you leave the page it is masked. If you miss it, delete the secret and create a new one.

The Secret ID column is not the secret. You want the Value.

Part 4: Add the connection in MigrationFox

You now have three values from Entra:

Enter them into MigrationFox:

  1. Sign in to app.migrationfox.com and open your project.
  2. Open the Connect tab.
  3. Click Microsoft 365 Mailboxes, then Bring your own Azure app.
  4. Paste the Application (Client) ID, Client Secret Value, and Directory (Tenant) ID.
  5. Save the connection.

MigrationFox checks the connection when you save it. If the check fails, something in Parts 1–3 didn't land; see Troubleshooting below for the common causes.

How the secret is used

The client secret is encrypted (AES-256) before it is stored. When a migration runs, MigrationFox uses it to request short-lived app-only access tokens from Microsoft with the client_credentials grant. App-only flows don't use refresh tokens.

Part 5: Build the mapping list

Bulk mail migrations are driven by a list where each line pairs one source mailbox with one destination mailbox. There are two columns:

ColumnRequired?Notes
sourceEmailYesThe address (UPN) of the mailbox to read. Must exist in the source tenant.
destEmailYesThe address (UPN) of the mailbox to write into. Must exist, licensed, in the destination tenant before the migration starts. MigrationFox does not create mailboxes.

Example:

sourceEmail,destEmail
alice@source.contoso.com,alice@target.contoso.com
bob@source.contoso.com,bob.smith@target.contoso.com
shared-ops@source.contoso.com,operations@target.contoso.com
finance@source.contoso.com,finance@target.contoso.com

Folder and date limits are not set per line. The wizard's Options step has a Folder Filter and From Date / To Date that apply to every mailbox in the migration. If different mailboxes need different limits, run them as separate migrations. The full list format (separators, header row, comments) is on the Bulk mail migration page.

Part 6: Start the bulk migration

  1. In your project, open the Migrate tab and click Start on the Mail card.
  2. Source: pick Office 365 and the app-only connection for the source tenant.
  3. Destination: pick Office 365 and the app-only connection for the destination tenant. (For moves within one tenant, source and destination can be the same connection.)
  4. Mailboxes: choose Paste / CSV mapping, then paste the list or click Upload CSV. Lines that aren't two valid addresses are listed with their line numbers and left out.
  5. Options: set any folder filter or date range.
  6. Review: check the mapping preview and click Start migration.

The list runs as one migration that processes the mailbox pairs one after another. A pair that fails (mailbox not found, missing permission) is recorded in the migration's log and does not stop the rest.

Troubleshooting

ErrorCauseFix
“Office 365 app-only credential requires a target user” An app-only connection was used with Just my own mailbox. App-only connections have no signed-in user, so every mailbox must be named explicitly. Choose Paste / CSV mapping or Pick users from my tenant in the Mailboxes step, and make sure each line has both addresses.
Graph 403 when reading or writing /users/{upn}/messages The permission was added without admin consent, or it was added as Delegated instead of Application. Entra ID → App registrations → your app → API permissions. Confirm each row is Application and its status reads Granted for <tenant>. Grant admin consent again if not.
AADSTS7000215: Invalid client secret provided The value pasted into MigrationFox is the Secret ID (a GUID) rather than the secret Value, or the secret has expired. Create a new secret in Certificates & secrets, copy the Value column (not the Secret ID), and update the connection.
AADSTS700016: Application not found in the directory The tenant ID points at a different tenant than the one the app is registered in, or the client ID is from a different app. Copy the Directory (tenant) ID and Application (client) ID again from the app's Overview page. Both must come from the same app registration.
The connection works but one specific mailbox returns 403 An application access policy limits Mail.ReadWrite to a mail-enabled security group, and that mailbox is outside the group. Add the mailbox to the policy's group, or widen the policy. See Microsoft's Limit mailbox access guide.
A pair fails with a mailbox-not-found error The destination user doesn't exist, isn't licensed for Exchange Online, or the mailbox hasn't been provisioned yet. License the user, wait for the mailbox to appear (Get-Mailbox), then run that pair again in a new migration.

Security and operational notes

This credential grants tenant-wide mailbox access

An app with the Mail.ReadWrite Application permission can read and change every mailbox in the tenant, including executives' mailboxes. Treat the client secret like an administrator password: keep it in a secrets vault, limit who can see it, and rotate it on a schedule. Entra ID sign-in logs (filtered by the Application ID) show when and where the app was used. When the migration project ends, delete the client secret so the app can no longer authenticate.

Related