Skip to main content

Configure Google Cloud for your deployment

Prepare Google Cloud for a MailGen deployment with personal Gmail or approved Workspace access. This guide produces separate OAuth client credentials and optional authenticated push routing.

Read first installation for the local machine and account login setup for application identity. Google app login and Gmail mailbox consent are separate. Gmail uses OAuth, not a simple API key.

Cloud Shell commands run in Google's hosted terminal. Commands marked local machine run in Bash from your downloaded motherrepo. You do not need a local Google Cloud CLI.

1. Create a project and choose addresses​

  1. Open Google Cloud Console with the operator account.
  2. Use the project selector and New project. Choose the available organization/location and create it. Personal accounts can use No organization.
  3. Select the new project and record Dashboard → Project info → Project ID. This differs from display name and numeric Project number.
  4. Link billing if the selected services require it. Inspect costs and set budget alerts. An alert is not an automatic spending cap.
  5. Prepare this deployment's public HTTPS ingress for OAuth and push routes. Localhost and .local cannot receive Google push callbacks.
ValueMeaning
PROJECT_IDCloud project identifier
PUBLIC_ORIGINYour public HTTPS origin without trailing slash
BFF_ORIGINActual browser application HTTPS origin, including port
Mailbox callbackPUBLIC_ORIGIN/v1/oauth/callback
App-login callbackPUBLIC_ORIGIN/api/auth/callback/google
Push endpoint and audiencePUBLIC_ORIGIN/v1/pubsub/push
Mailbox completionBFF_ORIGIN/api/google-mailbox/complete. Do not register it as Google's redirect.

Unified BFF typically uses https://bff.YOUR_INSTANCE.ecas.local:8443. The public ingress is separate. Preserve deployment UUID, OAuth clients, and encryption keys when moving existing data.

2. Enable APIs​

In APIs & Services → Library, open each required API and select Enable. Use the mailbox client's project.

APIIdentifierNeeded for
Gmailgmail.googleapis.comMail access
Peoplepeople.googleapis.comContacts
Calendarcalendar-json.googleapis.comCalendar availability
Cloud Pub/Subpubsub.googleapis.comPush notifications
Admin SDKadmin.googleapis.comApproved Workspace directory access only

Alternatively, open Activate Cloud Shell in the top bar. Approve its access when prompted. Start Bash and replace the example project/domain values:

bash
set -euo pipefail
PROJECT_ID='YOUR_PROJECT_ID'
PUBLIC_ORIGIN='https://YOUR_PUBLIC_DOMAIN'
TOPIC_ID='mailgen-gmail-watch'
SUBSCRIPTION_ID='mailgen-gmail-push'
PUSH_ACCOUNT_ID='mailgen-pubsub-push'
gcloud config set project "$PROJECT_ID"
gcloud services enable gmail.googleapis.com people.googleapis.com \
calendar-json.googleapis.com pubsub.googleapis.com --project="$PROJECT_ID"
gcloud services list --enabled --project="$PROJECT_ID"

The Console and shell routes perform the same preparation. Run subsequent Cloud Shell blocks in this session. set -e stops the shell after a failure. In a new shell, restore these variables before continuing.

PERMISSION_DENIED means the operator needs project/API, Pub/Sub resource, or service-account IAM access. Mailbox consent does not grant Cloud administration.

In Google Auth Platform → Overview, complete Get started if necessary. Set Branding application name, support email, and developer contact. Register your domain where requested and complete ownership checks.

Choose External for personal Gmail. Internal permits only the selected Workspace organization. For development, keep Testing and add each Google account under Test users.

Under Data Access, add the required scopes:

PurposeScope
Identityopenid, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile
Read and change mailhttps://www.googleapis.com/auth/gmail.modify
Contactshttps://www.googleapis.com/auth/contacts
Calendar availabilityhttps://www.googleapis.com/auth/calendar.events.freebusy

