Migrate Outlook Notes and Contacts from a .pst file into a Google Workspace
account, preserving the original PST folder structure as labels in Google
Keep and Google Contacts. Single-window Windows GUI, no Outlook install
required.
- Notes (
IPM.StickyNote,IPM.VoiceNotes) → Google Keep, with per-folder Keep labels. - Contacts (
IPM.Contact) → matched to existing Google Contacts and placed in Google Contact Groups matching the PST folders. Unmatched contacts optionally created under an Unmatched Contacts label for manual sorting. - Fully idempotent: dry-run mode, dedup on re-runs, exponential backoff on rate limits, resume from log for the Keep upload.
- Every label name is read from the PST — no hard-coding, works with any user's folder layout.
Pre-built Windows executable: see the Releases page. The
.exe is ~43 MB, single-file, no installer. Bundles a private Python
runtime plus every dependency below — nothing else to install.
- Set up Google-side prerequisites (once per Workspace, see below).
- Download
PSTToGoogle.exefrom the latest release. - Double-click it. Windows SmartScreen may ask you to confirm an unsigned binary → More info → Run anyway.
- Pick a tab, fill in the fields, click Dry run first, then Apply.
Extracts Sticky Notes and Voice Notes from a Notes PST, writes them to a
JSON array, then uploads each note as a Keep note via the official Keep
API. Folder path is prepended to the note body as [FolderName] so the
original context survives Google Keep's flat structure.
Auth: Google service account with domain-wide delegation for scope
https://www.googleapis.com/auth/keep, impersonating the target Workspace
user.
The official Keep API does not expose labels. This tab uses the unofficial
gkeepapi library, which requires the
target user's own credentials — captured through an in-app Google sign-in
window (Edge WebView2 with an Android user-agent). The oauth_token
cookie is automatically exchanged for a reusable Master Token via
gpsoauth.
Once signed in, the tab reads the folder metadata from the same _notes.json
produced by Tab 1 and applies each PST folder name as a Keep label to the
notes with matching titles. Notes already carrying the label are skipped.
Extracts contacts from a Contacts PST, matches each entry to an existing Google Contact by email (fallback: unique full name), creates any missing Google Contact Groups, and adds the matches to their corresponding group. Optionally creates the still-unmatched PST entries as new Google Contacts under an Unmatched Contacts label for later manual sorting.
Auth: Same service account as Tab 1, with an additional DWD scope of
https://www.googleapis.com/auth/contacts.
-
Create a GCP project (or use an existing one) and enable:
-
Create a service account. Download its JSON key.
-
In Google Workspace Admin console → Security → Access and data control → API controls → Manage domain-wide delegation, authorise the service account's Client ID for both scopes:
https://www.googleapis.com/auth/keep, https://www.googleapis.com/auth/contacts
The Keep Labels tab (Tab 2) does not use the service account — labels are an unofficial capability and require the account owner's own sign-in, performed interactively inside the app.
git clone https://github.com/<your-user>/PSTToGoogle
cd PSTToGoogle
python -m pip install -r requirements.txt pyinstaller
build.bat # produces dist\PSTToGoogle.exeOr run the source directly without building:
python launcher.pyPython 3.10+ recommended. Tested on Python 3.13 and 3.14.
Every label is derived from the folder tree in the source PST. The extractor walks the tree, skips well-known Outlook shell containers (Top of Outlook data file, Personal Folders, Recipient Cache, GAL Contacts, UUID-named folders, Deleted Items, Sync Issues, etc.), and treats every remaining leaf name as a label. This works regardless of Outlook version or interface language — nothing about folder names is hard-coded.
Item type is detected from the MAPI PR_MESSAGE_CLASS property, not
from folder names, so a "Notes" folder in a French locale
("Notes personnelles") is picked up correctly.
- Every write-side action has a Dry run checkbox that reports what would happen without touching Google.
- Every upload/label call uses idempotent APIs (
resourceNamesToAddfor contact groups,notes.createwith dedup pre-flight for Keep). Re-runs never create duplicates. - Keep upload retries on HTTP 429/5xx with exponential backoff (up to ~5 min per note across 7 attempts) and adaptively slows the base rate as it sees rate limiting. A resume log lets a killed run pick up exactly where it left off.
- Contact group modifications batch at 900 members per request (People API caps at 1000) with the same backoff treatment.
This project stands on the shoulders of several open source libraries:
- libpff / libpff-python (pypff)
by Joachim Metz — reads and parses Outlook
.pstfiles. MIT-style (LGPLv3 for the C library, Python bindings distributed aslibpff-pythonon PyPI). - google-api-python-client by Google — the Google API discovery client used for the Keep API and the People API. Apache 2.0.
- google-auth by Google — service account and domain-wide delegation credentials. Apache 2.0.
- gkeepapi by Kai Woolley — unofficial Google Keep client used exclusively for the label-writing path, since the official Keep API has no label field. MIT.
- gpsoauth by Simon Weber —
Android-style OAuth token exchange used to convert the browser
oauth_tokencookie into a reusable Master Token forgkeepapi. MIT. - pywebview by Roman Sirokov and contributors — embedded browser window used for the in-app Google sign-in flow. BSD-3-Clause.
- browser_cookie3 by Boris Babic and contributors — fallback cookie extraction from local Chrome / Edge / Brave / Firefox profiles. LGPL-3.0.
- PyInstaller — packages the app into a single Windows executable. GPL-2.0 with a runtime exception allowing distribution of bundled binaries.
The project was scaffolded from an earlier general-purpose PST-to-JSON
converter (convert-psts) and rewritten around a Google Workspace
migration workflow.
- Personal
@gmail.comaccounts are not supported. Keep API and People API only accept impersonation of Google Workspace accounts. The Keep Labels tab additionally requires Workspace because Google's anti-webview fingerprinting typically blocks the embedded sign-in flow for personal accounts. - Keep API has no label endpoint. That's the entire reason Tab 2 exists as a separate flow with a different auth path. There is no workaround inside Google's supported surface.
- Keep write rate limit is tight (roughly 60 writes/minute/user in practice). The default 1.2 s delay per note is a safe starting point; bump to 4–5 s if you hit sustained 429s.
- Google Contact Groups are flat. Nested PST folders are flattened to their leaf name; if two nested folders have the same leaf they will merge.
.exeis unsigned. Windows SmartScreen and some antivirus tools will flag it. Right-click → Properties → Unblock, or use More info → Run anyway in the SmartScreen prompt. Code-signing certificates are expensive and this is a one-person open-source project.
MIT. Use it, fork it, ship it. No warranty.