Skip to content

Commit b7247c4

Browse files
committed
fix(gmail): clear the history cursor only from a poll that held one
A search-mode poll (no stored cursor) sent historyId: undefined, which updateWebhookProviderConfig treats as a key removal. If it overlapped a newer poll that had just stored a cursor, it deleted that cursor. The poll state is now built once and carries historyId only when this poll started with a cursor or found one, so only a poll whose cursor Gmail rejected can clear it.
1 parent d4dfd17 commit b7247c4

2 files changed

Lines changed: 18 additions & 17 deletions

File tree

‎apps/sim/lib/webhooks/polling/gmail.test.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function persistedHistoryIds() {
136136
)
137137
}
138138

139-
describe('gmailPollingHandler expired history cursor', () => {
139+
describe('gmailPollingHandler history cursor', () => {
140140
beforeEach(() => {
141141
mockResolveOAuthCredential.mockResolvedValue('access-token')
142142
mockProcessEvent.mockResolvedValue({ success: true })
@@ -180,4 +180,14 @@ describe('gmailPollingHandler expired history cursor', () => {
180180

181181
expect(persistedHistoryIds()).toEqual(['40100'])
182182
})
183+
184+
it('leaves the stored cursor untouched when a search-mode poll finds no mail', async () => {
185+
mailboxWithExpiredHistory({ profileHistoryId: '40100', inboxMessages: [] })
186+
187+
await expect(gmailPollingHandler.pollWebhook(context({}))).resolves.toBe('success')
188+
189+
expect(mockUpdateConfig).toHaveBeenCalledTimes(1)
190+
const [, updates] = mockUpdateConfig.mock.calls[0]
191+
expect(updates).not.toHaveProperty('historyId')
192+
})
183193
})

‎apps/sim/lib/webhooks/polling/gmail.ts‎

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,14 @@ export const gmailPollingHandler: PollingProviderHandler = {
8383
requestId,
8484
logger
8585
)
86+
const pollState = {
87+
lastCheckedTimestamp: now.toISOString(),
88+
// Omit unless this poll held a cursor, so it can't clear one an overlapping poll stored.
89+
...(config.historyId || latestHistoryId ? { historyId: latestHistoryId } : {}),
90+
}
8691

8792
if (!emails || !emails.length) {
88-
await updateWebhookProviderConfig(
89-
webhookId,
90-
{
91-
lastCheckedTimestamp: now.toISOString(),
92-
historyId: latestHistoryId,
93-
},
94-
logger
95-
)
93+
await updateWebhookProviderConfig(webhookId, pollState, logger)
9694
await markWebhookSuccess(webhookId, logger)
9795
logger.info(`[${requestId}] No new emails found for webhook ${webhookId}`)
9896
return 'success'
@@ -110,14 +108,7 @@ export const gmailPollingHandler: PollingProviderHandler = {
110108
logger
111109
)
112110

113-
await updateWebhookProviderConfig(
114-
webhookId,
115-
{
116-
lastCheckedTimestamp: now.toISOString(),
117-
historyId: latestHistoryId,
118-
},
119-
logger
120-
)
111+
await updateWebhookProviderConfig(webhookId, pollState, logger)
121112

122113
if (failedCount > 0 && processedCount === 0) {
123114
await markWebhookFailed(webhookId, logger)

0 commit comments

Comments
 (0)