The application can request short OIDC names email and profile. Console can display their full userinfo URIs. App login requests identity only. Mailbox connection separately requests mailbox scopes.

External Testing refresh tokens with Gmail scopes generally expire after seven days. For sustained use, publish through the required production process and complete Google's applicable verification. gmail.modify is restricted. Public server-side data use can also require a security assessment. Publishing alone is not approval.

4. Create separate Web OAuth clients​

Under Google Auth Platform → Clients → Create client, choose Web application.

For the mailbox client, register exact PUBLIC_ORIGIN/v1/oauth/callback. Save its Client ID and Client secret securely at creation. The secret might not remain visible later. Do not reuse the app-login client.

Create a second Web application for app login with exact PUBLIC_ORIGIN/api/auth/callback/google. If users sign in with Microsoft and only connect Gmail, you can omit this client and leave GOOGLE_APP_LOGIN_ENABLED=false.

Authorized JavaScript origins do not replace redirect URIs. Avoid wildcard/callback paths there. Redirect registration must match HTTPS, domain, port, path, and trailing slash exactly.

On the local machine, add one assignment per variable to the root .env:

GOOGLE_APP_LOGIN_ENABLED=true
BFF_GOOGLE_LOGIN_CLIENT_ID=APP_LOGIN_CLIENT_ID.apps.googleusercontent.com
BFF_GOOGLE_LOGIN_CLIENT_SECRET='APP_LOGIN_CLIENT_SECRET'
GOOGLE_APP_LOGIN_CALLBACK_URL=https://YOUR_PUBLIC_DOMAIN/api/auth/callback/google
GOOGLE_APP_LOGIN_PROXY_SECRET='YOUR_SEPARATE_PROXY_SECRET'
GOOGLE_MAILBOX_OAUTH_ENABLED=true
GOOGLE_MAILBOX_SYNC_ENABLED=true
GOOGLE_MAILBOX_WATCH_ENABLED=false
GOOGLE_MAILBOX_CLIENT_ID=MAILBOX_CLIENT_ID.apps.googleusercontent.com
GOOGLE_MAILBOX_CALLBACK_URL=https://YOUR_PUBLIC_DOMAIN/v1/oauth/callback
GOOGLE_MAILBOX_BFF_COMPLETION_URL=https://bff.YOUR_INSTANCE.ecas.local:8443/api/google-mailbox/complete
ECAS_DEPLOYMENT_TENANT_ID=YOUR_STABLE_UUID

For a new Google-only deployment, generate the UUID with python3 -c 'import uuid; print(uuid.uuid4())'. Generate the separate proxy secret with openssl rand -hex 32.

Set MICROSOFT_CONNECTOR_ENABLED=false for Google-only and leave Microsoft/Graph fields empty. Mixed deployments retain their existing tenant identity and Microsoft settings. The full worker selection uses ECS_MEMORY_GIB=44, plus host/build reserves from capacity requirements.

On the local machine, save mailbox secrets without putting their text in shell history:

umask 077
mkdir -p .docker-state/google
chmod 700 .docker-state/google
if [ -e .docker-state/google/oauth-client-secret ]; then
printf '%s\n' 'Mailbox secret already exists. Preserve it or rotate credentials deliberately.'
else
read -r -s -p 'Mailbox Client secret: ' mailbox_secret
printf '\n'
test -n "$mailbox_secret" && printf '%s' "$mailbox_secret" > .docker-state/google/oauth-client-secret
unset mailbox_secret
fi
if [ ! -e .docker-state/google/mailbox-grant.key ]; then
openssl rand -base64 32 > .docker-state/google/mailbox-grant.key
fi
chmod 600 .docker-state/google/oauth-client-secret .docker-state/google/mailbox-grant.key

The client-secret file contains raw secret text, not downloaded OAuth JSON. Do not invent GOOGLE_MAILBOX_CLIENT_SECRET as a root secret input. The runtime reads the private file.

