///|
/// Execute `f`, retrying up to `max_retries` total attempts when it fails with
/// a retryable error (transport or transient SMTP failure). Permanent errors
/// (`SmtpPermanent`, `Auth`, `Config`, `ContentLength`, `Timeout`) are raised
/// immediately. This gives callers bounded retry with no external waiting
/// beyond a single scheduler pause between attempts.
pub async fn[T] with_retry(
max_retries : Int,
f : () -> T raise MailFailure,
) -> T raise MailFailure {
let mut attempt = 0
let mut value : T? = None
while value is None {
attempt += 1
try {
value = Some(f())
} catch {
MailFailure(err) => {
if attempt >= max_retries || !is_retryable(err) {
raise MailFailure(err)
}
@async.pause() catch {
_ => ()
}
}
}
}
value.unwrap()
}
///|
/// True when a failure is worth retrying.
pub fn is_retryable(err : MailError) -> Bool {
match err.kind() {
Transport => true
SmtpTransient => true
Io => true
_ => false
}
}