get()->filter( fn (MailOAuthConnection $connection): bool => $connection->usable(), ); if ($connections->isEmpty()) { $this->info('No connected OAuth mailboxes; nothing to refresh.'); return self::SUCCESS; } foreach ($connections as $connection) { $hadError = $connection->last_error !== null; try { // Serialised against sends: refresh() on its own is the // other half of the race freshAccessToken()'s lock is // there to stop. $refreshed = $brokers->for($connection->provider)->refreshSerially($connection); // Standing aside is a healthy outcome, not a silent one: // somebody else is refreshing this very connection, which // slides the window just as well. Saying "Refreshed" for // it would describe a token request that never happened. $this->info($refreshed ? "Refreshed {$connection->provider->value} ({$connection->account_email})." : "Skipped {$connection->provider->value} ({$connection->account_email}): a refresh is already in progress."); // Back from the dead (an admin fixed things upstream // without reconnecting): the applier may have been // resolving "not ready" and must see the recovery. if ($hadError) { $mailConfig->flush(); } } catch (MailOAuthException $e) { $this->error("Could not refresh {$connection->provider->value}: {$e->getMessage()}"); if (! $e->needsReconnect) { continue; } // Only on the transition into the broken state — the // notification would otherwise repeat daily for as long // as nobody reconnects, and a nagging alert trains // people to ignore the one that matters. // // Asked of broken_notified_at, not of last_error. The // question is "have the admins been told", and last_error // cannot answer it: the send path writes that column too // (OAuthCodeFlowBroker::refresh, reached from // freshAccessToken) and notifies nobody. On an // installation that actually sends mail, that write lands // first — so reading it as "already told them" left this // silent for good, on exactly the installations whose // password-reset mail rides on the connection. if ($connection->broken_notified_at === null) { $recipients = array_values(User::query()->where('type', UserType::Staff)->get() ->filter(fn (User $staff): bool => $permissions->allows($staff, Permission::EditSettings)) ->all()); $notifier->send('mail_oauth_connection_broken', $recipients, data: [ 'provider' => $connection->provider->label(), 'account' => (string) $connection->account_email, ]); $connection->broken_notified_at = now(); $connection->save(); } $mailConfig->flush(); } } return self::SUCCESS; } }