Experience
The resume is the short version. This is one entry per project, newest first, with promotions marked where they fell.
Milwaukee ToolMilwaukee, WI (Remote)Aug 2021 – Present
Nov 2025·Staff Software Engineer
Moving to warehouse integrations
Tech lead for the warehouse integrations, and maintainer of METI, the SDK and portal other teams build their apps on.
In November 2025 I asked to move teams. Dotcom was in a good place, and the integrations organization, MetcoIS, had a set of systems that worked but that nobody had recently taken the time to make solid. I'd just spent four years on that kind of problem.
I am the tech lead for the WMS integrations team. Oracle is Milwaukee Tool's central ERP, and every distribution center runs its own warehouse system. The job is keeping Oracle and that warehouse system in sync, without someone re-keying the work. We run a weekly PagerDuty rotation. P1 and P2 stay open until they are resolved, including the 2 a.m. pages, and if I cannot get one moving in about 45 minutes I bring in a second person.
An architect wrote the design for a third warehouse integration. The two already running are different systems, so this one needed its own design. I built it. It is in production, and the post-launch support period is over. One warehouse already in production writes straight to SQL and produces a lot of duplicate primary keys. I added a check that finds the duplicate and applies the rules for what to do with it.
Underneath all of it is Milwaukee Tool Integrations, METI, which I inherited and maintain. It is an SDK and a web portal that other teams build their apps on. About 200 apps across 20 to 30 teams run on it, easily a million messages a day. I own the platform, the warehouse integrations, and a few other apps on it. When the product team publishes a product change, it goes out on a products-updated feed; orders and customer data have feeds of their own; the warehouse integrations ride the same rails. METI is a durable execution layer over Azure Service Bus. I designed its status-publisher SDK so a downstream system can track message state over HTTP.
I added idempotency rules and a Redis queue with one entry per message. The Redis entry tracks each message until the work finishes. Service Bus locks expire after five minutes, so longer work was being delivered again before it finished. I also added a pause that holds every queue while an Oracle ERP patch rolls out. I refactored task cancellation so a cancelled task still records its status and does not lose its message. The old code was dropping a few hundred status updates a day. The same stretch of work moved the warehouse services and APIs from Application Insights to OpenTelemetry, standardized SQL provisioning with Terraform modules, and replaced per-identity role assignments with Azure AD groups. The subscription was running out of role assignments from the sprawl.
As tech lead of the scrum team I sit on the value stream's leads group. Each quarter I run the innovation-planning sessions. I also hosted the AI Community of Practice workshop through its six-week rollout: two hours a week, about ten engineers a session, on where we use AI at work and the safety rules that go with it.
I also travel for distribution and service center launches and work the floor, packing boxes and unloading trucks. After doing that I added on-call alerts for receipt, advance ship notice, and shipment notifications. When one of those fails, someone has to fix it quickly.
Nov 2025
Moved to the MetcoIS warehouse integrations team
Mar 2024·Staff Software Engineer
Stabilizing the new platform
After launch: moving services to Container Apps, consolidating per-service host code into MET.Kernel, A/B testing, release pipelines, and the hotfix track through peak season.
Once pages were live on the new stack, the services behind them needed to become a platform other people could build on.
I migrated MET.Search and the other extracted services from AKS to Azure Container Apps and built the production infrastructure around them in Terraform: Front Door, Service Bus, private DNS, Key Vault, and the container registry, across every environment, hardened with WAF rules, TLS, and the fixes from that year's penetration test.
Each service had also grown its own host code by then, so platform-level behavior like logging and startup drifted from app to app. I consolidated it into a single MET.Kernel app-builder so those things ship once. I set up A/B testing on Statsig and trunk-based release pipelines, and I ran a hotfix track through the launches and the peak commerce periods until the platform was stable enough to be uninteresting.
I sat with the other leads and product owners when a roadmap conflict had to be decided, and I was in the vendor evaluations: Statsig over LaunchDarkly for A/B testing, plus the CMS and search shortlists (Algolia, Coveo, Azure AI Search). I also piloted Azure AI Foundry for internal agent use cases.
By late 2025 the site was stable and the platform had owners.
Dec 2023·Staff Software Engineer
Taking the site headless, one page at a time
Taking the React proof of concept to production: phased page migration across four locales, PIPELINE 2024 on the new stack, and the product page redesign.
The proof of concept from the previous year had to become the real milwaukeetool.com. Lighthouse scores on the first pages we launched improved about ten times. We got to about 85% of pages. The business chose to drop the rest rather than rebuild features nobody was going to keep.
The migration ran in phases. Home, product pages, catalog, category, support, and navigation moved from Razor to React and Next.js App Router, across four locales, with each page served side by side with its legacy version through the auth proxy's routing flag until we were confident enough to cut over. Underneath that were dynamic catch-all routes for the language and slug segments, parallel and intercepted routes for modals, and server components that transform Sitecore layout service output into page trees. Doing it gradually meant no single deployment was a bet on the whole site.
PIPELINE came around again in 2024, and this time it was the first cycle delivered on the headless stack. I owned it from the backend services through to the frontend components. It was also the test of the goal we'd written down in 2022, and the time from the business asking for something to it being live had gotten shorter.
I shipped the product page redesign on the new stack: PIM-sourced galleries, a sticky call to action with regional pricing, spec comparison, and FAQ modules. I also took new category experiences from Figma designs to production components. Both became the template for the pages that came after.
Dec 2023
Promoted to Staff Software Engineer
May 2023·Senior Software Engineer
Hardening the platform, and growing the team
The work around the services in 2023: closing the year's penetration-test findings, building out SEO, and the parts of the job that weren't code.
Not everything in 2023 was a new service. The year's penetration test came back with findings, and I ran the hardening work that closed them, in phases: HSTS, CSRF, Auth0 session controls, rate limiting, and Snyk gates in the pipeline so a vulnerable dependency failed the build. I also built out the SEO infrastructure the site had been missing: hreflang and canonical URLs, and regional sitemaps that included the product pages.
This is also when the job started including things that weren't code. I hosted the team's Friday tech talks and ran our tech-debt analysis and refinement sessions. I took over the intern interview loop, and ended up running every intern interview for three years straight. I was also one of the first engineers at the company with a GitHub Copilot license, back when it was still an experiment.
Mar 2023·Senior Software Engineer
Images on demand
An on-demand image service that resized product photos to the width a page asked for, in the background, and cached the result.
Our photo team produced product images at around four thousand pixels wide, and we were serving those files to customers checking a tool spec on their phone in a Home Depot aisle. The requirement I was given was one line: make pages load faster and send fewer bytes.
I built METIMG, an on-demand image service, from design through delivery. A request carries a target width. A hit is served from the first cache that has that size: the CDN, then Redis, then blob, where generated sizes are kept for 180 days. SQL is the cold store. On a miss, the request goes onto a queue to be resized in the background, with PNG converted to WebP where the browser accepts it, and in the meantime you get the nearest larger cached variant served with no-cache headers.
Image bytes dropped by roughly twenty times. Typical image load times went from around 300 ms to around 80 ms. And once media traffic was off Sitecore, the AKS footprint shrank from twelve nodes to four.
Jan 2023·Senior Software Engineer
Authentication without Sitecore
A YARP reverse proxy that took over session and cookie auth, shrank the session cookie from about 10 KB to 377 bytes, and gave us a way to migrate the site one page at a time.
Session and cookie auth lived inside .NET Framework Sitecore, and every service that needed to know who the user was had to decrypt the same session. If we were going to put a React frontend next to the monolith, auth had to come out first.
I designed and built MET.Authentication, a YARP reverse proxy in .NET Core that took over authentication entirely: Auth0 and Azure AD as dual identity providers, a Redis session store, CSRF validation, Key Vault data protection, and multi-domain cookie management. The session cookie went from roughly 10 KB to 377 bytes.
The part that mattered most in the long run was routing. The proxy could send each page to a different backend based on a flag in the CMS. A content author marked a page as supporting React, and traffic for that page went to Next.js while everything else kept going to Razor. That flag is what let us migrate the site one page at a time over the next two years.
Nov 2022·Senior Software Engineer
The headless POC: choosing React over Svelte
The research that got the team off Razor: a Svelte proof of concept that beat the legacy site, ported to React in days so we could compare fairly. Terraform came in alongside it.
The site was tightly coupled to Sitecore's Razor rendering. Builds were slow, hiring for it was hard, and the patterns Sitecore recommended were complicated. In November 2022 the headless research epic opened up and I took it.
I built a proof of concept of a single page in Svelte and SvelteKit, with components and styles collocated, and it beat the legacy site on page metrics. Then, so the team could compare like for like, I ported the same page to React and Next.js. That took a few days. React won on ecosystem, hiring pool, and proven performance, and the goal we wrote down for the whole program was to shorten the time from a business ask to a deployed feature.
Around the same time I introduced Terraform. I'd done a Pulumi proof of concept first and preferred Terraform, and I used it to provision the first production services outside the monolith with real CI/CD pipelines. Every service that followed used the same setup.
Jul 2022
Promoted to Senior Software Engineer
Apr 2022·Senior Software Engineer
Splitting a Legacy Monolith one service at a time
The first capability I pulled out of the Sitecore monolith into its own service, and later the backend for the 2023 PIPELINE launch.
Product search ran through a Coveo plugin inside Sitecore. For large product sets the category API calls were taking around twenty-seven seconds, which on a catalog site is the same as not working.
In April 2022, I got the search direction. The approach I took, and kept taking for the next year and a half, was to find the piece of the monolith that hurt most and pull it out into its own service. Search was the first.
I built MET.Search as a standalone service on FastEndpoints backed by Solr: a proper product data API, a caching layer, and index refresh driven by events on Service Bus. It also began separating product data from page content, which the monolith had always mixed together. Category responses dropped to under a second.
The service ended up carrying more than search. When PIPELINE came around in 2023 (Milwaukee Tool's annual new-product launch site, and the most visible thing we ship each year), its backend lived here: search proxies, product lookup, compatibility with the legacy API, and the Service Bus refresh. I owned that backend for the 2023 cycle and delivered it for the launch window.
Mar 2022·Software Engineer II
First major project, the Compare Tool
The first customer-facing features I owned from the API to the screen, and the first two on the site built in Svelte.
Milwaukee Tool has a deep catalog, and customers constantly want to know how two drills or two saws differ. In spring 2022 I built the Compare Tool: Sitecore API endpoints, computed fields for specs, SKUs, and features, and a Svelte UI with a bottom sheet and comparison cards. It was the first interactive feature the team had shipped outside of Razor views. It was also the first thing on the live site I could point at and say I'd built all of it.
The merchandiser came next: a Svelte component with query-parameter filtering, a carousel, chip-based category navigation, and a custom Sitecore field type. Until then a new merchandising layout needed a developer. Afterwards a content author picked one from a dropdown.
I kept the deployment stack through all of this. I owned the AKS deployment patterns and the Helm charts, moved the front-end build from Gulp 3 to Gulp 4 on Node 16, and led the migration of every video player on the site from Brightcove to Vimeo. Brightcove was going end-of-life, so the deadline was real. Nineteen rendering surfaces each had their own player logic. We consolidated it into one shared component and shipped before the cutoff.
Those two Svelte features were also my first real argument that a lighter frontend framework could live next to Sitecore. That came up again in the fall.
Mar 2022
Promoted to Software Engineer II
Oct 2021·Software Engineer I
Making some worthwhile DevEx improvements
The deploy pipeline, plus the Glass.Mapper, dependency injection, and promotion-service cleanup work from my first year.
A large container deployment of the site took about sixty-five minutes. That is long enough that a deploy becomes an event. You start it, go do something else, come back, and hope nothing went wrong, because if it did you're running it again late in the afternoon.
I rebuilt the Sitecore deployment pipelines and the front-end build workflow and got a full deployment under ten minutes. Deploys got smaller and more frequent after that, since one no longer cost anybody their afternoon.
The rest of my first year was the kind of work a first-year engineer should be doing. I extended the strongly typed Glass.Mapper models across the HR, identity, navigation, and catalog layers, so a renamed content field failed the build instead of a page. I pulled deprecated libraries out of the account, navigation, and redemption modules and swapped static Sitecore context access for dependency injection, which made those modules testable for the first time. And I rewrote a promotion winner-selection service that was enumerating the same blob data over and over. A purpose-built hash set and correct async access against blob storage made it roughly fifteen times faster during peak promotion periods.
Aug 2021·Software Engineer I
Onboarding at Milwaukee Tool
My first six months at Milwaukee Tool, most of them spent learning the AKS cluster a contract team had built and then left behind.
I started at Milwaukee Tool in August 2021 with a math degree, a master's in computer science, and very little production experience. The dotcom team was three senior engineers, a product owner, two QA engineers, and me. We were tasked with owning milwaukeetool.com, a Sitecore 10 XP site running on .NET Framework 4.8 and deployed to Azure Kubernetes Service.
The stack was harder than it looked from outside. .NET Framework meant DLL conflicts when reaching for third party packages and Sitecore owned the thread pool which proved challenging for writing async code outside their conventions. Sitecore is a large, opinionated platform with its own ideas about how content, rendering, and data relate. A contract team had deployed it originally and there seemed to be lack of handoff in between. Control-plane upgrades weren't automated. Node upgrades happened when someone remembered. Production often had strange bugs that were left as "it was a blip".
Soon into the role as I started reading about kubernetes because I wanted to know where my code ran, and after a few months I was the one doing the upgrades. Then the Helm charts, then the Ingress-NGINX configuration, then startup, readiness, and liveness endpoints so the platform could tell a healthy pod from a stuck one.
Aug 2021