OAuth email carrier sends an expired access token, and a rejected token permanently drops the notification

Description

Summary

The Gmail/Outlook carriers keep using a stored access token for a full minute after it has expired, and when the provider rejects a token the notification is dropped permanently — no refresh, no retry. The approver is never notified, the step is stamped "Notification not delivered", and an admin has to press "Send manual notification" by hand.

Notification delivery failed: emailovh.atlasinc.guarana.http.HttpException: IDX14100: JWT is not well formed, there are no dots (.).

Root cause

  1. The expiry check has the wrong signOAuth2TokenRepository.loadAccessToken:

if (record.getAccessTokenExpiration().plus(ACCESS_TOKEN_MIN_LIFESPAN).isAfter(now)) {

The constant is one minute and its name states the intent: reuse the token only while it has that much life left. plus inverts it, so for 60 seconds past expiry the app knowingly sends a dead token instead of refreshing. Should be minus.

  1. A rejected token is terminal with no retry. EmailService.isRetryableCarrierError retries only 429 and 5xx, and TransientFailures.classifyByNetwork only on an IOException cause, so a 401 becomes DeliveryOutcome.Failure. Nothing forces a refresh and resends, so NotificationDispatchWorker.handleFailure records the error, the step is stamped, and the notification is gone.

OutlookApiCarrier's error handler also discarded the status code and URL, so the recorded detail did not even show the 401.

Manual tester notes

What was broken: the Gmail/Outlook carriers kept using an access token for a minute after it had expired, and when the provider rejected a token the notification was dropped for good - the approver was never notified, the step was stamped "Notification not delivered" and an admin had to resend by hand.

  1. Happy path. Authorize the carrier, send a test email, run one approval with an approver notification. Both arrive, no step is stamped "Notification not delivered".

  1. Dead authorization. Revoke the app at Google / Microsoft, then send. Expected: one failure carrying the HTTP status code and a message telling the admin to re-authorize - not the generic "Failed to send test email." - and no endless retrying. Re-authorizing must fix it immediately, without a restart, and "Send manual notification" on the stamped step must deliver.

  1. Carrier switching. Test email plus a real notification on each carrier (Integrated, SMTP, Gmail, Outlook). Switch carriers back and forth, re-authorize with a different account and change the sender email - the next mail must always use what was just saved (carriers are now cached for 10 minutes).