Offer Configuration Tool

Whenever an Audible engineer needed to check or change what a customer could buy (a credit, a membership upsell, a borrow), they went through five manual steps. First a stage version, then three separate change requests for Devo, Preprod and Prod, then a promotion to production. Each step was slow, and each hand-off was another chance for an error.

I designed and built the internal tool that turned the lookup side of that chain into one page. My focus was UX engineering, frontend development and cloud infrastructure: I wrote the design doc, drew the UI and the architecture, built and tested the responsive front end twice, and took it from a laptop to a deployed, authenticated site on AWS.

Built with React, JavaScript, TypeScript, Katal, AWS CDK, CloudFront, S3 and Route 53. The tool is internal to Amazon, behind employee sign-in, so there is no public link.

Client
Audible, an Amazon company
Type
Product Design
Year
2022
Role
Software Engineering Intern
The Offer Configuration Tool, an internal Audible app, shown in a desktop browser and on a narrow screen: a form for customer, title, marketplace and device on the left, matching offers in collapsible panels on the right.

Process

The problem was a process, not a page.

The service behind every Audible offer is called ABOS, the Audible Buying Options Service. Nothing was wrong with ABOS. The trouble was how you got to it: five steps across three environments, for something as simple as seeing which offers one customer qualifies for.

My users were the engineers on my own team. That made them easy to reach and hard to impress. They knew exactly how the old way worked and would only switch to something that was clearly better.

I wrote it down before I wrote code.

Nobody asked for a design doc. I wrote one anyway, with the architecture and a UI mockup, and shared it with the team while it was still rough. The point was to find out I was wrong while being wrong was cheap.

The mockup is deliberately plain: five inputs, one button, and a list of offers that expand to show buyability, ID, price and type. That structure lasted through every version that followed. Only the build underneath changed.

Before and after: five manual steps to change an offer (a stage version, three change requests, a promotion to production) compared with one internal page to look up offers.
First UI sketch from the design doc: five inputs and a Submit button that calls the offers service, then a results page listing offers that expand to show buyability, ID, price and type.

Method

Version one: prove the idea end to end.

I built the first version in React with JavaScript, HTML and CSS. It was deployed to S3, served through CloudFront and pointed at with Route 53, and a Lambda function called ABOS for the customer's offers. The diagrams below are the ones I drew for the project in 2022, redrawn here for clarity.

It worked. You entered a customer ID and a title, pressed submit, and real offers came back from the real service. But a working demo is not the same as a tool people trust, and I could see where this one would break.

Version two: rebuild it properly.

Partway through, I decided to rebuild on Katal, Amazon's internal front end framework. Rebuilding in the middle of an internship is a risk. Staying put was a bigger one. Katal let me move to TypeScript, gave me a design system instead of hand-written CSS, and generated the infrastructure as code: a CDK package, a website package, and a deployment pipeline to CloudFront.

The architecture also grew up. The site now sits behind Amazon's employee sign-in, so only signed-in employees can load it. Calls go through API Gateway to a Lambda function that has to pass service-to-service authentication before it is allowed to ask ABOS anything. Every request is over HTTPS.

I didn't build that back end alone. Two fellow interns on partner teams built the API Gateway, the Lambda function, the private network and the service permissions. I owned the front end, the infrastructure it shipped on, and the design work that tied it together. Part of the job was keeping three people's pieces fitting as each one changed.

Version one architecture diagram: the user enters a URL, Route 53 checks the Amazon VPN, a Lambda function calls the offers service, and the React project is deployed to CloudFront with static content in S3.
Version one, built in React: a form for customer ID, title, marketplace and device, with optional offer and program type filters and a Submit button.
Version one relying on the browser's generic warning: Please fill out this field.
Version two architecture diagram: the browser sends a Midway cookie to CloudFront with a trusted signer, the Katal project deploys to S3, and Route 53 points to API Gateway, which calls a Lambda function that uses CloudAuth to reach the offers service.

Design System

I stopped styling, and started composing.

Version one's look was entirely mine: a blue page, a grey card, my own CSS. It worked, but it belonged to nobody else. If another engineer opened it, they would be reading my choices, not Amazon's.

Version two is built from Katal's components: the inputs, dropdowns, checkboxes, button and accordion. Any Amazon engineer who has used Katal already knows how it behaves and how to extend it. On an internal tool that matters more than looks. Whoever inherits it after me can change it without learning a private system first.

