I still see from time to time, and even recently heard at a podcast, the advice of sending emails synchronously after certain relevant actions. I knew this old approach was widely used back in the phpBB and phpNuke days, where you couldn't do much asynchronous work in the web and had to do everything in the same process, thread and even same HTTP request. I thought that was already forgotten and clearly stated to be a bad practice. In my opinion it is only fine for your pet-projects, hackathon-based ideas, and under no circumstances should be done for production-like work, even if it is just a prototype.
Using an example of a new user registration flow, let's quickly see the ways of sending a welcome email:
a) Synchronous email sending after new user registered action. This is bad, as it makes the whole register take more time (in the order of seconds), it makes the registration logic more complex, etcetera. No matter if you do it with a function, with a "post register handler" or whatever you call it, if the execution is sequential and inside the same process, it is not good. The only good thing it does is that it provides all the required info to the mail sending logic without any need to query other services.
b) Enqueue asynchronous task to send email, including the new user id. This is good, as the user registration flow will finish quickly, before the email is sent. But could be improved, as it needs to pull user data from wherever it resides when actually executing the email sending task, so there will be extra calls inside your platform.
c) Enqueue asynchronous task to send email, including all required user data. If the "task queueing" message has all info it requires (user id, email, first and last name are probably enough), then there's no need for the mailing service/tasks processor/etc. to even know where the user data lives. No extra service calls, no delays or timeouts... as long as the queue message exists, it can be just consumed and deleted without any additional piece (if goes well, of course).
d) With event sourcing, pub/sub, or any similar event-based architecture, user registration would emit a
UserRegisteredEvent and, assuming it was registered as a subscriber, our mailing/notification service would pick it up and sent the corresponding email. This case can act like scenario b) if the notifications service needs to pull the data from somewhere else, but we could also send the relevant fields on the message so it becomes scenario c).
In the end, there's one requirement and one decision: you must defer execution of email sending, but you get to choose if you wish lightweight event messages with few data fields and extra communication steps between services, versus smarter messages containing the all fields you expect other services will need to act in response of, plus extra calls.
Oh, and if it is "critical", then setup a high-priority messaging queue for certain emails, mark the event as
critical=True so notifications service can properly move it to the top of the queue or process it immediately, or any other architectural change you can come around.
Just don't do a) 😉