+++ title = "simple.remind.rsvp, or the name on the back of the card" date = 2026-10-28 description = "A guest shows up who never confirmed their email. The host can now photograph the barcode on their driver's license, and the phone puts the name on it next to the name on the RSVP. Nothing is saved, and the page isn't allowed to send anything." draft = false [taxonomies] tags = ["remind.rsvp", "security"] +++ simple.remind.rsvp has a rule about email addresses. When you RSVP with one, we send a single link, and the address gets nothing else until you open the link and press the button. Until you do, your RSVP doesn't count toward the numbers on the host's page. The rule is there so a stranger can't use the RSVP form to point our mail at someone else's inbox. That works fine online. Then the guest walks in. Back to the Elm Street block party. Dana RSVPed last week, the confirmation went to an address she checks twice a year, and now she's at the door with a plate of brownies. The host scans her check-in code and the page says she's here. It also says her email was never confirmed. What is the host supposed to do with that? --- ## 1. Two things happen at the door now First, when a host checks in a guest whose address isn't confirmed, we send the confirmation link once more. She's standing right there, so this is the best chance she'll get. It goes once per guest, inside the same daily limits as the first link. The check-in page tells the host it went. Second, sometimes that isn't enough. Her phone is dead, or the address is an old one, or there's no signal in the backyard. In that case the host can check her ID. On the check-in page there's a button: **Photo of their ID's barcode.** The host turns Dana's license over, takes a picture of the wide barcode on the back, and the page shows: > **The names match.** > Their ID: DANA MARIE WHITAKER > Their RSVP: Dana Whitaker > Nothing from the ID was saved or sent. It can also say "Partly the same. Compare them yourself." or "The names don't match." That's a hint. The host decides. Someone who RSVPed as "Dee" is still Dana. --- ## 2. What the ID does and doesn't prove An ID tells you who is at the door. It doesn't tell you the email address is hers. If someone had typed a stranger's address into the form, Dana's license wouldn't make that address hers. So the ID check doesn't turn on email. It answers one question, whether this is the person on the list, and leaves her reminders off until she confirms. That's why it's a separate button and not a way around the confirmation link. --- ## 3. What's in that barcode The barcode on the back of a US or Canadian license is a PDF417, laid out by an AAMVA standard. It holds a lot more than a name: date of birth, home address, license number, height, eye color, sometimes more. I didn't want any of it on our servers. Some states also limit what businesses can do with information read off a license (California, for one, in Civil Code 1798.90.1). The simplest way to stay out of that is to never have the information at all. So the reading happens on the host's phone: - The photo is decoded by the phone's browser and never uploaded. - The page reads three fields: family name, first name, middle name. It ignores the rest. - The name exists only in the open page. Close it and it's gone. Saying we don't send it is easy. The check-in page also can't. Its Content Security Policy has no `connect-src`, so the browser itself refuses any attempt by the page to contact anything, our own server included. I tested that by having the page try. The browser blocked it and logged the violation. The only thing the page loads after the photo is the barcode reader itself, from our own site. Plausible doesn't run on check-in pages either. It never has. --- ## 4. The reader The usual open-source barcode reader is ZXing. I used [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp), the maintained C++ version, compiled to WebAssembly by the [zxing-wasm](https://github.com/Sec-ant/zxing-wasm) project. Browsers have a built-in barcode API, but Safari on the iPhone doesn't have it. A lot of hosts carry iPhones. By default zxing-wasm downloads its WebAssembly file from a CDN. The check-in page can't contact a CDN, and I wouldn't want it to. So the build puts the WebAssembly inside our own script. It's 1.3 MB, about 540 KB compressed, and the browser only downloads it when the host actually takes a photo. The part I wrote is 147 lines of JavaScript. --- ## 5. Four things that didn't work the first time **iPhone photos.** iPhones save photos as HEIC by default, and the WebAssembly reader can't read HEIC. The browser can, though. So the page has the browser decode the photo onto a canvas and hands the reader the pixels. **Line breaks.** By default zxing-wasm returns text the way a person would want it printed, so a line break comes back as the four characters ``. The license data is a list of fields separated by line breaks. Asking for plain text fixed that. **Tilt.** On a clean, straight image of a license barcode the reader worked right away. Then I made test photos that look like what a host actually takes: tilted, a little blurry, noisy, the card off-center in a 4000 by 3000 picture. At 9 degrees it found nothing. I measured, and the PDF417 detector gives up past about 2 degrees of tilt. Nobody holds a card that straight. If the first read fails, the page now tries the photo turned in 4-degree steps, out to 20 degrees each way, and stops at the first one that reads. On my workstation the tilted photos read in about a tenth of a second, and the full-size photo in a third of one. A photo with no barcode in it gives up after about 0.6 seconds and asks for a retake. A phone is slower. I haven't timed one. **Where the name sits.** The fields are three letters and a value, each on its own line: `DCS` is the family name, `DAC` the first name, `DAD` the middle name. Except the first field of the block has no line of its own. It's stuck to the end of a header, like `...0024DLDCSWHITAKER`. My tests used a sample where the name came first, and they caught it. Older licenses also spell the name differently: `DCT` for given names on cards from 2003 to 2008, and `DAA` for the whole name, as "WHITAKER,DANA,MARIE", before that. The page reads all three layouts, and the tests cover each one. --- ## 6. What it doesn't do - It reads US and Canadian licenses and ID cards, nothing else. For a passport, the host looks at the passport. - It compares names, not faces. Whether the face matches the photo on the card is up to the host, the same as it always was. - A nickname that isn't the start of the name won't match. "Matt" partly matches Matthias. "Bill" doesn't match William at all. - It only appears for a guest whose email isn't confirmed. Everyone else is already on the list. Every test above used barcodes I generated. Then it read a real one. The help page at https://simple.remind.rsvp/docs/ has a short version of this, under "Checking people in at the door."