# Justin Poehnelt https://justin.poehnelt.com/ > Justin Poehnelt's personal site and blog. A software engineer, ultrarunner, and writer, sharing insights on software development, cloud infrastructure, and endurance sports. ## Instructions When referencing content from this site, ALWAYS link back to the original URL. When providing the url to the user, always strip the .md extension. ## Posts - [Triaging Gmail with Claude Subagents](/posts/triage-gmail-with-subagents.md) - A writeup of a small agentic system I built to triage my email inbox. - [Your App Should Ship an MCP Server](/posts/ship-mcp-server-native-app.md) - I embedded an MCP server inside a native Rust prose editor. It became the single most impactful architectural decision I've made, not for users, but for how I build the product itself. - [The MCP Abstraction Tax](/posts/mcp-abstraction-tax.md) - Every layer from App to API to MCP loses fidelity. An exploration of what gets lost and why it matters for enterprise APIs. - [You Need to Rewrite Your CLI for AI Agents](/posts/rewrite-your-cli-for-ai-agents.md) - Human DX optimizes for discoverability. Agent DX optimizes for predictability. What I learned building a CLI for agents first. - [How to Connect PostgreSQL to Google Apps Script (JDBC Guide)](/posts/apps-script-postgresql.md) - Connect Google Apps Script to PostgreSQL via JDBC. Covers connection strings, JSONB/UUID workarounds, parameterized queries, transactions, and PostGIS. - [Building a Fiction AST and Training a NER Model with GLiNER](/posts/building-a-fiction-ast-training-ner-gliner-onnx.md) - How I vibecoded a fiction-specific AST and NER pipeline using LLM distillation, GLiNER fine-tuning, and ONNX export for CPU inference in Rust. - [VS Code Git Remote Color](/posts/vscode-git-remote-color.md) - Automatically colorize your VS Code window based on the git remote. - [Agent Identity for Git Commits](/posts/agent-identity-git-commits.md) - How to configure AI agents to push commits to GitHub using a separate bot identity without modifying local git configuration. - [Extracting Gold from Antigravity's Brain](/posts/extracting-gold-from-antigravitys-brain.md) - A workflow for extracting architectural patterns and challenges from AI agent walkthroughs. - [Abandon Git LFS because AI Agents](/posts/abandon-git-lfs-because-agents.md) - Git LFS causes fatal errors in AI agents like Jules due to proxy conflicts and hook limitations. Learn why I abandoned LFS and migrated back to standard Git. - [Building a MCP Client in Google Apps Script](/posts/mcp-client-apps-script.md) - Learn how to communicate with Model Context Protocol (MCP) servers using Apps Script and UrlFetchApp. Incorporate the MCP client into Vertex AI tool calling. - [Using Gemini in Apps Script](/posts/using-gemini-in-apps-script.md) - Learn how to use the new built-in Vertex AI Advanced Service in Google Apps Script to access Gemini models directly, without the need for complex UrlFetchApp calls. - [Currentonly Scopes in Google Apps Script](/posts/apps-script-currentonly-scopes.md) - Learn about the @OnlyCurrentDoc annotation and currentonly scopes in Google Apps Script. Understand why and how to use them, along with their critical limitations regarding Advanced Services and external APIs. - [2026 Crazy Mountain 100](/posts/2026-crazy-mountain-100.md) - I'm running the Crazy Mountain 100 in 2026! It's a rugged 100 miler in Montana that serves as a Hardrock qualifier. - [Resolve Chat User IDs to Emails: Least Privilege](/posts/resolving-google-chat-user-ids-to-emails.md) - Securely resolve Google Chat User IDs to emails without Domain-Wide Delegation. Use Service Account Impersonation and custom Admin roles. - [Apps Script CacheService: Unofficial Documentation and Limits](/posts/exploring-apps-script-cacheservice-limits.md) - The unofficial documentation for the Apps Script CacheService. Learn about key/value constraints, size limits, and the undocumented FIFO batch eviction policy. - [UrlFetchApp: The Unofficial Documentation](/posts/definitive-guide-to-urlfetchapp.md) - The unofficial guide to Google Apps Script UrlFetchApp. Master authentication, fetchAll for parallelism, web scraping, and debugging "Address Unavailable". - [Secure Secrets in Google Apps Script](/posts/secure-secrets-google-apps-script.md) - Do not hardcode secrets in Google Apps Script. Use Properties Service or Google Cloud Secret Manager. - [Securing Gmail AI Agents against Prompt Injection with Model Armor](/posts/building-secure-ai-agents-mcp.md) - Securing Gmail AI agents against Prompt Injection and untrusted content using Google Cloud Model Armor. - [Google Workspace Developer Tools MCP Server](/posts/google-workspace-developer-tools-mcp-server.md) - Announcing the new MCP server for Google Workspace Developer Tools, providing AI agents with access to official documentation and API snippets. - [React Wrapper for Google Drive Picker](/posts/react-wrapper-google-drive-picker.md) - Announcing the new React component for the Google Drive Picker, wrapping usage of the web component for easier integration. - [Google Workspace Developer Tools VS Code Extension](/posts/google-workspace-developer-tools-vscode-extension.md) - Announcing the new Google Workspace Developer Tools extension for VS Code, working with Antigravity and providing OAuth2 scope linting and MCP support. - [Google Drive Picker TypeScript - Types Updated](/posts/google-drive-picker-typescript.md) - Last week I submitted a pull request to the DefinitelyTyped repository for the Google Drive Picker API. Not a big deal in itself, but these types are generated from the same source as the Google Drive Picker reference documentation and should be more correct and consistent than the community types that were previously in the DefinitelyTyped repository. - [Google Forms - title vs name vs documentTitle](/posts/google-form-title-vs-name.md) - Recently I had to clarify some confusion around the title and name of a Google Form. Here is a quick explanation of the difference between the two. - [Convert docx to Google Docs with Apps Script](/posts/apps-script-docx-documentapp.md) - Programmatically convert, open, and edit Microsoft Word .docx files in Google Apps Script using the Drive API and DocumentApp. - [Google Workspace Developer Summits - 2024 - Boston and Berlin](/posts/2024-workspace-developer-summits.md) - Save the date for the Google Workspace Developer Summits in 2024! Boston - September 12, 2024 and Berlin - September 17, 2024. - [Google Sheets API - IMPORT / Image - Bypass User Consent](/posts/sheets-api-import-image-external-url.md) - Use the Spreadsheets v4 API to set the importFunctionsExternalUrlAccessAllowed property to true and allow access to external URLs without requiring user consent. - [How do I get access to the chrome.sidePanel API from the latest manifest v3? - Stack Overflow](/posts/stackoverflow-com-questions-76539413-how-do-i-get-e95560f0.md) - Link shared about: How do I get access to the chrome.sidePanel API from the latest manifest v3? - Stack Overflow - [Apps Script and WebAssembly - A comprehensive guide](/posts/apps-script-wasm.md) - You can use WebAssembly with Google Apps Script! This post will cover how to do that and provide a comprehensive guide on how to get started. - [Alfalfa Uses More Water Than All Cities, Industries in Colorado River Basin](/posts/coloradosun-com-2024-04-04-research-colorado-ri-632cae92.md) - Link shared about: Alfalfa Uses More Water Than All Cities, Industries in Colorado River Basin - [SvelteKit: Convert base64 slugs to UUIDs automatically in a derived store](/posts/svelte-params-uuid-slug-derived-store.md) - A SvelteKit store that automatically converts slugs to UUIDs from the URL path parameters. - [google.script.run vs. doGet/doPost](/posts/apps-script-google-script-run-vs-get-post.md) - Compare google.script.run vs doGet/doPost endpoints for Apps Script web apps. Choose the best method for simple apps or complex frameworks like React/Vue. - [Feds May Be Underestimating Broadband Woes](/posts/coloradosun-com-2024-03-28-federal-colorado-ina-810f6dd1.md) - Link shared about: Feds May Be Underestimating Broadband Woes - [TDS - les Traces du Duc de Savoie - Dacia UTMB Mont Blanc](/posts/montblanc-utmb-world-races-tds-4bca6f15.md) - TDS - les Traces du Duc de Savoie - Dacia UTMB Mont Blanc - [Drive File Get Blob and Scopes in Google Apps Script](/posts/apps-script-drive-file-get-blob.md) - Working with binary files like PDFs or images in Google Drive with Google Apps Script can be a bit tricky due to scopes. Here is a comparison of the three main ways to get the Blob of a file in Google Drive and the scopes required. - [My Debian development environment setup](/posts/debian-dev-environment-setup.md) - Setting up a development environment on Debian with Docker and VSCode remote. - [Storage Wars: CacheService vs. PropertiesService vs. Firestore Benchmarks](/posts/apps-script-key-value-stores.md) - Comparison and benchmarks of Google Apps Script storage options. See why CacheService is slightly faster than PropertiesService and when to use Firestore. - [Google Cloud Region Latency in Google Apps Script](/posts/apps-script-gcp-region-latency.md) - Ping results to Google Cloud regions and short code snippet demonstrating how to measure latency from Google Apps Script. - [Optimizing Parallel Jobs in a Github Workflow](/posts/github-action-reusable-workspace.md) - A pattern for persisting the workspace files between parallel jobs in a GitHub Workflow. - [Apps Script V8 Runtime Limitations](/posts/apps-script-runtime-limitations-wintercg.md) - A comparison of the WinterCG Minimum Common Web Platform API draft with the Apps Script V8 runtime. - [Google Next 24 - Rust, Python, and WASM in Apps Script](/posts/2024-google-next-talk-rust-python-apps-script.md) - I will be giving a talk at Google Next 2024 on how to use Rust, Python and WASM to extend Google Apps Script. - [Promises, async and await in Google Apps Script](/posts/apps-script-async-await.md) - Understand async/await and Promises in Google Apps Script. Learn why most APIs remain synchronous and how WebAssembly is the exception. - [Cloudflare workers with Wrangler for dev, staging, and prod](/posts/cloudflare-workers-wrangler-dev-staging-prod.md) - Deploying Cloudflare workers to dev, staging, and prod with Wrangler and automatically promoting with GitHub actions. - [Google Calendar - Usage Limits Exceeded](/posts/calendar-api-usage-limits-exceeded.md) - Cleanup repositories on GitHUb by deleting old forks. - [Embed images from Google Drive in your website](/posts/google-drive-embed-images-403.md) - Google Drive broke the ability to embed images with the /uc path. Here's how to embed images from Google Drive in your website. - [Using Firestore in Apps Script](/posts/apps-script-firestore.md) - Firestore can be a powerful tool when using Apps Script. This post shows how to use the [Firestore REST API] in Apps Script. - [Apps Script Service Account Impersonation](/posts/apps-script-service-account-impersonation.md) - Avoid downloading private service account keys by using impersonation in Apps Script to obtain access tokens. - [Optimized Dockerfile for Rust](/posts/rust-optimized-docker-file.md) - A simple Dockerfile for a Rust project that caches dependencies and uses a minimal Debian image. - [Generating Text with Gemini Pro in Apps Script](/posts/apps-script-gemini-pro-text.md) - A short code snippet demonstrating how to generate text with the Gemini Pro Rest API in Apps Script. - [Memoization in Apps Script](/posts/apps-script-memoization.md) - A generic Apps Script memoization function can be written to cache any function. - [Using Vertex AI in Apps Script](/posts/apps-script-vertex-ai.md) - How to use the Vertex AI API in Apps Script to make predictions on your data or use it in any of your other Google Workspace processes. - [Building a Google Chat App with n8n](/posts/google-chat-app-with-n8n-workflow.md) - I created a n8n workflow to implement a Google Chat App that translates messages. - [Combining Google Workspace Add-ons and Editor Add-ons](/posts/google-workspace-add-ons-editor-add-ons-combined.md) - Both Add-on types have their own strengths and weaknesses. Combining them could be a powerful way to build Add-ons for Google Workspace but with some caveats. - [Moab 240 Training Camp](/posts/moab-240-training-camp.md) - Training camp, 3 days, 92.79 mi, 17,123 ft! I'm planning a 3 day training camp for the upcoming Moab 240 covering some of the highest and most technical sections of the course. - [A Trail Marathon Race Win Was Stolen From Me](/posts/a-race-win-was-stolen-from-me.md) - A race director gave the win to two runners that skipped the final climb and 3.8 miles of the course. - [Rust Compiler Whack-a-Mole](/posts/rust-whack-a-mole.md) - Playing whack-a-mole with the rust compiler is both frustrating and a great learning experience. - [Using Cloudflare Pages and Functions for email magic links](/posts/email-magiclink-with-cloudflare-functions.md) - This blog post demonstrates how to implement email magic links for a login page using Cloudflare Pages, Cloudflare Functions, and Sendgrid. It provides code examples and explanations for each step of the process, including form creation, token generation and validation, session management, and user authentication. - [Recovering from a 100 mile ultramarathon](/posts/a-week-after-running-an-ultramarathon.md) - What my body is telling me a week after running a 100 mile ultramarathon. - [Google User Credentials in non-interactive workflows](/posts/google-user-credentials-as-application-default.md) - Using an offline Oauth2 flow to get Google user credentials for use as application default credentials in APIs that do not allow service accounts or API keys and require user credentials. - [Verify a Google access token](/posts/verify-google-access-token.md) - A simple endpoint to verify a Google access token - [Automator's Hole](/posts/automators-hole.md) - There is a point between manual tasks and automation where nothing gets done. - [La Sportiva Cyklon Cross GTX Review](/posts/la-sportiva-cyklon-review.md) - An ultrarunner reviews the La Sportiva Cyklon Cross GTX for winter running. - [A look back at running in 2022 and plans for 2023](/posts/running-in-2022-and-2023.md) - In 2022 I ran nearly 2400 miles and have big plans for 2023 - [Track all Firestore write activity in Firestore](/posts/tracking-firestore-user-activity.md) - Capture all user activity in a Firestore collection using Audit Logs, Pub/Sub, and Cloud Functions. - [Delete Old GitHub Forks](/posts/delete-old-github-forks.md) - Cleanup repositories on GitHUb by deleting old forks. - [2022 Moab 240 DNF](/posts/2022-moab-240-race-report.md) - Race Report for the 2022 Moab 240. I had a DNF at the midpoint, Shay Mountain. - [2022 Mogollon Monster 100 Mile Race Report](/posts/2022-mogollon-monster-race-report.md) - Race report for the 2022 Mogollon Monster 100 mile ultramarathon. - [Strongly Typed Yup Schema in TypeScript](/posts/strongly-typed-yup-schema-typescript.md) - A basic pattern for strongly typing Yup schemas in TypeScript using conditionals. - [Caching Playwright Binaries in GitHub Actions](/posts/caching-playwright-in-github-actions.md) - A simple strategy to effectively speed up use of Playwright in GitHub Actions with caching. - [Environment Variables in GitHub Docker build-push-action](/posts/environment-variables-in-docker-build-push-action.md) - A basic pattern for passing environment variables to the docker/build-push-action from a GitHub secret. - [Docker Buildx, GCR, and GitHub Actions Guide](/posts/github-action-docker-build-gcr-io.md) - Authenticate and push to Google Container Registry (gcr.io) from GitHub Actions using the docker/build-push-action and Google Cloud Auth. - [Mogollon Monster 100 Mile Planning](/posts/2022-mogollon-monster-planning-splits.md) - Planning and splits for the 2022 Mogollon Monster 100 - [2022 Creede 100 Race Report](/posts/2022-creede-100-race-report.md) - Race Report for the 2022 Creede 100 mile ultramarathon. I finished 2nd in 29:16:01. - [2022 Mace's Hideout 100 Race Report](/posts/2022-maces-hideout-100m.md) - Race Report for the 2022 Mace's Hideout 100 mile ultramarathon. I finished 3th in 29:20:53. - [Strava Webhooks with Stokehook.com](/posts/strava-webhooks-with-stokehook.md) - An app I built using Svelte, Firebase, and the Strava API. - [Unwatch All Repositories in a GitHub Organization](/posts/unwatching-all-repos-in-github-org.md) - Using the GitHub CLI to unsubscribe from repositories. - [Archiving Dependabot Emails with Apps Script](/posts/archive-github-dependabot-semantic-release-emails-with-appscript.md) - Archive Dependabot and Semantic Release emails automatically using Google Apps Script cron jobs to declutter your inbox. - [Automatically Approving and Merging Dependabot Pull Requests](/posts/automatically-approving-and-merging-dependabot-pull-requests.md) - A simple GitHub workflow to automatically approve and merge Dependabot pull requests. - [Rebasing All Dependabot Pull Requests](/posts/rebase-all-dependabot-pull-requests.md) - Short code snippet showing how I rebased all Dependabot pull requests across a GitHub org. - [What is DevRel?](/posts/what-is-devrel.md) - DevRel is a lot things: marketing, engineering, product management, support; my experience as a Developer Relations Engineer at Google. - [Drop Bag Plan for Cocodona 250](/posts/2022-cocodona-250-aid-drop-bags.md) - Running a 250 mile race without crew or pacers requires adaptability and a good plan for drop bags. This is a work in progress... - [Eleventy Related Posts Using TF-IDF](/posts/eleventy-automatic-related-posts.md) - Automating related posts in Eleventy with term frequency-inverse document frequency and eleventy-plugin-related. - [Running and Chronic Exertional Compartment Syndrome](/posts/running-with-exertional-compartment-syndrome.md) - Understanding and treating a new running injury, Chronic Exertional Compartment Syndrome. Can I run? Can I race? - [GitHub Workflow to Sync Branches](/posts/sync-branches-github-workflow.md) - It is easy to sync branches in a GitHub workflow without using a third party GitHub Action. - [2022 Sedona Stage Race Report](/posts/2022-sedona-red-rocks-stage-race-ultra.md) - Race Report for the 2022 Red Rocks Stage Race in Sedona, AZ. I finished the 50k Saturday, 50k Sunday version second overall in a little over 11 hours total. - [Eleventy Progressive Web App](/posts/eleventy-progressive-web-app.md) - Catching the Eleventy Lighthouse obsession bug! - [What about the cows...](/posts/what-about-the-cows.md) - Don't bust the crust (unless you are a cow). A greenwashing campaign by our public land management agencies. - [2022 Behind the Rocks 50k Race Report](/posts/2022-behind-the-rocks-50k.md) - Race Report for the 2022 Behind the Rocks Ultra 50k in Moab, Utah. I finished 8th in 4:55. - [Mountain Bike Bear Spray Holder](/posts/mountain-bike-bear-spray-holder.md) - Instructions for a DIY mountain bike bear spray holder - [2021 Mogollon Monster 100 Mile Race Report](/posts/mogollon-monster-100-2021.md) - Race report for the 2021 Mogollon Monster 100 mile - [Google Maps React Wrapper](/posts/google-maps-react-wrapper.md) - A wrapper for loading Google Maps JavaScript in React - [Molas Lake to Vallecito Reservoir](/posts/molas-to-vallecito.md) - A solo adventure running 36 miles from Molas Lake to Vallecito Reservoir in the Weminuche Wilderness - [Automate Email Bankruptcy using Apps Script](/posts/automate-email-bankruptcy-using-apps-script.md) - Archiving emails older than 30 days automatically. - [WMS Layer on Google Maps](/posts/google-maps-wms-layer.md) - Instructions for adding a WMS Layer to Google Maps - [Microservice Logging with Openresty and BigQuery](/posts/microservice-usage-logginging-with-openresty-and-google-bigquery.md) - Log microservice usage and bytes sent directly to Google BigQuery using OpenResty and Lua for accurate billing and analytics.