Two capture tools, nine builds in ten days, and the misread business card that shaped everything. Built, demoed, not yet adopted.
Every conference ends the same way: a fistful of business cards in a jacket pocket. Everyone means to enter them into the CRM. Almost nobody does. The cards go through the wash, and the relationships go with them.
I work on TrebleHook, a CRM for the construction industry built on Salesforce. (A CRM is the system a company uses to keep track of its clients and deals.) Over about ten days this summer I built two tools that try to catch relationship data at the two moments it actually shows up, and usually disappears. The first is a business card scanner that lives inside the Salesforce app on your phone: you photograph a card, and it hands back a cleaned-up contact and their company, already checked against what's in the system so you don't create a duplicate. The second lives in the panel beside your Outlook inbox: open an email and it works out who sent it, reads their signature, suggests which project the email belongs to, and notices everyone else copied on the thread.
I should be precise about what I know and what I'm guessing. I built the screens; I haven't stood at the conference booth or watched a project manager dig through their inbox. The "before" picture I designed against is part real, part inference. The real part is concrete enough: one of my demo emails is a word-for-word recreation of how contacts get into CRMs today, a note to a coworker that says "Can you please add the following contacts to TrebleHook?" followed by five names, titles, and addresses. Somebody was about to type all of those in by hand. That email is the old way, exactly.
For years we simply couldn't help customers who wanted this. There used to be a third-party card scanner on the Salesforce AppExchange that people relied on, and a couple of years ago it was delisted and quietly went away. After that, when a customer asked us for a way to get cards into the system, we didn't have a good answer. The need didn't disappear; the tool did.
So I didn't start from a blank page. I put the requirements together the slow way, from talking to a handful of customers who actually wanted this, and from our sales team, who hear the same request over and over on presales calls. By the time I started building, I knew what it had to do, because the people who needed it had already told us.
The email assistant came from an even more specific place. On one presales call, a potential customer asked whether their next CRM could pull the details out of an email accurately and file them for you, instead of the usual copy-and-paste by hand. For them that wasn't a nice-to-have; it was a deciding factor in which system they'd choose. The question was clear enough that it was practically a specification, so I built the answer to it.
That's why the "not yet adopted" part further down bothers me less than it might. For years this was something we could not offer at all. Being able to build it, on requirements that came straight from the people asking for it, is the part I count as a win, whatever adoption does next.
One more piece of honesty, because it sits under everything below. Both of these tools are Lightning Web Components, which is Salesforce's current way of building custom screens. Before this, I had never built one. I'd built on the two older frameworks it replaced, Visualforce and Aura, so I knew the platform, the data, and roughly how the pieces fit together, but this framework was new to me. I built both tools while learning it, and I'll come back to what that meant at the end.
The whole design ended up resting on one uncomfortable fact: the software that reads a card gets small things wrong, and a small wrong thing in a CRM is worse than a blank space. A blank field, you notice and fix. A plausible-looking mistake, you trust.
Here is the example that taught me. Turning a photo of a card into usable text is a job called OCR, short for optical character recognition; it's the same kind of technology a bank uses to read a check. On a real card, scanned on a real phone in bad light, the OCR misread the email address. Say the correct address was jdoe@acme-env.com. What the software saw was jdoeaacme-env.com: the @ symbol had been read as the letter "a." That one slip can't be undone by simple rules, because once the @ is gone there's no way to know where it used to sit. My first version used a set of pattern-matching rules, and it handled the four cards I'd typed up myself perfectly. On the real card it quietly produced a contact with no email address and a nonsense website, and reported success.
The OCR read the @ sign as the letter a, and no rule on earth can tell you where it went.
That single failure set the shape of everything after it. The rules still run first, because they're instant and make the app feel quick. But the raw text the camera saw stays visible, tucked under a tap, so you can always check the machine's work against the actual card. Every field can be edited before anything is saved. And in the background, an AI model rereads the messy scan and repairs what only judgment can repair. It was the only thing I found that could look at jdoeaacme-env.com and correctly put the @ back, because it reasons the way a person would. Last, anything the tool creates can be undone with a single tap, on the theory that a tool confident enough to create records for you should be just as willing to take them back.
The first version froze on a real phone after a single card. I'd designed the camera like a conveyor belt, reopening itself for the next scan automatically, over and over. On an iPhone, that second automatic reopen simply hangs and never comes back. My only tester, me, plus a coworker whose phone I wasn't allowed to touch, had to force the app closed to recover. The fix was the embarrassing kind: take one picture per tap, and always reset the camera cleanly when a scan finishes, even when it fails.
Then I found the app had been reading the scanned words from the wrong place in the camera's results the whole time, so it had only ever been working from a thin slice of what was actually captured. Pointing it at the right place gave the rules real text to work with for the first time.
Then the vocabulary was too narrow. I'd taught the tool construction job titles, superintendent, estimator, preconstruction, so when a card read Managing Editor it didn't recognize it as a person's title at all. In another signature, the words "Senior Project Executive" made the email tool suggest three unrelated senior-living projects, because it had latched onto the word "senior." Teaching software which words to ignore is humbling work.
Then came branding. A sales demo needed the email panel dressed in a prospect's colors, so I built a small theming system: fill in one record with a logo and three colors, and the whole product re-skins itself. The prospect's logo lost its last letter, twice, for two different reasons at once. The image I'd drawn spilled slightly past its own edges, and Outlook clings to old images with a fierce grip, so even after I fixed the artwork the panel kept showing the stale copy. (For honesty's sake: that logo was my own recreation, clearly labeled as such, because you can't deploy an image someone pasted into a chat.)
Then a deliberately adversarial review, me trying hard to break my own work, turned up the bug I'm most grateful never shipped. Outlook's side panel doesn't reload when you click a different email; it stays put and swaps the new message in underneath. My tool kept showing its reading of the previous email, which meant it could have filed a brand-new email under the wrong person's name. That's the quiet kind of data problem, the kind nobody catches until it's everywhere. It now notices when the email has changed and starts its analysis over.
Late in the build I wired the AI cleanup to run two ways. It can call the AI vendor directly on our own account, or route through Salesforce's own governed AI path, the Einstein Trust Layer, where the same request runs inside Salesforce's security boundary and is paid for in platform credits rather than dollars. A single setting picks which provider and which model, so the two can be compared head to head on the same card.
The cost comparison was clarifying. The direct route runs well under a penny per card. The governed route, going by published rates, runs somewhere in the range of ten to twenty times that. That isn't a knock on it. It's the measurable price of a real guarantee, that your data never leaves the platform's walls, and for some companies that guarantee is exactly worth paying for. What I like is that the design turns it into a deliberate choice instead of a default nobody examined.
Here's the honest ending. Everything above lives in a demo environment, not in production. The user base, so far, is one person testing with his own hands: real cards, a real inbox, a real phone. Nine rebuilds in ten days, and almost every one was set off by something breaking in that testing: the freeze, the clipped logo, a success screen that linked to the wrong record. That's the kind of specific, unsparing feedback you only get from someone who genuinely wants the thing to work.
So let me be honest in both directions. None of this is adoption yet, and I won't pretend otherwise. But I'm just as confident about the part that doesn't wait on adoption: as a way to get a business card or an email into a CRM, this is better than anything Salesforce gives you out of the box. The standard experience is a blank form and your own typing. This reads the card, checks it against your data, fixes its own mistakes, and lets you undo the whole thing if it got something wrong. That comparison I'll stand behind today.
What I can't hand you yet are usage numbers, because there isn't usage yet. If these tools clear their demos and land in real hands, the follow-up will have real numbers in it, or it won't run at all.
I want to be straight about this, because it's the part I'm proudest of and the part the story above quietly hides. I had never built a Lightning Web Component before this project. Read back over what broke and how it got fixed and it sounds like the work of someone who knew exactly what he was doing. I didn't. I'd built on the older Salesforce frameworks, Visualforce and Aura, so I understood the platform and the shape of the problem, but the framework itself was new to me the entire way through.
What made it work was a genuine partnership with an AI coding agent. I directed it, tested every version on real hardware, and pushed back with screenshots when something looked wrong; it wrote and deployed the code, explored the system, and reviewed its own work. The freeze and the clipped logo were caught by my hands, not its tests. It wrote in a framework I couldn't yet write in myself, and I could tell whether the result was right because I understood what it was supposed to do.
I don't think of this as "vibe coding," where you describe a feeling and ship whatever comes back. I had a clear picture of how the thing needed to be built from the very start: heuristics first so it feels fast, the raw scan always visible so you can check it, every field editable, an AI pass for the mistakes rules can't fix, and undo on everything. Those calls were mine, and they're also what made the whole process safe. When the machine is allowed to be wrong, and you can always see the mistake and take it back, you can move fast without betting the data on it. The agent closed a skill gap I genuinely had. It did not replace the judgment about what to build.
That's the part I'd underline for anyone uneasy about working this way. I'm not a front-end specialist, and I didn't turn into one overnight. I brought a decade of knowing how these systems should behave, paired it with a tool that could write in a framework I was still learning, and shipped something I'm confident beats the default. The gap it closed was mine. The method was steady enough that I turned around and did it again, on a harder problem, in the map with its own build story.