@@ -123,7 +123,7 @@ export async function insertRow(
123123 }
124124
125125 // Best-effort capacity check against the workspace's current plan limit.
126- await assertRowCapacity ( {
126+ const rowLimit = await assertRowCapacity ( {
127127 workspaceId : table . workspaceId ,
128128 currentRowCount : table . rowCount ,
129129 addedRows : 1 ,
@@ -144,6 +144,14 @@ export async function insertRow(
144144 now,
145145 } )
146146
147+ // Post-commit: a pre-commit notify would email/burn the dedup claim for a rolled-back insert.
148+ notifyTableRowUsage ( {
149+ workspaceId : table . workspaceId ,
150+ currentRowCount : table . rowCount ,
151+ addedRows : 1 ,
152+ limit : rowLimit ,
153+ } )
154+
147155 logger . info ( `[${ requestId } ] Inserted row ${ rowId } into table ${ data . tableId } ` )
148156
149157 const insertedRow : TableRow = {
@@ -194,13 +202,20 @@ export async function batchInsertRows(
194202) : Promise < TableRow [ ] > {
195203 // Best-effort capacity check against the workspace's current plan limit. Import
196204 // paths call `batchInsertRowsWithTx` directly and gate capacity up front instead.
197- await assertRowCapacity ( {
205+ const rowLimit = await assertRowCapacity ( {
198206 workspaceId : table . workspaceId ,
199207 currentRowCount : table . rowCount ,
200208 addedRows : data . rows . length ,
201209 } )
202210
203211 const result = await db . transaction ( ( trx ) => batchInsertRowsWithTx ( trx , data , table , requestId ) )
212+ // Post-commit: notify with the actual inserted count, so a rolled-back batch never emails.
213+ notifyTableRowUsage ( {
214+ workspaceId : table . workspaceId ,
215+ currentRowCount : table . rowCount ,
216+ addedRows : result . length ,
217+ limit : rowLimit ,
218+ } )
204219 dispatchAfterBatchInsert ( table , result , requestId , data . userId )
205220 return result
206221}
@@ -349,12 +364,20 @@ export async function replaceTableRows(
349364) : Promise < ReplaceRowsResult > {
350365 // All existing rows are deleted, so the footprint is just the new set. Checked
351366 // before the tx opens — never inside it (the plan lookup is a separate pool read).
352- await assertRowCapacity ( {
367+ const rowLimit = await assertRowCapacity ( {
353368 workspaceId : table . workspaceId ,
354369 currentRowCount : 0 ,
355370 addedRows : data . rows . length ,
356371 } )
357- return db . transaction ( ( trx ) => replaceTableRowsWithTx ( trx , data , table , requestId ) )
372+ const result = await db . transaction ( ( trx ) => replaceTableRowsWithTx ( trx , data , table , requestId ) )
373+ // Post-commit: footprint is the new set (prior rows deleted → prior count 0).
374+ notifyTableRowUsage ( {
375+ workspaceId : table . workspaceId ,
376+ currentRowCount : 0 ,
377+ addedRows : result . insertedCount ,
378+ limit : rowLimit ,
379+ } )
380+ return result
358381}
359382
360383/**
0 commit comments