Skip to content

Commit

Permalink
Added Templated email sending. Closes #60
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Nov 30, 2024
1 parent 35a26d5 commit 0c5c74b
Show file tree
Hide file tree
Showing 33 changed files with 715 additions and 166 deletions.
28 changes: 21 additions & 7 deletions docs/design-principles/0100-email-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ Many processes in the backend of a SaaS product aim to notify/alert the end user

Sending emails/SMS is often done via 3rd party systems like SendGrid, Twilio, Mailgun, Postmark, etc., over HTTP.

> Due to its nature, doing this directly in a use case, isn't very reliable, nor is it optimal with systems under load, and with 3rd parties applying rate limits. All of these aspects can lead to a poor user experience is the user is waiting for these workloads to complete before they can move on with their work.
> Sending emails/SMS directly in a use case, can be very unreliable, and is it optimal with systems under load, where 3rd party API availability may not be 100%, and they can also apply rate limits. These aspects can lead to a poor user experience is the user is waiting for these workloads to complete before they can move on with their work.
Messaging, especially email/SMS is inherently "asynchronous" (from the users' point of view) because it is always out of band (OOB) from the application sending the messages. Thus:

* We need to "broker" between the sending of emails/SMS, and the delivering of them, to make the entire process more reliable, and we need to provide observability when things go wrong.
* Since an inbound API request to any API backend can yield of the order ~10 emails per API call, delivering them reliably across HTTP can require minutes of time, if you consider the possibility of retries and back-offs, etc. We simply could not afford to keep API clients blocked and waiting while email delivery takes place, let alone the risk of timing out their connection to the inbound API call in the first place.
* Delivery of some emails is critical to the functioning of the product, and this data cannot be lost. Consider an email to confirm the email of a registered account, for example.
* Since an inbound API request to any API backend can yield of the order ~10 emails per API call, delivering them all reliably across HTTP can require many seconds/minutes of time, if you consider the possibility of retries and back-offs, etc. We simply could not afford to keep API clients blocked and waiting while email delivery takes place, let alone the risk of timing out their connection to the inbound API call in the first place.
* Delivery of some emails is critical to the functioning of the product, and this data cannot be lost, in a failure. e.g. Consider the email sent to a user to confirm the registration of their new account, or the SMS used to provide two-factor authentication to gain access to your account. These messages must be delivered, albeit in a reasonably short period of time.

Fortunately, an individual email arriving in a person's inbox is not a time-critical and synchronous usability function to begin with.
Fortunately, an individual email arriving in a person's inbox, or SMS text message arriving on your phone, is not a time-critical and synchronous usability function to begin with.

> Some delay, in the order of seconds to minutes, is anticipated.
> Some delay, in the order of seconds to minutes, is anticipated by the user, and is common even today.
Thus, we need to take advantage of all these facts and engineer a reliable mechanism.
Thus, we need to take advantage of all these facts and engineer a reliable and usable mechanism.

## Implementation

This is how emails and SMS messages are delivered from subdomain, using the Ancillary API:

![Email/SMS Delivery](../../docs/images/Email-Delivery.png)

> Although, not shown in this diagram (explicitly), SMS text messages have the same mechanisms.
### Sending notifications

Any API Host (any subdomain) may want to send notifications to users.
Any API Host (any deployed subdomain) may want to send notifications to users.

They do this by calling very specific and custom `IUserNotificationsService.NotifyXXXAsync()` methods.

Expand Down Expand Up @@ -92,6 +96,16 @@ The specific adapter will likely use an exponential back-off retry policy, which

Any exception that is raised from this processing will fail the API call, and that will start another delivery cycle from the queue.

#### Templating

In some products, you might want to control the HTML body of emails being sent.

> In some products you may also want to allow your customers to control their emails.
Most 3rd party provides support email templating in some capacity or another.

Emails can be sent either as hardcoded HMTL, or by specifying a collection of "substitutions" that are rendered by the templating engine of the 3rd party provider (e.g., MailGun, Twilio, etc).

### Delivery status

Even though the attempt to send the email message to the 3rd party service succeeds, that 3rd party service may, later, fail to deliver the email message, even though they respond to the delivery request with success.
Expand Down
3 changes: 3 additions & 0 deletions iac/AzureSQLServer-Seed-Eventing-Generic.sql
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ CREATE TABLE [dbo].[EmailDelivery]
[IsDeleted] [bit] NULL,
[Attempts] [nvarchar](max) NULL,
[Body] [nvarchar](max) NULL,
[ContentType] [nvarchar](max) NULL,
[Delivered] [datetime] NULL,
[DeliveryFailed] [datetime] NULL,
[DeliveryFailedReason] [nvarchar](max) NULL,
Expand All @@ -276,7 +277,9 @@ CREATE TABLE [dbo].[EmailDelivery]
[SendFailed] [datetime] NULL,
[Sent] [datetime] NULL,
[Subject] [nvarchar](max) NULL,
[Substitutions] [nvarchar](max) NULL,
[Tags] [nvarchar](max) NULL,
[TemplateId] [nvarchar](max) NULL,
[ToDisplayName] [nvarchar](max) NULL,
[ToEmailAddress] [nvarchar](max) NULL,
) ON [PRIMARY]
Expand Down
Loading

0 comments on commit 0c5c74b

Please sign in to comment.