WORKBENCH · ARCHAEOLOGY
PCI · 1999

I built an internal comms tool at 17. Beekeeper was the second one.

Looking for something else in my Drive, I found a 28 KB zip from 1999. Inside: 823 lines of Java, two UDP ports and six text files, written to stop phone messages getting lost in my father's five-person office in Mexico City. I didn't remember writing most of it. The data model is the one I'd spend the next fourteen years of my life on.

823LINES OF JAVA
5USERS
2UDP PORTS
26YEARS IN A ZIP

PciCheck.java 05.08.1999 · Server.java 14.11.1999 · Pci.java 19.02.2000

The problem was the handoff

My father ran a business in Mexico City. Five people: him, Tere, Rita, Gaby and Mary. The phone rang all day, and the person it rang for was usually not at their desk — in the warehouse, with a client, out.

So somebody took the call, wrote it on paper, and walked it over. Sometimes. The note got lost, or it landed on a desk nobody came back to that afternoon, or two people took the same call and neither knew.

I was seventeen and I had just learned sockets. So I built the thing that was in front of me.

The product was never really about messages. It was about a handoff: somebody has information, and the person who needs it is somewhere else.

What it looked like

One window. Pci.java lays it out with setLayout(null) and positions every single control by hand with setBounds(x, y, w, h) — so this reconstruction isn't from memory or a screenshot. It's drawn from the coordinates in the source.

PAG
_
Usuario:
Lic. Grossmann
Contaseña
••••••
Autorizado
Mensaje:
Habló el Sr. Möller de Filatelia. Pregunta por los sellos conmemorativos del 50 aniversario. Vuelve a llamar a las 4.
Tipo:
Mensaje interno
Llam. Personal
Llam. CNLT
Llam. Proveedores
Llam. Filatelia
Llam. Ventas
Llam. Cliente
Llam. Admon
Otros
Para:
Lic. Grossmann
Tere
Rita
Gaby
Mary
Todos
:8888
Enviar
Buzón de mensajes para : Lic. Grossmann
Leer nuevos
Leer viejos
No: 0042 De: Gabriela Tipo: Llam. Proveedores Hora: Sat Feb 19 11:34:07 GMT-06:00 2000 Mensaje: Confirman el envío del lunes ********** No: 0041 De: Sra. Tere Tipo: Llam. Cliente

Pci.java · JFrame “PAG” · 550 × 700 px · rebuilt from the setBounds() calls

Aviso!
Mensaje recibido de Gabriela a las Sat Feb 19 11:34:07 GMT-06:00 2000

The notification. Fired by an applet, not by a refresh.

Server
Socket opened on: 9999 ReadThread listening on port 9999 Ready to accept packages Processing incoming Message... Message received at: Sat Feb 19 11:34:07 from: gab to: alj Message written to: alj.txt Message written to logFile About to send notification to : Lic. Grossmann

Server.bat · the whole backend, printing to a DOS window

The typo that survived

The password label reads “Contaseña”. It has been wrong for twenty-six years and it is reproduced exactly.

That grey box at :8888

Not decoration. It's a Java applet embedded in the window, holding a socket open, waiting to be told something arrived.

The passwords

passwords[] = {"","","","",""}. Five empty strings. The auth flow was built. The passwords were never set.

How it worked

No database. No framework. No HTTP. A server holds one UDP port open; a second port exists purely so the recipient finds out without looking.

CLIENTS · 10.0.0.0/24 Lic. Grossmannalj · 10.0.0.2 Sra. Tereter · 10.0.0.3 Ritarit · 10.0.0.4 Gabrielagab · 10.0.0.1 Marymar · 10.0.0.5 Pci.bat → java Pci UDP :9999 SERVER Server.javaDatagramSocket(9999) ReadThreadreceives 256 bytes WriteThreadparses and writes notify.send() appends MAILBOXES · FLAT TEXT alj.txtoldalj.txt ter.txtoldter.txt rit.txtoldrit.txt gab.txtoldgab.txt mar.txtoldmar.txt all.txtbroadcast pci.logevery message, forever newest message goes on TOP of the file numbered No: 0001 … 9999 UDP :8888 · notification listenCThreadapplet in the window · listens on :8888 Pci.popUp()“Aviso!” window on the recipient's screen THE WHOLE LOOP Gaby types → :9999 → the server files it in alj.txt and pci.log → :8888 → a window opens on my father's screen. No refresh, no polling.

