OAuth Token Refresh & Google Sheets Integration: Automating Monthly Revenue Reports for Ship Captain Crew
What Was Done
We deployed a patched OAuth token-refresh mechanism to the JADA operations EC2 instance to enable reliable, long-lived Google Sheets and Gmail API access. This unblocked automated generation of monthly revenue reports—specifically, a templated Excel workbook that pulls real charter data from DynamoDB, enriches it with booking context from Gmail, and delivers it via email to stakeholders.
The core problem: the existing reauth_google.py script on the box was failing to refresh expired tokens, which meant any long-running or scheduled task that touched Gmail or Sheets would eventually hit a 401. The fix involved patching the token-refresh logic, redeploying it, validating the refresh cycle, and then building the first end-to-end monthly report workflow to prove it works.
Technical Details
Token Refresh Patch
The original reauth_google.py (located at ~/repos/reauth_google.py on the EC2 box) was hitting a token-expiry wall. Rather than manually re-authenticate every month, we patched the script to properly handle the OAuth2 refresh-token grant flow.
Key changes:
- Fixed the refresh logic to correctly invoke
google.oauth2.service_account.Credentials.refresh()with a freshRequest()transport object - Ensured the patched script preserves the refresh token across refreshes so subsequent calls don't require re-auth
- Validated syntax on the box before deploying to production (
python3 -m py_compile reauth_patched.py) - Backed up the original before replacing (
cp reauth_google.py reauth_google.py.bak)
After deployment, we confirmed token refresh by running:
python3 ~/repos/reauth_google.py
# Then verified the master Sheets doc was accessible and listed all tabs
Excel Workbook Generation
Built /tmp/build_sheet.py to generate a templated Excel workbook with the following workflow:
- Query DynamoDB: Read all charter records from the
crew-dispatchtable (region: us-east-1) to extract May charters withtotal_amountfields - Enrich from Gmail: For each charter, search Gmail for booking confirmation emails from the guest name to pull additional context (guest list, special notes, final confirmation date)
- Compute reportable revenue: Apply the business rule that only charters with a confirmed payment status and revenue amount >= threshold are included
- Format as Excel: Use
openpyxlto build a multi-tab workbook matching the house format (one tab per month, with totals row, footnotes, and currency formatting) - Upload to Drive: Push the completed workbook to Google Drive (specific folder ID in the JADA business folder) and make it available via the shared Sheets master doc
The script reads a prior report to understand the exact column headers and number-format rules, ensuring consistency month-to-month.
Email Delivery
Built /tmp/send_statement.py to:
- Search Gmail for prior statement recipients (e.g., messages with subject containing "Monthly Statement") to extract a distribution list
- Attach the newly generated Excel file
- Send with an HTML body summarizing the reporting period, total revenue, and any notes from the master sheet footnotes
The script also validates recipient addresses before sending and logs delivery status for audit purposes.
Infrastructure & Data Flow
DynamoDB Tables:
crew-dispatch(us-east-1): Contains charter records with fieldscharter_id,sail_date,total_amount,guest_names,payment_statuscrew-roster(us-east-1): Captain and crew availability; used for cascade crew-call logic
Google Services:
- Sheets: Master workbook in the JADA business folder; accessed via
drive.files().list()andsheets.spreadsheets().values().get() - Gmail: Search queries for booking confirmations and prior statements; read via
gmail.users().messages().list()with search filters - Drive: Monthly report workbooks uploaded as Excel files; accessible via folder ID queries
EC2 Instance:
- Host:
ubuntu@34.239.233.28(t3.medium, us-east-1) - Repos:
~/repos/contains production scripts and credential store (~/.secrets/) - Credentials: Stored as encrypted JSON (Google service-account key, AWS IAM role via instance metadata)
Key Decisions
Why DynamoDB + Gmail enrichment? The crew-dispatch table is the source of truth for charters, but guest context and confirmation dates live in email. Rather than duplicate data, we query both systems in real time. This keeps the report fresh and means no additional data-entry burden.
Why openpyxl instead of pandas/xlsxwriter? Pandas has a heavier dependency footprint; we needed fine-grained control over cell formatting (currency, date formats) and multi-sheet workbooks. openpyxl gives us that control with minimal overhead and is already installed on the box.
Why patch the token-refresh in place rather than use a cron job? The business requirement is that reports must be generated on demand (when requested by a stakeholder) and also on a scheduled basis (month-end). A long-lived token (refreshed transparently) is simpler than a scheduled re-auth cron job, and it means any ad-hoc script that touches Gmail/Sheets just works without special setup.
Why distribute via email + Drive? Email ensures stakeholders get the report in their inbox with a known filename; Drive link in the email body lets them access historical copies. This dual approach supports both real-time notification and long-term audit trails.
What's Next
We're now ready to:
- Schedule monthly generation: Add a cron job to
/etc/cron.d/that runs the report script on the last business day of each month - Extend to crew dispatch: Use the same token-refresh pattern to enable automated crew-call emails based on the captain cascade rule stored in DynamoDB
- Add metrics: Log report generation time, row counts, and email delivery status to CloudWatch for visibility into the automation health
- Iterate on templates: Collect feedback from stakeholders on column layout and footnote requirements; update the template function in the script as needed
The token-refresh patch is the foundation; everything else—cascading crew calls, dynamic crew availability, instant statement generation—now flows from a reliable, refreshed OAuth