Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: automatically retry failed scheduled sends #1660

Merged
merged 6 commits into from
Sep 18, 2024

Conversation

dkoo
Copy link
Contributor

@dkoo dkoo commented Sep 16, 2024

All Submissions:

Changes proposed in this Pull Request:

Implements an auto-retry system for scheduled newsletter sends, and adds logging for scheduled send failures.

#831 fixed handling for failed scheduled sends by resetting the post to draft status if a scheduled send failed to happen, but by resetting to draft status the newsletter must be manually sent or rescheduled. This PR will attempt to automatically retry the send in exponentially increasing intervals up to a maximum of 10 times.

How to test the changes in this Pull Request:

  1. Check out this branch.
  2. Add return new WP_Error( 'newspack_newsletters_error', 'Temporary error' ); to the first line of the Newspack_Newsletters_Service_Provider::send_newsletter() method to force a failure upon scheduled send.
  3. Schedule a newsletter to be sent at some point in the near future.
  4. Refresh the newsletter in the editor after the scheduled send time has passed and confirm you see an error message about the failed send at the top of the editor that says "Error sending newsletter: Temporary error". Also confirm that the post remains in "Future" status with a scheduled send time of some minutes in the future.
  5. Wait a while or bump the MAX_SCHEDULED_RETRIES number down to something like 3 and wait a bit less, then refresh the newsletter in the editor again. Confirm that the post has reverted to "Draft" status with the error message "Failed to send 10 times. Please check the provider connection and try sending again."
  6. Schedule the newsletter to be sent again at some point in the future, then wait for it to fail once.
  7. Remove the temporary error message you added in step 2 and wait for the next attempt—this time confirm the scheduled send succeeds and that the newsletter post successfully transitions to "Private" or "Published" status.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@dkoo dkoo self-assigned this Sep 16, 2024
@dkoo dkoo requested a review from a team as a code owner September 16, 2024 18:37
Copy link

codecov bot commented Sep 16, 2024

Codecov Report

Attention: Patch coverage is 28.98551% with 49 lines in your changes missing coverage. Please review.

Project coverage is 19.57%. Comparing base (b245716) to head (ad2246c).
Report is 82 commits behind head on release.

Files with missing lines Patch % Lines
...rs/class-newspack-newsletters-service-provider.php 28.98% 49 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             release    #1660      +/-   ##
=============================================
+ Coverage      17.74%   19.57%   +1.82%     
- Complexity      2316     2389      +73     
=============================================
  Files             43       45       +2     
  Lines           8758     9029     +271     
=============================================
+ Hits            1554     1767     +213     
- Misses          7204     7262      +58     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

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

When a newsletter send fails, it'll always send an email to the admin. Given that we're now adding a new retry layer for scheduled newsletters, it's probably best to suppress these emails until the attempts exhaust, otherwise they might get 5 emails regarding the same newsletter. More confusingly, they might get an email when the first attempt fails, only to get resolved by the second attempt.