Eight bytes of header, by hand

No JSON. No XML. No serialisation. The entire message is one string inside a 256-byte datagram, and fixed character positions are the contract.

aljbytes 0–2
gabbytes 3–5
04bytes 6–7
Habló el Sr. Möller…bytes 8 → 255
// Pci.java — the line that builds the packet
msg = codeArray[codeTo] + codeArray[userIndex] + tipo + messageToSend;
sendProto.sendMsg(msg);

// WriteThread.java — the line that takes it apart, across the wire
msg      = data.substring(8);
codeTo   = data.substring(0,3);
codeFrom = data.substring(3,6);
typCode  = data.substring(6,8);

The nine call types are a portrait of the business

This is the part I'd forgotten, and it's the part that matters. These categories aren't generic. Every line was a thing that actually happened in that office, often enough to deserve its own radio button.

CodeTypeWhat it was
00Mensaje internoDesk to desk
01Llam. PersonalFamily, things from home
02Llam. CNLTOne account important enough to get its own category
03Llam. ProveedoresSuppliers
04Llam. FilateliaStamps. The business itself, on its own line
05Llam. VentasSales
06Llam. ClienteCustomers
07Llam. AdmonAdmin
08OtrosEverything else
09No especificadoNot in the interface — the server's fallback when the code arrives broken

What I got wrong

A lot, and it's worth being specific, because the bugs are more interesting than the parts that worked.

No acknowledgement

UDP. Fire and forget. If a datagram was dropped the message simply never existed, and nobody found out.

256 bytes, silently

Anything past the buffer was truncated with no warning to the sender.

Shared mutable indices

i and k are instance fields, and the lookup loops start wherever they were left. It only worked because every message got a fresh WriteThread.

Two tables of IPs

WriteThread and notify each declare their own ipArray — and they don't match. Whoever it notified, it wasn't always who it filed the message under.

State inside the text

To get the next message number it reopens the file and parses substring(4,8) of the first line. The database was the formatting.

Security by comment

Four wrong passwords called System.exit(0). All five real passwords were the empty string.

Version 0.1

In 2011 I co-founded Beekeeper. The pitch, cleaned up for investors, was a communication platform for the people who don't sit at a desk — factory floors, hotels, hospitals, retail. It ended up serving 1,500+ customers in 130+ countries before the exit.

The data model on the whiteboard in 2012 was: recipient, sender, type, timestamp, body, a mailbox per person, an archive, an audit log of everything, and a notification that reaches someone who is not looking at the app.

That is a description of WriteThread.java, written in 1999 by someone who had never heard the phrase “frontline worker”.

I'm not claiming foresight. I'm claiming the opposite: I didn't invent that problem, and neither did the market. It was already sitting in a five-person office, obvious enough that a teenager with a socket tutorial could see it. The reason it took another twelve years and a company is that seeing the problem was never the hard part.

In 1999 I was competing with a paper note walked across an office. Fifteen years of Beekeeper and fifteen hundred customers later, I was still competing with the paper note.

The other thing I take from the zip is less flattering and more useful. The 1999 version has no acknowledgements, no retries and a silent truncation at 256 bytes — and it ran in that office for months. Nobody complained, because the alternative was a paper note. Shipping something honest and narrow beat shipping nothing, and it beat waiting until I understood TCP.

That's still how I work. It's rule one on the workbench: if it hasn't run in production against real life, it's an opinion.

The companion piece to this one is about the other thing I found in that Drive folder — the Jini specification, dated December 1998 — and what happened when I finally built the house it described: Jini, 1999 → the house, 2026.

← BACK TO THE WORKBENCH