Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PSTToGoogle

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.

Download

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.

Quick start

  1. Set up Google-side prerequisites (once per Workspace, see below).
  2. Download PSTToGoogle.exe from the latest release.
  3. Double-click it. Windows SmartScreen may ask you to confirm an unsigned binary → More info → Run anyway.
  4. Pick a tab, fill in the fields, click Dry run first, then Apply.

Tabs

1. Notes → Google Keep

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.

2. Keep Labels

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.

3. Contact Labels

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.

Google setup (one-time)

  1. Create a GCP project (or use an existing one) and enable:

  2. Create a service account. Download its JSON key.

  3. 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.

Building from source

git clone https://github.com/<your-user>/PSTToGoogle
cd PSTToGoogle
python -m pip install -r requirements.txt pyinstaller
build.bat          # produces dist\PSTToGoogle.exe

Or run the source directly without building:

python launcher.py

Python 3.10+ recommended. Tested on Python 3.13 and 3.14.

Automatic label detection

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.

Safety and rate limiting

  • Every write-side action has a Dry run checkbox that reports what would happen without touching Google.
  • Every upload/label call uses idempotent APIs (resourceNamesToAdd for contact groups, notes.create with 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.

Credits

This project stands on the shoulders of several open source libraries:

  • libpff / libpff-python (pypff) by Joachim Metz — reads and parses Outlook .pst files. MIT-style (LGPLv3 for the C library, Python bindings distributed as libpff-python on 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_token cookie into a reusable Master Token for gkeepapi. 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.

Known limits

  • Personal @gmail.com accounts 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.
  • .exe is 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.

License

MIT. Use it, fork it, ship it. No warranty.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages