RFCs are a way for Atlassian to share what we’re working on with our valued developer community.
It’s a document for building shared understanding of a topic. It expresses a technical solution, but can also communicate how it should be built or even document standards. The most important aspect of an RFC is that a written specification facilitates feedback and drives consensus. It is not a tool for approving or committing to ideas, but more so a collaborative practice to shape an idea and to find serious flaws early.
Please respect our community guidelines: keep it welcoming and safe by commenting on the idea not the people (especially the author); keep it tidy by keeping on topic; empower the community by keeping comments constructive. Thanks!
Project summary
We’re developing the capability of First-in, First-out (FIFO) processing of events in Forge queues. This will include a new Forge module, fifoConsumer
. This capability aims to address the limitations of current async events by providing FIFO sequential event processing.
Timeline:
-
Publish: 16 September, 2025
-
Discuss: 7 October, 2025
-
Resolve: 28 October, 2025
Problem
Currently, the Async Events API allows apps to push events to a queue for later processing. This, however, doesn’t guarantee the order of events, making it unusable for workflows or integrations that require strict ordering. If events are not processed in the required strict order, this can lead to inconsistent states, race conditions, and failed operations.
Proposal
To address this, we’re proposing the addition of FIFO event queues.
With FIFO event queues, an app can declare a queue
in the manifest with a single consumer function.
Along with a queue
, we will have a groupId
parameter in place, and the ordering of events will be guaranteed within a unique combination of installationId
, queue
, and groupId
. This implementation may change, depending on feedback gathered.
Producers
Apps can push events using the push
API, which will allow apps to push multiple events at a time.
Sending events from a Forge app
import { FifoQueue } from '@forge/events';
const fifoQueue = new FifoQueue({ queue: 'my-queue' });
export async function sendEvent() {
const result: PushResult = await fifoQueue.push([{
groupId: "fifo-mvp-group",
eventId: "b462857a-1426-4ef7-8332-1747080fb461",
payload: {
hello: "world"
}
}]);
if (result.status == 'rejected') {
console.error(`Events was rejected with error code ${result.errorCode} and error message ${result.errorMessage}`);
}
}
export interface PushResult {
status: "success|rejected";
errorCode: "string";
errorMessage: "string";
}
Event retention
While events pushed to a FIFO event queue have a base maximum retention period (beyond which events will be dropped), this period has the potential to be extended in the event of outage periods caused by platform errors or instances of unexpected platform performance degradation.
Consumers
Consumers for each queue will receive a batch of events, where the order of the events belonging to a groupId
will be in the same order as they were sent by the producer for that groupId
.
Proposed consumer configuration in manifest
modules:
fifoConsumer:
- key: consumer1
queue: queue-1
function: consumer-function
- key: consumer2
queue: queue-2
function: consumer-function-2
function:
- key: consumer-function
handler: consumer.handler
- key: consumer-function-2
handler: consumer.handler2
Event processing
// Events in order for queue1-fifo-mvp-group
export const handler = async (eventContext: EventContext, context) => {
const {events} = eventContext;
for (const event of events) {
await processEvent(event.payload);
}
};
// Events in order for queue2-fifo-mvp-group
export const handler2 = async (eventContext: EventContext, context) => {
const {events} = eventContext;
for (const event of events) {
await processEvent(event.payload);
}
};
export interface EventContext {
installationContext: string;
groupId: string;
queueName: string;
events: Event[];
environmentId: string;
environmentType: string;
environmentKey: string;
}
export interface Event {
eventId: string;
receiptHandle: string;
payload: Record<string,any>;
ingestionTime: Timestamp;
}
Limitations
The following limitations are subject to change, depending on feedback gathered.
-
There would be limitations on the number of
queues
, number ofgroupIds
, and number of events that can be obtained by the consumer in a single invocation. -
A single consumer can be present for a single
queue
. -
Consumers will have a maximum timeout of 55 seconds.
-
Invocation of consumer functions is subject to the same rate limit as that of the rate limit present for invocation.
-
The payload size of the function cannot be more than 200 KB.
Feedback
Thank you for taking the time to read this RFC. We’d love to know what you think about this proposal, especially around the following points:
-
What is your use case for Forge FIFO queues? How are you solving for this currently?
-
Would you have a use case for different kinds of invocations or time limits, as compared to the ones being offered in this RFC?
-
In case of partial event processing failures on the app side, would you prefer to delete the processed events, or would you be willing to handle the logic to reprocess the same event?
-
What kind of of monitoring or observability capabilities would be helpful for this feature?
-
Would you need to use dead-letter queues (DLQs), which handle and store messages that cannot be processed? If yes, what would be your use case for this functionality?