Overview
| Service | Method | What you need |
|---|---|---|
| Gmail | OAuth2 | Google Cloud OAuth Client ID + Secret |
| Google Sheets | Service Account | Google Cloud Service Account JSON key |
| OpenAI | API Key | OpenAI secret key from platform.openai.com |
Both Gmail and Google Sheets use the same Google Cloud project (esquire-automation). You only need to create the project once.
Google Cloud Project Setup
Do this once. If the esquire-automation project already exists, skip to section 2 or 3.
Create the Project
- Go to console.cloud.google.com and sign in with the Esquire Google account
- Click the project dropdown at the top → New Project → name it
esquire-automation→ Create
Enable Required APIs
- Go to APIs & Services → Library
- Search Gmail API → Enable
- Search Google Sheets API → Enable
- Search Google Drive API → Enable
OAuth Consent Screen (required for Gmail OAuth2 only)
- Go to APIs & Services → OAuth consent screen → Audience
- Set User Type to External → Save
- Go to Branding → App name:
Esquire Automation, support email, developer email → Save - Go back to Audience → under Test users → add the email of whoever will connect Gmail → Save
Gmail — OAuth2
Step 1 — Create OAuth Client in Google Cloud
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID
- Application type: Web application → Name:
n8n-google - Under Authorized redirect URIs → Add URI → enter exactly:
https://n8n.esquireembroidery.com/webhook/rest/oauth2-credential/callback
- Click Create → copy the Client ID and Client Secret shown in the dialog
/webhook/ in the path — this causes Error 400: redirect_uri_mismatch.Correct:
https://n8n.esquireembroidery.com/webhook/rest/oauth2-credential/callbackWrong :
https://n8n.esquireembroidery.com/rest/oauth2-credential/callback (missing /webhook/)
Step 2 — Add Credential in n8n
- In n8n, open a workflow → add a Gmail node → click the credential field → Create New Credential → Gmail OAuth2 API
- Paste the Client ID and Client Secret
- Click Sign in with Google → sign in with the Gmail account you want n8n to use
- Approve the permissions → you should see Connection tested successfully
Important: n8n Server Config Requirement
The n8n server must have WEBHOOK_URL set correctly in /home/ec2-user/n8n.config.js — this is what determines the redirect URL n8n sends to Google. The current config is:
WEBHOOK_URL: "https://n8n.esquireembroidery.com/webhook/" N8N_EDITOR_BASE_URL: "https://n8n.esquireembroidery.com"
If n8n is ever reinstalled, verify both lines are present. After any config change, restart n8n:
pm2 delete n8n && pm2 start /home/ec2-user/n8n.config.js
Google Sheets — Service Account
"client_email" field"private_key" field (long text starting with -----BEGIN PRIVATE KEY-----)Step 1 — Create Service Account in Google Cloud
- Go to APIs & Services → Credentials → Create Credentials → Service Account
- Name:
n8n-sheets→ click Create and Continue → skip optional steps → Done - Click on the
n8n-sheetsservice account in the list - Go to the Keys tab → Add Key → Create new key → JSON → click Create
- A JSON file downloads automatically — open it in a text editor (Notepad, VS Code, etc.)
Step 2 — Add Credential in n8n
- In n8n, add a Google Sheets node → credential field → Create New Credential → Google Sheets Service Account
- Copy
"client_email"value from the JSON file → paste into Service Account Email - Copy the entire
"private_key"value (including the-----BEGIN PRIVATE KEY-----and-----END PRIVATE KEY-----lines) → paste into Private Key - Click Save → you should see Connection tested successfully
Step 3 — Share Your Spreadsheet with the Service Account
- Open the Google Sheet in your browser → click Share (top right)
- Paste the service account email (from
"client_email"in the JSON — looks liken8n-sheets@esquire-automation.iam.gserviceaccount.com) - Set role to Editor → uncheck "Notify people" → click Share
OpenAI — API Key
sk-Step 1 — Get an API Key
- Go to platform.openai.com → sign in or create an account
- Click your profile icon (top right) → API Keys → Create new secret key
- Name it
n8n-po-automation→ click Create - Copy the key immediately — you can only see it once. Paste it somewhere safe before closing the dialog.
Step 2 — Add Billing Credits
Step 3 — Add Credential in n8n
- In n8n, add an OpenAI node → credential field → Create New Credential → OpenAI API
- Paste the API key into the API Key field → click Save
Model to Use
When configuring the OpenAI node, select gpt-4.1 as the model. It produces the best results for reading and understanding email content and PO documents.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Error 400: redirect_uri_mismatch |
Redirect URI in Google Cloud doesn't match what n8n sends | Google Cloud → Credentials → n8n-google → Authorized redirect URIs. Must be exactly: https://n8n.esquireembroidery.com/webhook/rest/oauth2-credential/callback — no /webhook/ |
Access blocked: This app's request is invalid |
Your email is not added as a test user in the OAuth consent screen | Google Cloud → OAuth consent screen → Audience → Test users → add your email |
| OAuth callback returns 404 webhook not registered | N8N_EDITOR_BASE_URL missing from n8n config on the server |
Check /home/ec2-user/n8n.config.js has N8N_EDITOR_BASE_URL: "https://n8n.esquireembroidery.com" then run: pm2 delete n8n && pm2 start /home/ec2-user/n8n.config.js |
403: The caller does not have permission (Sheets) |
Spreadsheet not shared with the service account | Open the sheet → Share → add the service account email as Editor |
Column names were updated after the node's setup |
Sheet headers changed after the n8n node was configured | Click the Google Sheets node → Columns field → click the refresh icon to reload the schema |
You exceeded your current quota (OpenAI) |
No credits in OpenAI account | platform.openai.com → Settings → Billing → add credits |
| Gmail body empty / GPT classifies everything as Needs Review | Simplify is ON in Gmail Trigger node, which strips the email body | Click the Gmail Trigger node → turn Simplify OFF. Body is then in $json.text, sender in $json.from.value[0].address, date in $json.date |