vendure-ecommerce / vendure

The commerce platform with customization in its DNA.
https://www.vendure.io
Other
5.67k stars 1.01k forks source link

Adding a callback to run after an email is sent #1970

Open koljam opened 1 year ago

koljam commented 1 year ago

Is your feature request related to a problem? Please describe. I want to run some logic after an email is sent. In my case I want to update a fulfillment from Pending to Delivered, because the sending of the email was the delivery (digital product), so it makes sense to directly hook it to this event. This feature could also be used to add a note after critical emails have been sent or to inform the admin that the sending of critical mails have failed.

Describe the solution you'd like

const quoteRequestedHandler = new EmailEventListener('quote-requested')
  .on(QuoteRequestedEvent)
  .setRecipient(event => event.customer.emailAddress)
  .setSubject(`Here's the quote you requested`)
  .setTemplateVars(event => ({ details: event.details }))
  .onSent((event,senderResult)=>{
    /*Check senderResult to determine what to do next*/
  });

The challenge could be that we need to pass the original event as well as the senderResult. I think there are 3 steps to this:

  1. Add a way to add a callback here, for example:
    onSent(onSentFn[...types and stuff...]) {
        this.onSentFn = onSentFn;
        return this;
    }
  2. Make the jobQueue return the return value of the EmailSender, which in turn needs to return the return value of the mail-sending API
  3. if (result.onSentFn) result.onSentFn(event, senderResult) here

Describe alternatives you've considered Adding an event to fire when an email is sent. I don't know exactly how events are triggered but I assume it would be difficult to know what customer/order/fulfillment this email is supposed to be connected to. Plus I think the implementation above is more intuitive.

Additional context I would try a PR myself but I am not comfortable with the jobQueue and TypeScript in general yet 😬

ThomasBurleson commented 1 year ago

Function callbacks are often limiting as we may actually need multiple callbacks: notify(), error(), complete(). RxJS introduced the idea of a Subscriber: an object that supports any or all of these ^ callbacks.

You would used subscribe() instead of onSent().

// Overloaded methods
function subscribe(subscriber: Subscriber):void;
function subscribe(
    notify: (phase, data)=>void, 
    error?: (phase, error) => void, 
    complete?:() => void
): Unsubscribe;

This would then improve the code quality:

const confirmSend = <T>(event: Phase,senderResult:T )=>{ };   // Check senderResult to determine what to do next
const extractDetails = event => ({ details: event.details });
const extractEmail = event => event.customer.emailAddress;

const subscription = new EmailEventListener('quote-requested')
  .on(QuoteRequestedEvent)
  .setRecipient(extractEmail)
  .setTemplateVars(extractDetails)
  .setSubject(`Here's the quote you requested`)
  .subscribe(confirmSend);

// Disconnect all callbacks
subscription.unsubscribe();

Note: since subscribe() returns a subscription, it should be the last function in the 'chain'.

ThomasBurleson commented 1 year ago

The goal ^ is to have an architecture solution for all processes and events.

michaelbromley commented 7 months ago

In v2.2.0 the event-based approach has been implemented: https://github.com/vendure-ecommerce/vendure/commit/e4175e742e8431a20b4e60be61fe6c9a27efc982

I'll leave this issue open however since we could still add the more intuitive API as described in the original issue.