Escalate the third declined charge in a month
Alert the account owner's team when an account's card fails for the third time in a calendar month in PostgreSQL. A window function makes the count.
- PostgreSQL
- Slack
- Zendesk
- Billing
- once per account per month
The trigger
One read-only SELECT against PostgreSQL. Rowfire polls it for new rows, and the rule fires once per account per month.
SELECT *
FROM (
SELECT a.id AS account_id,
a.name AS account,
a.owner_email,
pa.invoice_no,
pa.amount,
pa.attempted_at,
count(*) OVER (
PARTITION BY pa.account_id, date_trunc('month', pa.attempted_at)
ORDER BY pa.attempted_at
) AS declines_this_month
FROM payment_attempts pa
JOIN accounts a ON a.id = pa.account_id
WHERE pa.outcome = 'failed'
AND a.is_internal = false
) d
WHERE d.declines_this_month >= 3A third failure in one month is a derived event: a count over rows, not any single row. The window function numbers each account’s declines within the month, and the outer query keeps the third and later ones. Table and column names are a sketch; adapt them to your schema.
The clock is attempted_at and the key is account_id. With once per
account per month, the third decline fires and the fourth and fifth that
month don’t.
{{ account }} has had {{ declines_this_month }} declined charges this month. Latest: {{ invoice_no }} ({{ amount }}). Owner: {{ owner_email }}.
Pair it with the everyday rule on single declines in declined cards to Slack and Zendesk: the first one handles routine retries, this one escalates the accounts that keep failing.
Questions
How is this different from alerting on every declined card?
The first decline is often an expired card the customer fixes on their own. The third in a month is a pattern worth a person's time, so this rule only fires once the count reaches three.
Does Rowfire's time window cut off the earlier declines?
No. Rowfire wraps the whole query in its time window, so the window function counts over every failed attempt first, and only the outer rows are filtered by time.