Penumbra Tech
← All projects
[Tug Comanche · nonprofit · client build·]

Saving a WWII ship is mostly paperwork.

Comanche is a 1944 Navy ocean tug, later the Coast Guard cutter WMEC-202, and she still gets underway under her own power on Puget Sound. A handful of volunteers keep her running. The romantic part is the ship. The part that actually decides whether she survives is money and paperwork, and that runs on software. So the site I built for the Tug Comanche Historical Rescue Foundation is really an operations backend wearing a museum-ship front end.

Node + ExpressMySQLnginx + PM2Givebutter donationsAmazon SES + blastsBoard portalGrant PDFsNonprofit
[The money·]

The whole site points at one button.

A rescue like this lives or dies on donations, so everything is arranged to make giving the shortest path on the page. Donate sits in the nav, floats in the corner, and is the loud button in the hero. The actual money runs through Givebutter, embedded right in the Support page, which means the foundation never touches a card number or has to mail a receipt. Same move I make everywhere: don't rebuild the parts a specialist already does well and has to be compliant about. Wire into them and get out of the way.

[The back office·]

There's a board portal doing the real work.

Behind the public pages is a full backend, the same stack I'd build for any client: Node and Express on MySQL, behind nginx and PM2. The volunteer board logs into a portal at /admin with real session accounts (bcrypt, roles for admin and editor), and new board members get on by invite link instead of someone emailing a shared password around. Every contact, volunteer, and partner form writes to the database and pings a notify address. The news feed is database-backed but falls back to a bundled list if the API ever hiccups, so the front page never goes blank.

None of that is visible to a visitor. It's the difference between a nonprofit that runs on one person's inbox and one that keeps working when that person is out on the water.

[The shoebox·]

I turned a box of sign-in sheets into a mailing list.

The foundation had years of supporters, and they were sitting in a stack of paper sign-in sheets from open-boat days. So I scanned and transcribed them: 159 people, around 550 candidate email addresses once you account for the guesses and permutations. That went into a real contact list on the server (kept out of git, since it's actual people's info) with a verify-and-bounce workflow, so addresses get marked good or dead instead of silently rotting.

From there the board sends branded email blasts straight out of the portal: pick an audience, send, watch a per-recipient log. It runs on Amazon SES, and when a message bounces, SES tells a webhook and the address gets flagged automatically. A pile of paper became a list the crew can actually reach, which for a donation-funded nonprofit is most of the whole game.

[The paperwork·]

The grant proposals build their own PDFs.

Grants are how a project like this gets real money, and grant packets are miserable to keep in sync. So each proposal is one object in the code, and that single source renders both the page you read on the site and a downloadable PDF for the application. The budget isn't typed in twice, it's computed from the line items plus a project-management percentage and a contingency (it's a 1944 hull, things surprise you), rounded up to the nearest $10k. The number on the website and the number in the grant packet literally cannot disagree.

grants.js
ONE SOURCE · PAGE + PDF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// grants.js: one source of truth per proposal. The /grants page AND the
// downloadable PDF both render from this object. The budget is computed,
// not typed, so the number on the page can't drift from the number in
// the grant packet.
export const drydock = {
title: 'Dry-dock and hull survey',
lineItems: [
{ name: 'Haul-out and lay days', cost: 42000 },
{ name: 'Hull survey + gauging', cost: 15000 },
{ name: 'Steel and welding', cost: 88000 },
],
pmPercent: 12, // project management
contingencyPercent: 15, // it's a 1944 hull; things surprise you
};
// total = roundUpTo10k(sum(lineItems) * (1 + pm% + contingency%))

Same generator stamps out the rest of the fundraising kit from templates: a flyer, a 4x6 donation card, a QR code, a printable donation receipt. The stuff a volunteer needs at a table on the dock, printed and ready, not something they have to design the night before.

[The facts·]

It's a history site, so I sweated the history.

A museum ship's whole credibility is that it tells the truth about itself. So the dates and claims that came from secondary sources are flagged in the code and read as “reported” on the page, waiting to be reconciled against the official Navy and Coast Guard histories, instead of getting stated as gospel. And one distinction stays in every line of copy: Comanche is an operational museum ship that cruises through nonprofit partnerships, she is never a charter you can rent. Getting that right is its own kind of engineering. It just happens to be about words instead of code.

A twelve-person volunteer board now runs on the same kind of software a real company would, which is the point.

They get to spend their time on the ship instead of on spreadsheets and shared passwords. If you run a small nonprofit and the admin work is eating the mission, that gap is the thing I close.