if ( in_array( $new_status, self::$controlled_statuses, true ) && 'future' === $old_status ) {
update_post_meta( $post->ID, 'sending_scheduled', true );
$result = $this->send_newsletter( $post );
if ( is_wp_error( $result ) ) {
$prior_attempts = intval( get_post_meta( $post->ID, 'scheduled_send_attempts', true ) );
Copy link
Member

Choose a reason for hiding this comment

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

Why not leverage the existing newsletter_send_errors meta for this? It's persisted and each error entry is already an attempt.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

f430275 changes the approach to use the newsletter_send_errors meta.

@miguelpeixe
Copy link
Member

It might be a good idea to add some exponential backoff. If the first attempt failed because of a server hiccup, it would be fine to try again sooner, like 1 minute, then exponentially try on 2, 4, and 8 minutes. We use something similar for webhooks:

https://github.com/Automattic/newspack-plugin/blob/6e8d7205852f82367fe49c748aebb8a9d09491c9/includes/data-events/class-webhooks.php#L763-L765

@miguelpeixe
Copy link
Member

I left the attempts running while I reviewed, after it exhausted all 5 attempts the newsletter ended up being marked as sent 🤔

image image

@dkoo
Copy link
Contributor Author

dkoo commented Sep 17, 2024

@miguelpeixe thanks for the suggestions!

I left the attempts running while I reviewed, after it exhausted all 5 attempts the newsletter ended up being marked as sent 🤔

f430275 should fix this. It was happening because we were deleting the sending_scheduled meta when we hit max retries.

When a newsletter send fails, it'll always send an email to the admin. Given that we're now adding a new retry layer for scheduled newsletters, it's probably best to suppress these emails until the attempts exhaust, otherwise they might get 5 emails regarding the same newsletter. More confusingly, they might get an email when the first attempt fails, only to get resolved by the second attempt.

f430275 also addresses this. If the post has a sending_scheduled meta, it will only send an email after the last attempt, and it will include all error messages in the email body. If the post doesn't have the meta, it means it failed on a regular send attempt, so the email will get sent. We could consider implementing the retry system for regular sends too, but I think that's out of scope for this PR.

It might be a good idea to add some exponential backoff. If the first attempt failed because of a server hiccup, it would be fine to try again sooner, like 1 minute, then exponentially try on 2, 4, and 8 minutes.

bbb656b implements the exponential backoff and also bumps the max retries up to 10, because why not?

Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

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

Awesome! It's working like a charm now. Thank you for the revisions.

@miguelpeixe
Copy link
Member

miguelpeixe commented Sep 18, 2024

Whoops, seems like something is up with the email notification.

When sending regularly, the error message includes the link to edit the newsletter. When scheduled, the link is missing:

image

That's because get_edit_post_link() returns null if current_user_can( 'edit_post', $post->ID ) is false, which will be the case in a cron/scheduled action. Since there's also a small bug, unrelated to this PR, in which the link is currently broken because it's formatted with & instead of & due to the default $context second argument, I think it's ok to switch to admin_url( 'post.php?post=' . $post_id . '&action=edit' );. This will fix the existing bug and allow the scheduled action to retrieve the URL.

@dkoo
Copy link
Contributor Author

dkoo commented Sep 18, 2024

@miguelpeixe ad2246c makes the suggested change and seems to work well for me.

@dkoo dkoo merged commit e0cca64 into release Sep 18, 2024
9 checks passed
@dkoo dkoo deleted the fix/retry-failed-scheduled-sends branch September 18, 2024 16:47
matticbot pushed a commit that referenced this pull request Sep 18, 2024
## [3.1.6](v3.1.5...v3.1.6) (2024-09-18)

### Bug Fixes

* automatically retry failed scheduled sends ([#1660](#1660)) ([e0cca64](e0cca64))
@matticbot
Copy link
Contributor

🎉 This PR is included in version 3.1.6 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

matticbot pushed a commit that referenced this pull request Sep 20, 2024
# [3.2.0-alpha.4](v3.2.0-alpha.3...v3.2.0-alpha.4) (2024-09-20)

### Bug Fixes

* a typo that came after a refactor ([#1656](#1656)) ([0ae5b61](0ae5b61))
* automatically retry failed scheduled sends ([#1660](#1660)) ([e0cca64](e0cca64))
* **cc:** urlencode query args before passing to CC API ([#1659](#1659)) ([4f645d2](4f645d2))
* dont send metadata on subscribe ([#1648](#1648)) ([5b4a059](5b4a059))
* handle encoded URLs and block patterns in redirect check ([#1635](#1635)) ([2fa5d16](2fa5d16))
* **mailchimp:** allow contacts to resubscribe after unsubscribing ([#1654](#1654)) ([b76dbc2](b76dbc2))

### Features

* **mailchimp:** parse contact name into merge fields ([#1628](#1628)) ([aeba65a](aeba65a))
@matticbot
Copy link
Contributor

🎉 This PR is included in version 3.2.0-alpha.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Successfully merging this pull request may close these issues.

3 participants