Typed contracts, not guesses.

The move to TypeScript was a design decision as much as a technical one. A customer ID, a marketplace, a device, five offer types and six program types: once each of those has a type, the form and the API agree on what a valid request looks like. They can't quietly drift apart.

Decisions

Errors should say what to fix.

Version one relied on the browser's built-in warning. Leave a field empty and you got "Please fill out this field". That's accurate, but it doesn't help if the field isn't empty, just wrong.

In version two every required field says what it needs, right beside the field. A customer ID that's too short gets "Please provide a 14 character CustomerID", not a generic failure after the request has already gone to the service. Catching a bad input on the page is quicker for the user and spares a service call that was always going to fail.

Free text where there should be choices was another source of errors. Device was a text box in version one, so "iPhone", "iphone" and "IPhone" were three different inputs. In version two it's a dropdown, like the marketplace already was.

A response is not a result.

ABOS returns a lot. Each offer carries a long encoded ID, and version one printed every offer fully open, one after another, down the side of the page. Everything was there, and nothing was easy to find.

Version two folds each offer into an accordion. You see Offer 1 through Offer 5 at a glance and open only the one you came for. The four fields that matter (buyability, ID, price, offer type) sit in the same order every time, so the second offer reads faster than the first.

Wide screen, narrow window.

Internal tools get used in the margins: half a laptop screen next to a terminal, or a window squeezed beside a ticket. So I checked both versions narrow as well as wide.

On a wide screen the form and the results sit side by side. In a narrow window the results drop beneath the form instead of squeezing into a column too thin to read, and the inputs stretch to the full width. Same content and same order, only the layout changes.

Version one, a custom React form with hand-written CSS, next to version two, rebuilt from Katal design system components.
Version one, a custom React form with hand-written CSS, next to version two, rebuilt from Katal design system components.
Version one's generic browser warning, Please fill out this field, compared with version two's specific message, Please provide a 14 character CustomerID.
Version two, rebuilt on Katal: each required field shows its own inline error, such as Please provide a CustomerID and Please provide a Device.
Version one printing every offer fully open, next to version two folding Offer 1 to Offer 5 into an accordion.
Version one in a wide window, form beside the offer list, and in a narrow window, where the offers drop below the form.
Version two in a wide window, form beside an accordion of offers, and in a narrow window, where the offers stack below the form.

Craft

Two builds, one structure.

Rebuilding meant throwing away working code. What I kept was everything that had been learned: the field order, the required and optional split, and the four facts every offer shows. The first mockup is still recognisable in the final tool. The second build got the same design on a foundation that could hold it.

Learning fast was part of the job.

Most of this stack was new to me at the start of the summer: Amazon's internal build system, Katal, TypeScript, the CDK, and AWS services from CloudFront to Route 53. I learned them while shipping, not before.

The tool, end to end.

The finished form in version two: typed inputs, dropdowns where free text used to be, and an error beside each field that names exactly what it needs.

Version two of the form: a short customer ID shows the inline error Please provide a 14 character CustomerID right beside the field.

What Came Next

The tool shipped with Get Offers. The next phase was already designed: a Create Offers flow on the same page, which would retire the five-step chain for writes as well as reads. I also scoped restricting access to my own team through Amazon's permission groups, and merging the separate AWS accounts the three of us had built on. These were next steps, not part of what I delivered.

Proposed Create Offers design, not built: the same form with a Create Offers button beside Get Offers, and an example offer showing buyability, ID, price and type.

Outcome

Five manual steps became one page to look up offers.

Two complete builds in a single summer. The second was rebuilt on Amazon's own design system, typed end to end in TypeScript, and deployed through infrastructure as code. Every required field explains its own error. Every offer reads in the same four facts. The site is behind employee sign-in, served over HTTPS, and reaches ABOS only through an authenticated back end.

These are delivery and quality results. I did not measure time saved or usage after launch, so I am not claiming them.

It sits where design, front end and cloud infrastructure meet. That is the seam I like working on most.

Interested in working together?

Let’s connect about product design and digital experiences.

Interested in working together?

Let’s connect about product design and digital experiences.

Interested in working together?

Let’s connect about product design and digital experiences.