noticed icon indicating copy to clipboard operation
noticed copied to clipboard

Batch Deliveries

Open excid3 opened this issue 5 years ago • 11 comments

From @SirRawlins

Batching is something I'd love to see.

If we have a notification to deliver to several thousand recipients, then some mail services such a Postmark/Sendgrid/Mailgun allow you to batch send 1,000 messages at a time, converting 000s of API requests into a single call.

Being able to defineDeliveryMethod classes that were instantiated and enqueued with a collection of recipients, instead of just a single recipient would be nice.

Something like this pseudocode is what I imagine:

class DeliveryMethods::Postmark < Noticed::DeliveryMethods::Base

    # Define batch size on the delivery method class.
    deliver_in_batches_of 1_000

    def deliver
        post "https://api.postmarkapp.com/email/send", {
            # Has access to 1_000 recipients, instead of single recipient.
            to: recipients.map(&:email),
            subject: notification.subject
        }
    end

end

class GroupNotification < Noticed::Base

    deliveryby :database
    deliver_by :twilio
    deliver_by :postmark

end

User.all.count
# => 5000

GroupNotification.deliver_later(User.all)
# => Enqueues DeliveryMethods::Database.deliver 5,000 times.
# => Enqueues DeliveryMethods::Twilio.deliver 5,000 times.
# => Enqueues DeliveryMethods::Postmark.deliver 5 times.

This would require some refactoring of the delivery, deliver_later and run_delivery methods in Noticed::Base to batch and loop recipients if the DeliveryMethod supported it.

Not sure if the gains in efficiency and scalability would make it worthwhile though?

Originally posted by @SirRawlins in https://github.com/excid3/noticed/issues/83#issuecomment-774141149

excid3 avatar Feb 05 '21 16:02 excid3

I think this would make more sense to be a bit more like:

delivery_by :postmark # Sends individual emails
delivery_by :postmark, bulk: true # Sends them in bulk
class DeliveryMethods::Postmark
  def deliver 
  end

  def bulk_deliver
    recipients.each_slice(1000) do |group|
      # send group
    end
  end
end

That way you could still use Postmark to delivery individually or in bulk.

The question I have is...how does Postmark know when to actually send in bulk? It'd need to queue up the recipients somehow know when it was ready to run.

excid3 avatar Feb 05 '21 16:02 excid3

@excid3 yeah that's nice, I like the idea of being able to support both single and batch delivery in a single delivery method. 👍

One consideration is resiliency to failure. By putting the each_slice inside the bulk_deliver method, if one of the API calls was to fail, due to network issues or something, it would break the subsequent slices of the batch, and also make it very difficult to retry.

If the each_slice was to happen before enqueueing, then each slice would be independent of one another and could be caught with ActiveJobs retry_on in the base job class and retried.

As for your question, Postmark, Sendgrid, Mailgun and just about every other mail provider I've come across deal with bulk sending via a single POST request, with up to 1,000 email addresses in the payload, they'll then split that list, queue and deliver everything once it's on their server.

Does that make sense?

SirRawlins avatar Feb 05 '21 17:02 SirRawlins

If the each_slice was to happen before enqueueing, then each slice would be independent of one another and could be caught with ActiveJobs retry_on in the base job class and retried.

Agreed. 👍

Maybe the option becomes something like this where you can specific the size of each slice.

delivery_by :postmark, bulk: { group_size: 1000 }

Then this would queue up X number of Postmark bulk delivery job (3 jobs if it had 3000 recipients).

In theory, this wouldn't change too much of the existing functionality, just add to it.

excid3 avatar Feb 05 '21 17:02 excid3

@excid3 how to improve the bulk insert with the database option using Rails 5.2? Trying to send 11K notifications without slow down the app

matteomelotti avatar Apr 06 '21 09:04 matteomelotti

@SirRawlins I added an implementation in https://github.com/excid3/noticed/pull/127 🙇. Let me know your thoughts!

manojmj92 avatar Jun 08 '21 13:06 manojmj92

@matteomelotti I saw this same issue when sending to a large number of recipients, the deliver_later method runs very slow while it adds all the jobs to the queue.

I pulled the call to deliver_later into its own background task, so it doesn't slow down the request for the user.

SirRawlins avatar Jul 12 '21 08:07 SirRawlins

@manojmj92 awesome, I'm sorry for the slow reply, I've not been working with Noticed over the past few weeks. Your PR looks good on the face of things but will leave it for @excid3 to review properly when he gets time.

SirRawlins avatar Jul 12 '21 08:07 SirRawlins

I'm leaving for a couple weeks on vacation, but I'll try and check this out when I get back.

excid3 avatar Jul 13 '21 00:07 excid3

@excid3 great. Have an awesome vacation, it's well deserved.

SirRawlins avatar Jul 13 '21 06:07 SirRawlins

@excid3 Any updates on the review? 😄 Would love to use noticed in a project of mine, https://github.com/excid3/noticed/pull/127 will really help.

mishaelajay avatar Oct 05 '21 12:10 mishaelajay

Any update?

francesco-loreti avatar Feb 17 '22 11:02 francesco-loreti

I would love to see this, as I'm implements Expo notifications, which supports batch delivery. Just one caveat, that I haven't seen considered - it might be useful to consider batching by i18n language, so you can render the notification by user language. Possibly something to add to a future documentation of #127 ?

sergioisidoro avatar Mar 30 '23 12:03 sergioisidoro

@excid3 Could you review this? Seems like an essential feature!

madhums avatar Jun 20 '23 12:06 madhums