The grant key encrypts server-side OAuth grants. It is not the personal email key. Preserve its original value with retained grants. See separate secret backups.

5. Create authenticated Gmail push routing​

Skip this section for polling only and leave watch disabled. For push, use the mailbox OAuth client's project. Choose Console or shell creation, not both. Existing resources need inspection rather than repeated create commands.

Console route​

Create a Pub/Sub topic such as mailgen-gmail-watch without a default subscription. On that topic, grant Pub/Sub Publisher to gmail-api-push@system.gserviceaccount.com. Organization domain restrictions can require an exception.

Create a service account such as mailgen-pubsub-push. It needs no broad project role or private JSON key. Record its email and numeric Unique ID, distinct from the Project number.

Create/enable the Pub/Sub service agent with gcloud beta services identity create --service=pubsub.googleapis.com --project="$PROJECT_ID". On the push service account, grant Service Account Token Creator to service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com.

Grant the subscription creator Service Account User on that push account for actAs. Then create the subscription:

FieldValue
TopicThe prepared topic
DeliveryPush
Endpoint URLExact PUBLIC_ORIGIN/v1/pubsub/push
AuthenticationEnabled, selected push service account
AudienceExact same push endpoint
Payload unwrappingDisabled
ExpirationNever expire
Message retentionSeven days

Cloud Shell route​

Use the variables from section 2:

PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
PUSH_SA="${PUSH_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com"
PUSH_ENDPOINT="${PUBLIC_ORIGIN}/v1/pubsub/push"
OPERATOR_EMAIL="$(gcloud config get-value account)"
gcloud beta services identity create --service=pubsub.googleapis.com --project="$PROJECT_ID"
gcloud pubsub topics create "$TOPIC_ID" --project="$PROJECT_ID"
gcloud pubsub topics add-iam-policy-binding "$TOPIC_ID" --project="$PROJECT_ID" \
--member='serviceAccount:gmail-api-push@system.gserviceaccount.com' --role=roles/pubsub.publisher
gcloud iam service-accounts create "$PUSH_ACCOUNT_ID" --project="$PROJECT_ID" \
--display-name='MailGen authenticated push'
gcloud iam service-accounts add-iam-policy-binding "$PUSH_SA" --project="$PROJECT_ID" \
--member="serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com" \
--role=roles/iam.serviceAccountTokenCreator
gcloud iam service-accounts add-iam-policy-binding "$PUSH_SA" --project="$PROJECT_ID" \
--member="user:${OPERATOR_EMAIL}" --role=roles/iam.serviceAccountUser
gcloud pubsub subscriptions create "$SUBSCRIPTION_ID" --project="$PROJECT_ID" \
--topic="$TOPIC_ID" --push-endpoint="$PUSH_ENDPOINT" \
--push-auth-service-account="$PUSH_SA" --push-auth-token-audience="$PUSH_ENDPOINT" \
--message-retention-duration=7d --expiration-period=never

Do not add --push-no-wrapper. The server expects the normal JSON envelope. These commands use the current operator user. Service-account impersonation uses a different IAM principal.

Generate the routing JSON​

In Cloud Shell, supply the public mailbox Client ID. Console-route users must also define the variables from section 2:

MAILBOX_CLIENT_ID='YOUR_MAILBOX_CLIENT_ID.apps.googleusercontent.com'
PUSH_SA="${PUSH_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com"
PUSH_ENDPOINT="${PUBLIC_ORIGIN}/v1/pubsub/push"
PUSH_SUBJECT="$(gcloud iam service-accounts describe "$PUSH_SA" \
--project="$PROJECT_ID" --format='value(uniqueId)')"
export PROJECT_ID TOPIC_ID SUBSCRIPTION_ID PUSH_SA PUSH_ENDPOINT PUSH_SUBJECT MAILBOX_CLIENT_ID
python3 - <<'PY'
import json
import os
routing = [{
'topicName': f"projects/{os.environ['PROJECT_ID']}/topics/{os.environ['TOPIC_ID']}",
'subscriptionId': f"projects/{os.environ['PROJECT_ID']}/subscriptions/{os.environ['SUBSCRIPTION_ID']}",
'pushAudience': os.environ['PUSH_ENDPOINT'],
'pushServiceAccountEmail': os.environ['PUSH_SA'],
'pushServiceAccountSubject': os.environ['PUSH_SUBJECT'],
'oauthClientId': os.environ['MAILBOX_CLIENT_ID'],
}]
print("GOOGLE_PUBSUB_CONFIG_JSON='" + json.dumps(routing, separators=(',', ':')) + "'")
PY

