Skip to content

Conversation

@nshirley
Copy link
Contributor

@nshirley nshirley commented Dec 13, 2025

Because:

  • We want to enable retrying an email send if the send fails

This Commit:

  • Adds a new retryWithBackoff for accounts-email-sender
  • Adds configuration to change maxRetries, backoffMs, ignoreTempaltes
  • Adds tests for new functionality

Closes: FXA-12591

Checklist

Put an x in the boxes that apply

  • My commit is GPG signed.
  • If applicable, I have modified or added tests which pass locally.
  • I have added necessary documentation (if appropriate).
  • I have verified that my changes render correctly in RTL (if appropriate).

Screenshots (Optional)

Please attach the screenshots of the changes made in case of change in user interface.

Other information (Optional)

Any other information that is important to this pull request.

@nshirley nshirley requested a review from a team as a code owner December 13, 2025 20:44
@nshirley nshirley force-pushed the FXA-12591 branch 2 times, most recently from b18fb68 to b0b6425 Compare December 15, 2025 17:38
retryWithBackoff<T>(
fn: () => Promise<T>,
{ maxRetries: maxAttempts, backoffMs }: MailerConfig['retry'],
{ template }: Pick<Email, 'template'>
Copy link
Contributor Author

@nshirley nshirley Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized, I don't love this, having to pass in the email for the template value which is just used in the statsd. I want to keep the Pick<> in case template becomes a stronger type, like a union type. But also, do we already have something to do this backing off, from a library we're already importing maybe?

But, I'm unsure a better way to do this outside of just hoisting the logic back into the sendMail function, but then that'll start getting bloated.

doc: 'Always ignore retries for these email templates',
format: Array,
default: [
/** No default for now */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May not be necessary, but I figured there will be emails we never want to have retry if send fails. Maybe something like MFA codes?

* @param fn The promise to retry
*/
retryWithBackoff<T>(
fn: () => Promise<T>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL: this has to be a factory function you can't pass in just a Promise - with the latter, the loop will have reference to the initial promise, which once resolved or rejected will continue to return that value instead of executing again. With the factory approach, the loop gets a new instance of the promise each time, thus "making" the request again each time!

Because:
 - We want to enable retrying an email send if the send fails

This Commit:
 - Adds a new retryWithBackoff for accounts-email-sender
 - Adds configuration to change maxRetries, backoffMs, and
   ignoreTempaltes
 - Adds tests for new functionality

Closes: FXA-12591
this.statsd.increment('email.send.retry', {
template,
});
setTimeout(execute, backoffMs * Math.pow(2, attempts - 1));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have some kind of jitter in here, even a small amount so we're not always backing off a static amount?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants