# shipthing: I named it for shipping rates and shipped a contacts spine instead

Source: https://www.michaelchurley.com/blog/shipthing-contacts-spine-not-carrier-rates  
Published: 2026-08-08  
Author: Michael C. Hurley  
Tags: shipthing, nextjs, convex, clerk, resend, sentry, posthog, proxy-ts, contacts, shipping, vercel, michaelmonetized

![ShipThing home. lead form + signed-in contacts table](/blog/shipthing-contacts-spine-not-carrier-rates/screenshots/home-contacts.png)

## Who

I wanted a shipping-rate desk for e-commerce sellers: compare USPS, UPS, FedEx, print labels, stop guessing retail rates.

What I built instead is an honest stack spine: Clerk auth, Convex contacts, Resend lead email, Sentry, PostHog, and a Next 16 `proxy.ts` filename law. People who read PLAN.md, then open the tree, and do not pretend the carrier boxes are checked.

If you have ever named a repo after the product you meant to ship and then shipped the scaffolding that every later app copies, this is that receipt.

## What

I built **ShipThing**. Package `shipthing` **0.1.0**, public under **michaelmonetized/shipthing**. Layout metadata title: **Shipthing**. Description: **Combining convex, posthog, clerk and sentry**. That description is more accurate than the repo name.

Stack from the lockfile: **Next.js 16.1.1**, **React 19.2.3**, Tailwind **4**, Bun, **Clerk**, **Convex**, **Resend** + React Email, **Sentry** (org `hustle-launch`, project `shipthing`), PostHog, zod 4, react-hook-form, Radix/shadcn UI. `stripe` sits in dependencies with a long `.cursor/rules/STRIPE.md`. **Zero app imports.** Thirty-nine commits. HEAD `9f91d97`.

![PLAN.md Not Started vs what the tree actually contains](/blog/shipthing-contacts-spine-not-carrier-rates/screenshots/plan-vs-shipped.png)

Surfaces that exist: `/` lead form ("Be the first to contact us!" / Send Message with name, 10-digit phone, email, message) plus signed-in **Contacts** table with delete; `/login`; `/sentry-example-page`; API routes `/api/send/notification` and `/api/send/confirmation`; `proxy.ts` Clerk middleware file; `sitemap.ts` / `robots.ts`.

Convex schema is a single `contacts` table. Search index on name, indexes by name/phone/email/page. Notification mail sends from `Notifications <notify@uncap.us>` to `michaelmonetized@gmail.com` and `8285931935@vtext.com`. Confirmation is a short "Hey {name}, we received your message" React Email.

Navbar lists Security, Auth, Layout, Typography, Forms, Analytics, Error Tracking, Email, Realtime Data Sync, APIs, More. **No `/features/*` pages** in the tree. Footer still links Learn / Examples / nextjs.org from create-next-app.

`PLAN.md` still sells the other product: carrier APIs, rate comparison, ZPL/PDF labels, address validation, Shopify import, batch labels, tracking, cost analytics. Success metrics: active users > 500, monthly labels > 10,000, savings > 30%. Every checkbox is empty.

## Where

Code: [github.com/michaelmonetized/shipthing](https://github.com/michaelmonetized/shipthing). **Public.** Live: [shipthing.vercel.app](https://shipthing.vercel.app) (**HTTP 200**, Clerk signed-out chrome, `X-Robots-Tag: index, follow`).

![proxy.ts + check:proxy Next 16 guardrail](/blog/shipthing-contacts-spine-not-carrier-rates/screenshots/proxy-guardrail.png)

Audience sits next to every "I'll bolt carriers on next sprint" SaaS skeleton. Sibling operator furniture: Fallow gate notes in `AGENTS.md`, Bun-only local law, Blacksmith/Vercel prebuilt rules, env.template for Clerk + Resend + Convex.

![Resend notification + confirmation lead path](/blog/shipthing-contacts-spine-not-carrier-rates/screenshots/resend-lead-path.png)

## When

**2025-03-22.** Create Next App. Same day: Convex + PostHog + Sentry, not-found + shadcn button, middleware build thrash, Clerk, forms.

**2025-03-26–27.** Convex contacts land. Resend starts. Real bugs: could not access `name` in notification email, copy-pasta, split emails so sending stops after the first try/catch, more Resend fixes, light-mode toggle attempt.

**2025-03-28–29.** Navbar, error boundary, login. Then: `convex dev, i finally recovered my github login 🎉`.

**2025-04.** Layout components; **box, stack, deck** + Next update.

**2025-12-29.** React Server Components CVE pass.

**2026-01-08.** `PLAN.md` with shipping-rate "improvement opportunities." Jan 31 chore sync.

**2026-02.** CVE PR #1; rename `middleware.ts` to `proxy.ts` (#7); env.template (#8); proxy filename guardrail (#10); security headers (#11); sitemap + robots (#12).

**2026-06-22.** Nightly ×2.

**2026-08-08.** HEAD `9f91d97`: set `X-Robots-Tag` to `index, follow` on Vercel.

![Commit arc Mar 2025 to Aug 2026](/blog/shipthing-contacts-spine-not-carrier-rates/screenshots/commit-arc.png)

## Why

The shipping product needed a spine before it needed a carrier SDK, and the spine is what survived.

Next 16 renamed the middleware file. I wanted a script that fails if `middleware.ts` comes back (`bun run check:proxy`).

A lead form that emails me and texts `8285931935@vtext.com` is a product loop I can prove. A FedEx rate matrix I never integrated is not.

Naming the repo ShipThing and leaving PLAN.md full of unchecked USPS boxes is more useful as an operator story than as a fake launch post.

When your PLAN.md still lists the vertical and your `layout.tsx` description lists the stack, which one should the blog title obey?
