Presso Events
The Upload Link That Closes Itself
You have an upload link somewhere that never expires.
Not the ticketing checkout. The other one.
The Google Form you built two years ago so speakers could send you a headshot. The Dropbox folder you shared with a sponsor once and never revoked. The “just email me the photo” address that turned into “just use this link” because emailing photos back and forth got tedious after the third event.
It still works. Anyone holding the link can still use it. You have probably forgotten it exists.
That is not a dig. It is the natural shape of the problem. Someone needs to get a file onto your system, and the fastest fix is a door that stays open. Building a door that closes itself takes longer than building the event.
We hit the same choice this week, from the other side of it.
Presso Events runs through an MCP server, which is a formal way of saying an AI tool calls functions, not a person clicking through a browser. That is fine for almost everything an organiser does. It is useless for a photograph. A tool call cannot attach a file. There is no upload button inside a conversation.
So when organisers asked to set the hero photo on their event page without a console to log into, we faced your exact choice with the speaker headshots. Open a door and leave it open. Or build one that closes.
We built one that closes.
A tool call mints a link. Signed, tied to one event, good for thirty minutes. The organiser opens it on their phone, the photo lands, the link stops working. Ask again tomorrow and you get a new one. Nothing standing around for someone to find later.
That part travels, and you can copy it without touching a line of our code. If you are handing anyone outside your team, an organiser, a speaker, a sponsor, a way to put something on your server, give it an expiry and exactly one job. Not “the upload page.” “The upload link for this one photo, until half past three.”
Here is the part we nearly got wrong.
The feature had been sitting half-built on a branch for eighteen days. In that time, a different piece of work shipped the actual hero image field on the event page. Our branch did not know that. It still thought the hero photo lived in the old field, the one built for the logo.
Had we merged it as written, here is what would have happened to the first organiser who used it. They open the link. They upload the photo. They see a message saying it worked.
Because it would have worked. The bytes land, the database write succeeds, nothing throws an error.
And their event page looks exactly the same as before. No hero photo. No error either. Just a green tick and nothing to show for it.
We have built this exact shape of bug before. Twice, on other features, in other months. At some point you stop calling it bad luck and admit it is a shape you keep building by accident: a message that is true about the write and false about the outcome. The system did precisely what it was told, and not the one thing anyone actually wanted.
Caught before it shipped, this time, because the test checks the event page for the photo. Not the upload endpoint for a 200.
Silent failures are the worst kind for anyone deciding whether to trust a platform. A crash gets reported and fixed. A quiet no-op just sits there, and the organiser assumes the fault is theirs, tries a smaller file, tries a different browser, gives up, and never tells you why.
That is the second thing worth taking from this, whether or not you touch a line of code all week. A “saved” message is a claim about the save. It is not proof about the thing you actually cared about. If you run anything with a save button, a sync job, a webhook that reports success, ask yourself when you last checked the real outcome rather than the confirmation screen.
Most people have not, recently. I had not, until a branch that sat for eighteen days nearly proved it to an organiser on our behalf.
Ours closes itself now. Thirty minutes, one event, one job, then gone.
The one you have forgotten about is still open.