Capture first and last name at registration, so emails can greet informally #106

Closed
opened 2026-08-21 16:57:41 -05:00 by bermudalamb · 0 comments
Owner

Registration collects a single optional Name. Emails that greet a customer therefore have only a whole name to work with, which is why the cart reminder reads Hi${name ? ' ' + name : ''} and produces "Hi Thom Lamb," rather than "Hi Thom,".

Splitting it into first and last name makes an informal greeting possible, which is the point.

What has to change

customers.name TEXT is nullable and touched in nine places:

Where What it does
routes/customers.ts:62,75 reads name from the register body, inserts it
routes/customers.ts:53 publicCustomer returns it to the client
routes/customers.ts:289 the account page updates it
server.ts:47 the cart-reminder greeting
AuthForm.tsx:83 the optional Name field on the register form
admin/Customers.tsx:346,426 the customer list cell and the drawer title
CustomerRow / frontend Customer the shapes that carry it

The decisions that need making before anyone starts

Required, or still optional? Name is optional today — you can register with an email and a password and nothing else. "Register with firstName and lastName" reads as making them required, which is a change to the signup flow and to what an existing customer with no name sees on their account page. If they stay optional, the greeting needs a fallback anyway, and the informal greeting only happens for people who volunteered a first name.

What happens to the names already stored? This is the part with no clean answer. Existing rows hold one string, and splitting on the first space is a guess that mangles real names: "Mary Jane Smith" becomes either a first name of "Mary" or of "Mary Jane" and both are wrong for somebody. Names also do not reliably divide into two parts at all. The honest options are to backfill first name only and leave last name empty, to leave existing rows untouched and treat the old column as legacy, or to ask customers to re-enter it — and the first is probably right, but it should be chosen with the mangling in mind rather than discovered afterwards.

Does the old column go? Dropping name means a migration and updating every consumer in one change. Keeping it alongside means two sources of truth for the same fact, which will drift. Dropping is cleaner; it just makes the change bigger.

What this is not

shipping_addresses.full_name already exists and is NOT NULL — a delivery name is captured where delivery actually needs it. That is deliberately separate: the person an order ships to is not always the account holder, and a gift is the obvious case. This issue should not fold the two together, and the shipping name should stay exactly as it is.

Relationship to #92

#92 makes the emails editable and introduces a {{greeting}} placeholder that resolves to "Hi Thom," or "Hi,". That placeholder works against whatever name field exists, so the two are technically independent — but the point of this issue is that the greeting should use a first name, and #92 shipping first would mean {{greeting}} produces the formal full name until this lands.

Doing this one first means the greeting is right from the moment it becomes editable.

Verification

Integration tests that registration stores both parts, that the account page updates them, and that publicCustomer returns them without leaking anything new. A migration test that existing rows survive whatever backfill is chosen. End-to-end that registering with a first name and reaching the account page shows it.

Severity

Low. Nothing is broken — this is about the emails being able to sound like a person wrote them.

Registration collects a single optional **Name**. Emails that greet a customer therefore have only a whole name to work with, which is why the cart reminder reads `Hi${name ? ' ' + name : ''}` and produces "Hi Thom Lamb," rather than "Hi Thom,". Splitting it into first and last name makes an informal greeting possible, which is the point. ## What has to change `customers.name TEXT` is nullable and touched in nine places: | Where | What it does | | --- | --- | | `routes/customers.ts:62,75` | reads `name` from the register body, inserts it | | `routes/customers.ts:53` | `publicCustomer` returns it to the client | | `routes/customers.ts:289` | the account page updates it | | `server.ts:47` | the cart-reminder greeting | | `AuthForm.tsx:83` | the optional **Name** field on the register form | | `admin/Customers.tsx:346,426` | the customer list cell and the drawer title | | `CustomerRow` / frontend `Customer` | the shapes that carry it | ## The decisions that need making before anyone starts **Required, or still optional?** Name is optional today — you can register with an email and a password and nothing else. "Register with firstName and lastName" reads as making them required, which is a change to the signup flow and to what an existing customer with no name sees on their account page. If they stay optional, the greeting needs a fallback anyway, and the informal greeting only happens for people who volunteered a first name. **What happens to the names already stored?** This is the part with no clean answer. Existing rows hold one string, and splitting on the first space is a guess that mangles real names: "Mary Jane Smith" becomes either a first name of "Mary" or of "Mary Jane" and both are wrong for somebody. Names also do not reliably divide into two parts at all. The honest options are to backfill first name only and leave last name empty, to leave existing rows untouched and treat the old column as legacy, or to ask customers to re-enter it — and the first is probably right, but it should be chosen with the mangling in mind rather than discovered afterwards. **Does the old column go?** Dropping `name` means a migration and updating every consumer in one change. Keeping it alongside means two sources of truth for the same fact, which will drift. Dropping is cleaner; it just makes the change bigger. ## What this is not `shipping_addresses.full_name` already exists and is `NOT NULL` — a delivery name is captured where delivery actually needs it. That is deliberately separate: the person an order ships to is not always the account holder, and a gift is the obvious case. This issue should not fold the two together, and the shipping name should stay exactly as it is. ## Relationship to #92 #92 makes the emails editable and introduces a `{{greeting}}` placeholder that resolves to "Hi Thom," or "Hi,". That placeholder works against whatever name field exists, so the two are technically independent — but the *point* of this issue is that the greeting should use a first name, and #92 shipping first would mean `{{greeting}}` produces the formal full name until this lands. Doing this one first means the greeting is right from the moment it becomes editable. ## Verification Integration tests that registration stores both parts, that the account page updates them, and that `publicCustomer` returns them without leaking anything new. A migration test that existing rows survive whatever backfill is chosen. End-to-end that registering with a first name and reaching the account page shows it. ## Severity Low. Nothing is broken — this is about the emails being able to sound like a person wrote them.
bermudalamb self-assigned this 2026-08-21 17:50:36 -05:00
bermudalamb added this to the Make the password-reset email editable from Admin project 2026-08-21 17:50:45 -05:00
bermudalamb moved this to Review in Make the password-reset email editable from Admin on 2026-08-21 18:31:44 -05:00
bermudalamb added reference feature/106-customer-first-last-name 2026-08-21 18:41:15 -05:00
bermudalamb moved this to Ready for Release in Make the password-reset email editable from Admin on 2026-08-21 18:59:59 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#106