Copy the complete printed assignment into your local root .env. Set GOOGLE_MAILBOX_WATCH_ENABLED=true with GOOGLE_MAILBOX_SYNC_ENABLED=true. Extend existing routing arrays instead of replacing other projects.

Each entry identifies a full topic/subscription pair in one project, exact audience, service-account email, numeric subject, and OAuth client or Workspace credential reference. Runtime accepts 1–50 entries and rejects duplicate subscription IDs. The backend checks Google's signed OIDC token. No push service-account private key is needed.

6. Check routing and start​

In Cloud Shell, inspect IAM and exact subscription routing:

gcloud pubsub topics get-iam-policy "$TOPIC_ID" --project="$PROJECT_ID"
gcloud iam service-accounts get-iam-policy "$PUSH_SA" --project="$PROJECT_ID"
gcloud pubsub subscriptions describe "$SUBSCRIPTION_ID" --project="$PROJECT_ID" \
--format='yaml(name,topic,pushConfig,expirationPolicy,messageRetentionDuration)'

The OIDC email/audience must match the JSON. Keep payload wrapping enabled. Never-expire means no expiration TTL. Ask the project operator for missing IAM permissions instead of weakening authentication.

On the local machine, continue first installation or use ordinary lifecycle for a configured deployment. Flags select OAuth bridge, onboarding/history polling, and push ingress profiles independently of app login.

Connect the personal Gmail mailbox​

Use a signed-in MailGen session whose actor has accounts.manage permission. The Google mailbox OAuth bridge must be enabled and healthy.

  1. Open /onboarding on the BFF HTTPS address.
  2. Under Connect your mailbox, select Continue with Google.
  3. Approve the dedicated mailbox consent for Gmail, contacts, and calendar availability. Google application login is a separate identity grant.
  4. Keep the same MailGen session through the callback. /api/google-mailbox/complete returns to /onboarding and selects the connected Google account.
  5. Wait at Prepare your mailbox until Opening workspace… appears and the workspace opens.

Google initial synchronization starts automatically. The page says Syncing your mailbox. MailGen opens when mail and search are ready. This flow uses Google progress and recovery, independently of the Microsoft initial-import dispatcher.

If requested, select Open email key setup and follow personal-key backup and unlock. A new owner creates and downloads the key backup. For existing encrypted content, import the original owner's backup.

If setup pauses, select Retry onboarding only when offered. For denied access, check connector permissions with your administrator. For an encryption lock, restore the correct original key before retrying. For a session mismatch or expired callback, restart the Google connection from /onboarding in the current session. Do not replay a saved callback URL.

Send an approved test message to the connected mailbox. Check its arrival, body, and synchronization state. With watch enabled, also inspect Pub/Sub delivery metrics. Resource creation or an OAuth return alone does not prove completed synchronization. See first login.

MailGen creates and renews Gmail mailbox watches against the prepared topic. The Cloud subscription is a separate routing resource.

Workspace-managed access additionally needs domain-wide delegation, approved scopes, and explicit mailbox enrollment. Enabling Admin SDK alone is insufficient. Use Workspace configuration.

Official contracts: OAuth consent, Web OAuth credentials, Gmail push, authenticated Pub/Sub push, and subscription create.