I am currently migrating the BHNT attendees from Meetup to SmokeSignal (bit of context here: Sustaining events | ligi | WhiteWind blog)
But I am facing a problem now - with SmokeSignal you cannot limit RSVPs - and yesterday the event was already a bit more full than I would want it to be - some people did not get chairs. In meetup I was able to limit the amount of people that can RSVP - people where able to then go on a waitlist and I could then pick people from the waitlist (e.g. people with hacks to present) - or the waitlist was filled when people where removing their RSVP.
How do you think this should work @ligi?
As an organizer, what other flows are must haves vs nice to haves?
This is a tricky problem to solve, and I have a few thoughts on how to move in that direction.
First, I want to recognize that in a decentralized and distributed platform records are owned by their creators and records can reference other records, it’s really really hard to prevent people from creating the records they want to. Anyone can create an RSVP, thus there is no practical way to prevent people from doing so.
Smoke Signal could provide some basic soft limits that make it more difficult to do so, but people who really want to get around those limits could. For example, we could add an attendee constraints block to events that looks something like this:
{
"$type": "community.lexicon.calendar.event",
"name": "Coffee Chat",
"attendees": {
"max": 20
}
}
It would be entirely up to applications to display and use that information to impose that “max attendee” constraint. Doing so also adds the complex requirement that applications have enough of a world view and data to apply them uniformly.
I’m not against implementing a basic max-rsvp-for-status feature where once a threshold of “going” is reached, it prevents additional “going” RSVPs from being created and limits new RSVPs to “interested” and “not going”. I’m just worried that people won’t practically realize that it isn’t a hard limit.
After thinking about it for a bit - my suggestion would be the following:
we add one new record type like: community.lexicon.calendar.rsvp_confrmation
(name should be improved)
so the flow is:
- organizer creates event record
community.lexicon.calendar.event
as currently done - attendee creates
community.lexicon.calendar.rsvp
- organizer creates
community.lexicon.calendar.rsvp_confirmation
As so the use-cases stay mostly outside the lexicon and we can keep this minimal and reduce the pain of migrating.
So one simple flow could be that the organizer always issues the confirmation. Or only issues confirmation when under a certain amount of people.
This way also specific selection of people is possible (e.g. speakers or other guests)
this could already work as is. But I would also love a rsvp_type field in the event (can default to the current behavior) - but could also describe this new path or even a link to an external ticketing solution. I think this will provide a good update path with minimal changes to the lexicon.
I’ve got a solution in-progress that uses a pattern called signatures.
The idea is that when someone demonstrates their intention to attend an event, they create an RSVP record with the status “going”. This is pretty straight forward.
An event organizer can “accept” their RSVP by providing a signature that is added to the RSVP record looks like this:
{
"$type": "community.lexicon.calendar.rsvp",
"status": "community.lexicon.calendar.rsvp#going",
"subject": {
"cid": "bafyreic6ev7ulowb7il4egk7kr5vwfgw5nweyj5dhkzlkid5sf3aqsvfji",
"uri": "at://did:plc:lehcqqkwzcwvjvw66uthu5oq/community.lexicon.calendar.event/3ltl5aficno2m"
},
"createdAt": "2025-07-14T19:36:02.830Z",
"signatures": [
{
"$type": "community.lexicon.calendar.rsvpAcceptanceSignature",
"issuedAt": "2025-07-14T12:43:53.919Z",
"issuer": "did:plc:lehcqqkwzcwvjvw66uthu5oq",
"signature": "u56lN3UF3E..",
"status": "accepted"
}
]
}
This has several effects.
The first is that both the event organizer and event attendee have demonstrable consent to go to the event. The attendee created the RSVP and the organizer has “accepted” the RSVP.
This also implies consent by the record owner (event attendee) that they are OK with and accept the acceptance by allowing the signature to be added to the RSVP record that they own and store in their PDS.
Next, the “proof” that the organizer has accepted the RSVP is immediately available as a part of the RSVP record itself. That means that jetstream/relay subscribers will see 2 commits: The first for the RSVP being created and a second for a modification to include a signature by the event organizer.
Lastly, this could support both anonymous-record and linked-record signatures. The above example is an anonymous-record signature where the record content is directly embedded in the list of signatures.
A linked-record signature would look like this:
{
"$type": "community.lexicon.calendar.rsvp",
"status": "community.lexicon.calendar.rsvp#going",
"subject": {
"cid": "bafyreic6e..",
"uri": "at://did:plc:lehcqqkwzcwvjvw66uthu5oq/community.lexicon.calendar.event/3ltl5aficno2m"
},
"createdAt": "2025-07-14T19:36:02.830Z",
"signatures": [
{
"$type": "com.atproto.repo.strongRef",
"uri": "at://did:plc:lehcqqkwzcwvjvw66uthu5oq/community.lexicon.calendar.rsvpAcceptanceSignature/01k1gexswnrs6y22cg75ytpgs4",
"cid": "bafyreic6e.."
}
]
}
This gives the signer the ability to revoke their signature by deleting or changing the record. During validation when the linked-record signature is retrieved and the signature checked, if the AT-URI and CID does not match, the signature can be considered revoked.
Currently this is implemented for badges.
For example on https://badges.smokesignal.events/ you can see that @ligi has the badge “Bug Squasher”.
The badge award record for that is:
at://did:plc:yw5jnwqzszdy273jm77n52c7/community.lexicon.badge.award/3ltwciyroim2a
The record has this shape:
{
"did": "did:plc:yw5jnwqzszdy273jm77n52c7",
"$type": "community.lexicon.badge.award",
"badge": {
"cid": "bafyreif4pspdiak3v2xqh55x2i7m3vhdlnjpprb5tv4uuheh6r3knirvie",
"uri": "at://did:plc:tgudj2fjm77pzkuawquqhsxm/community.lexicon.badge.definition/3ltwciyroim2a",
"$type": "com.atproto.repo.strongRef"
},
"issued": "2025-07-14T12:15:25.232292Z",
"signatures": [
{
"issuer": "did:plc:tgudj2fjm77pzkuawquqhsxm",
"issuedAt": "2025-07-14T12:32:47.917Z",
"signature": "uNn0lKYATaSO8TcLutffeaHeoF2cnF9iWskQyq9CxDijBowJWhwVQLpJrgqRx31k6dmhIkpjmqHdKjtgb6sUbeA"
}
]
}
A viewer of that badge knows that it is authentic because:
- The authority of the badge definition is the same issuer of the signature
- The signature is computed off of the normalized CBOR value of the record (including a reference to the DID and collection)
So where all of this leads to is the idea of filtering and verification.
Smoke Signal could, for example, automatically “accept” (by way of creating a RSVP acceptance signature) for the first 20 (or however configured) “going” RSVPs for an event. Once the limit is reached, it would soft-prevent “going” RSVPs from being created and instead default to “interested” (with “going” disabled).