<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Jake’s Blog</title><description>Ramblings, mostly about software</description><link>https://www.jjbt.dev/</link><language>en-gb</language><item><title>But what is strict positivity anyway?</title><link>https://www.jjbt.dev//blog/2026/positivity/</link><guid isPermaLink="true">https://www.jjbt.dev//blog/2026/positivity/</guid><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded/></item><item><title>Memory Management</title><link>https://www.jjbt.dev//blog/2026/memory/</link><guid isPermaLink="true">https://www.jjbt.dev//blog/2026/memory/</guid><pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Computers need to store data while they are performing computations.
This is (hopefully) not a revolutionary concept to you.
However, in my experience very few people really take the time to learn how this works exactly.
Most programmers have at least heard of concepts like the stack and the heap.
But a few months ago I had a conversation with one of my friends (who I consider to be a very good programmer),
and he revealed that he didn’t really understand what we needed the heap for.&lt;/p&gt;
&lt;p&gt;So, I’m aiming today to explain what the stack and the heap are, why we need them, and perhaps even discuss some of the more advanced ideas in memory management that have come out since they first became popular that can tackle the same problems more gracefully.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;unstructured-memory&quot;&gt;Unstructured Memory&lt;/h3&gt;
&lt;p&gt;Before we had high level languages like &lt;code&gt;C&lt;/code&gt;, we had to write code in bare assembly. This was quite ordeal, I understand.
I, like many other others, got a tiny view of it in my degree, where we were required to write a little assembly to help solidify my understanding of how computers work at the lowest level.
When you are writing assembly, there is no notion of an organised memory structure - all that exists is just empty space, free for you to use.&lt;/p&gt;
&lt;p&gt;This is probably worse than it sounds. For example, function calls are just &lt;code&gt;goto&lt;/code&gt;s with a hard-coded address;
If you’re lucky, you get something like &lt;code&gt;JRA&lt;/code&gt; (Jump relative address) or assembly-level jump labels,
and you wouldn’t need to re-write your whole program if something else was already using your preferred address space.
If you weren’t lucky, jumps took bare addresses.
Then wanted to move the function, or say, add a line to the program before that function’s definition,
then you would need to manually update the address, everywhere the function was called.
And in assembly, it’s up to you to enforce your calling convention.
There is not (in general) a distinction between program memory and data memory -
so you are also responsible for making sure you never accidentally overwrite some critical
part of your code with the data you’re processing.&lt;/p&gt;
&lt;p&gt;This requires quite a lot of discipline from the programmer -
more, it turns out, than even the most hardcore modern programmers are willing to exert.
Raw-dogging unstructured memory is inflexible and error-prone.
But it’s also painful because simple tasks become very difficult.
Consider for example implementing a linked list of unknown size;
how do you know where the best place to allocate the next node is?
How do we track if a space is free or if it’s currently in use?
Managing this with raw assembly would have been a truly monumental task.&lt;/p&gt;
&lt;p&gt;But some very clever people realised that if we could just impose some structure on memory,
and some corresponding structure on the programs, then we get rid of a lot of the tedium and
handle memory automatically. Enter the stack.&lt;/p&gt;
&lt;h1 id=&quot;the-stack&quot;&gt;The stack&lt;/h1&gt;
&lt;p&gt;Take a look at the following function:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.y0rd3.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.p1z7b.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame has-title&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;span class=&quot;title&quot;&gt;simple.c&lt;/span&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;c&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;main&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; b &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; a &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; b;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;int main() {  int a = 1;  int b = 2;  return a + b;}&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And lets imagine that we don’t have a nice optimising compiler that will just inline &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. How much space does it need to run?
There are two &lt;code&gt;int&lt;/code&gt;s, and integers are generally 4 bytes long; so we need 8 bytes of space.&lt;/p&gt;
&lt;p&gt;Now think about this: when do we need to keep the space for &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; around? The key observation is that neither of these values will ever be needed after the function ends - the references &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are only valid within the &lt;em&gt;scope&lt;/em&gt; of main. So we can allocate the space for them when the function gets called, and then deallocate them all together when the function ends. Great!&lt;/p&gt;
&lt;p&gt;This is the notion of a stack. For any given function, we calculate the amount of memory it needs to run &lt;em&gt;statically,&lt;/em&gt; ahead of time. Then whenever a function is called, we allocate that space in what’s called a stack frame. So long as the data is tied to the function, we can safely deallocate it when the function returns.&lt;/p&gt;
&lt;p&gt;This is called “the stack” because it resembles &lt;a href=&quot;https://en.wikipedia.org/wiki/Stack_(abstract_data_type)&quot;&gt;the data structure of the same name&lt;/a&gt;. We select some region of memory (usually the top end of the program data region, but it doesn’t really matter) to use as the stack. We then keep a pointer to the next free bit of memory in that space. Whenever we need memory for a function call, we just increment the stack pointer as required. When a function returns, we decrement it. Frames come off the stack in reverse order to how we added them - just like the data structure.&lt;/p&gt;
&lt;h3 id=&quot;problems&quot;&gt;Problems&lt;/h3&gt;
&lt;p&gt;There is of course, a problem with this. I laid out two assumptions necessary to get the stack to work - can you work out what they are?&lt;/p&gt;
&lt;p&gt;The first is the lifetime issue - what if our data is going to be used after the function ends?
One option is to just directly return the data - i.e. allocate space in the calling frame, and put the data there when the function ends.
This can work, but if the data is very large, we probably don’t want to be passing it around &lt;em&gt;by value&lt;/em&gt; like that.
We’d like a more efficient representation - we want to be able to pass it by reference.
This solution also doesn’t really handle ideas like function closures all too well, which should bind values in the scope they were created in.&lt;/p&gt;
&lt;p&gt;A bigger problem for us is that it also does not handle the second issue - sizing.
It’s not always possible to estimate the size of a data structure ahead of time.
For a simple example, consider a program that reads in a user-entered string.
How much space should we put aside for it?&lt;/p&gt;
&lt;p&gt;You could put an upper limit on the length of the string, but say that’s not possible
(and there are cases where it’s not - determining the size requirement of an arbitrary function in a turing complete language is undecidable).
The truth is that there isn’t a good, clean way to allocate space for this statically.&lt;/p&gt;
&lt;p&gt;These two issues - sizing uncertainty and lifetime uncertainty - that provide the motivation for the heap.&lt;/p&gt;
&lt;h2 id=&quot;the-heap&quot;&gt;The heap&lt;/h2&gt;
&lt;p&gt;Unlike the stack, the name here is coincidental - the runtime heap bears no relation to &lt;a href=&quot;https://en.wikipedia.org/wiki/Heap_(data_structure)&quot;&gt;the data structure of the same name&lt;/a&gt;.
The heap usually does have some structure, although it’s much more complicated - what structure it has precisely depends on the implementation.
Generally speaking, the heap is split up into &lt;em&gt;granules&lt;/em&gt;.
A granule is a small region of memory - the smallest amount we can allocate in one go.
Part of why they exist has to do with alignment issues - even if you allocate space for only a single bit, the computer can only do loads in register-sized blocks.
This is why ordering your struct properties in C correctly is so important - it influences the memory layout of the struct, which in turn directly impacts it’s memory usage.&lt;/p&gt;
&lt;p&gt;But returning to topic; we cut the heap up into granules which can be individually allocated.
Access to the heap is allowed through two functions: an allocator and a deallocator.
An allocator takes in a &lt;em&gt;size&lt;/em&gt; - i.e. the amount of memory we want to allocate - and returns a pointer to a newly allocated block of memory of (at least) that size.
Intuitively, you can understand this as allocating enough consecutive granules to fit the object.
If you want &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.4306em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; bits, then you need &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&quot;false&quot;&gt;⌈&lt;/mo&gt;&lt;mfrac&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mtext&gt;granule size&lt;/mtext&gt;&lt;/mfrac&gt;&lt;mo stretchy=&quot;false&quot;&gt;⌉&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\lceil \frac{n}{\text{granule size}} \rceil&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1.2311em;vertical-align:-0.4811em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;⌈&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.6954em;&quot;&gt;&lt;span style=&quot;top:-2.655em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;granule size&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.394em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.4811em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;⌉&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; granules, although things are more complicated in practise.&lt;/p&gt;
&lt;p&gt;Inside an allocator, we maintain (probably several) lists of unused memory, called free-lists.
These are often implemented as very simple linked lists - in any given granule, you write in the address of then next granule in the list, and keep a pointer to the head.
These free-lists have different granule sizes - although, usually the size of any given one will be a multiple of the smallest available size.
When we allocate space for an object, we take a granule from the smallest free-list which can fit it.
There’s also usually a large block of memory that hasn’t yet been segmented. This is used to allocate space for extremely large objects, which don’t fit in any of the free-lists, and so that we can dynamically increase the size of the other free-lists as required.
Why do it this way? The short answer is speed. Finding many consecutive granules might take a long time, so we don’t even try.
We want our allocators to be fast - so it’s important that we don’t have to work super hard to allocate an object.&lt;/p&gt;
&lt;p&gt;Deallocators take in pointers to &lt;em&gt;live&lt;/em&gt; (i.e. allocated, in-use) blocks of heap memory and mark them as unused - so that they can be recycled.
In practise, this just means adding the granule back to the free list.&lt;/p&gt;
&lt;p&gt;In a simple implementation, these functions are basically list.pop() and list.push() operations (with some fancy dressing).
That hopefully explains why things like double free and use after free errors are such a big deal.
If you free a memory location twice, it can end up in the &lt;em&gt;free list&lt;/em&gt; (as it’s called) twice, and therefore be allocated twice.
You can then end up with two different parts of the code accidentally accessing the same section of memory, assuming that they have sole ownership.
This can be disastrous, and cause all kinds of horrific memory corruption issues.
At the very best, your code will be wrong - but it will usually be a lot worse.&lt;/p&gt;
&lt;p&gt;Why expose ourselves to this potential issue, if it’s so dangerous?
Well, because &lt;code&gt;C&lt;/code&gt; programmers just won’t quit.
BUT ALSO, because the heap solves the uncertainty issues we just discussed!
&lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; make managing the memory of the program the problem of the programmer.
Through the magic of ‘make the user do it’ we solve the uncertainty problem;
So long as &lt;em&gt;we, the programmer&lt;/em&gt; can work out what the lifetime or size of an object should be,
We have no problem allocating and deallocating the memory for it.&lt;/p&gt;
&lt;p&gt;There are other ways of tackling the heap problem - lots, in fact.
It turns out that managing the memory yourself with malloc and free is a fantastic way
to shoot yourself in the foot. So a lot of development in PL theory and runtime systems has been
in finding ways to automate the management of heap memory.&lt;/p&gt;
&lt;h2 id=&quot;garbage-collection&quot;&gt;Garbage Collection&lt;/h2&gt;
&lt;p&gt;Another very popular memory management mechanism is garbage collection.
In a garbage collected language, free is never called. Instead, whenever the allocator is called,
it can choose to run the garbage collector (GC).
The garbage collector is responsible for figuring out what allocated memory is now dead, and safe to be reused.&lt;/p&gt;
&lt;p&gt;There is a whole plethora of different GC implementations, all of which have different advantages.
Indeed, there are really several different axes on which we can place GC systems, depending on how exactly they work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Precise or Conservative&lt;/li&gt;
&lt;li&gt;Compacting or Non-Compacting&lt;/li&gt;
&lt;li&gt;Tracing vs Reference Counting vs Generational vs Something else…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The simplest garbage collector we could come up with is probably reference counting GC.
In reference counting, with each block of memory, you associate a counter, which tracks how many references to a that block exists.
When that count hits 0, you can deallocate the block.&lt;/p&gt;
&lt;p&gt;The biggest problem that RC GC faces is reference cycles. Simply put: What if two dead blocks point at each other?
This is the motivation for &lt;em&gt;tracing&lt;/em&gt; GC.
The trace algorithm first looks through the stack - what is called the ‘root set’ - and finds pointers to live objects.
Everything else is then scanned in turn, looking for yet more pointers. This process continues until it there is no new pointers to follow.
So long as we ensure we only follow a given pointer once, we are guaranteed to terminate.
All the live memory trace visits is marked as live. The stuff that didn’t get marked as live can safely be considered dead - and can be allocated anew, or ‘swept’.
Thus, this kind of tracing GC is sometime called ‘mark-sweep’.&lt;/p&gt;
&lt;p&gt;I assumed in this explanation that we could always determine what counts as a pointer - this is called ‘precise’ GC. But in practise, that’s not always true.
In C for instance, &lt;code&gt;*void&lt;/code&gt; and &lt;code&gt;u_size&lt;/code&gt; integers look identical. In &lt;em&gt;conservative GC,&lt;/em&gt; we assume that anything that &lt;em&gt;could&lt;/em&gt; be a pointer &lt;em&gt;is&lt;/em&gt; a pointer.
Then you simply run trace on that.
This sounds a bit weird, but it has a very big advantage - you can implement a GC like this as a drop-in replacement for &lt;code&gt;C&lt;/code&gt;s malloc and free.
You can garbage collect a program with no change to the code required - just change how it gets linked.
A good example here is &lt;a href=&quot;https://www.hboehm.info/gc/&quot;&gt;BDWGC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is just scratching the surface of what GC and managed runtimes do -
for instance, compacting GC squishes objects together
to reduce the amount of awkward free space there is, and reduce fragmentation.
This can be very expensive, and doing it well is a really interesting problem to solve.&lt;/p&gt;
&lt;p&gt;There’s also been lots of work on how to statically analyse programs to allow the compiler to automatically insert
the proper free calls. Rust is probably the most famous example of this - it’s ownership system guarantees that safe free calls can be inserted automatically.
Work of this kind has been around for ages; &lt;a href=&quot;https://doi.org/10.1006/inco.1996.2613&quot;&gt;Tofte and Talpin’s paper on &lt;em&gt;region based memory management&lt;/em&gt;&lt;/a&gt; is perhaps the most famous.
Regions is to allow stack frames to grow in size at runtime.
Essentially, you associate with each a list of pointers, freed when the frame is popped.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;That about covers everything I wanted to say.
Hopefully you can walk away with this feeling confident about what the stack and heap really are,
and maybe also with a bit of an intuition for what malloc and free and doing.
At it’s core, memory management is really not that difficult to understand.
I also hope that I’ve convinced you that there is some really interesting problems in memory management.
Even today, people are still working on new techniques, and I think it’s one of the most interesting areas of research within PL.&lt;/p&gt;</content:encoded></item><item><title>Rate limiting the emails for our NextJS app with BullMQ</title><link>https://www.jjbt.dev//blog/2026/rate-limited-emails/</link><guid isPermaLink="true">https://www.jjbt.dev//blog/2026/rate-limited-emails/</guid><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I thought I’d write something quick about how I handled a problem that came up for us on &lt;a href=&quot;https://github.com/amps-kt/spa&quot;&gt;SPA&lt;/a&gt; recently. Here goes:&lt;/p&gt;
&lt;p&gt;One of the many tasks the app has to do is send email notifications. Its important because it helps maintain visibility - if you get an email, you have a record that something happened, even if the data on the sever changes later.&lt;/p&gt;
&lt;p&gt;But sometimes, we need to send a lot of emails - for instance, if we are emailing everyone who is late in submitting their marks. We ran into an issue when doing this - our SMTP provider rate limits us to something like 200 emails every 10 mins.&lt;/p&gt;
&lt;p&gt;The basic answer to this problem is a message queue. A message queue lets you “queue up” jobs and process them later. There are lots of mature solutions in this space, and that’s great, because there are lots of good ways to solve it. The downside is that figuring out which one is right for you can be quite tricky.&lt;/p&gt;
&lt;p&gt;I looked at two potential solutions for our use case. First is &lt;a href=&quot;https://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt;. RabbitMQ is a very popular industry standard message brokering system. Its very powerful and has lots of capabilities. But for us, it’s probably a bit overkill, seeing as we are trying to just handle a rate limit on some emails.&lt;/p&gt;
&lt;p&gt;Something better suited to our needs is &lt;a href=&quot;https://bullmq.io/&quot;&gt;BullMQ&lt;/a&gt;. BullMQ is node-native, and uses redis (or a compatible alternative like &lt;a href=&quot;https://valkey.io/&quot;&gt;valkey&lt;/a&gt;) as a backend. It’s got a very simple architecture based on queues and jobs. It’s essentially perfect for our use case - it what it was designed to do.&lt;/p&gt;
&lt;p&gt;Having settled on the tech to solve it, we then needed to work out how to integrate it with our existing code. The relevant parts here are NextJS and react-email (which we use to create pretty email templates).&lt;/p&gt;
&lt;h2 id=&quot;finally-the-actual-tutorial&quot;&gt;Finally! The actual tutorial&lt;/h2&gt;
&lt;p&gt;To begin with, let’s define some simple config values we will need later:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;link rel=&quot;stylesheet&quot; href=&quot;/_astro/ec.y0rd3.css&quot;&gt;&lt;script type=&quot;module&quot; src=&quot;/_astro/ec.p1z7b.js&quot;&gt;&lt;/script&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;EmailJob&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;cc&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;EMAIL_QUEUE_NAME&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;EMAIL_QUEUE&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export interface EmailJob {  text: string;  html: string;  subject: string;  to: string[];  cc?: string[];}export const EMAIL_QUEUE_NAME = &amp;#x22;EMAIL_QUEUE&amp;#x22;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;EMAIL_QUEUE_NAME&lt;/code&gt; should be quite self-explanatory. It’s the name of the queue we are adding jobs to. BullMQ lets you have have as many as you need. Each queue can have a payload type attached to it. When you push a job to the queue, you need to provide that data. The worker can then access that data and do something with it. &lt;code&gt;EmailJob&lt;/code&gt; is the type of the payload for our jobs. Note it has both a text and HTML field, so can specify both for the email.&lt;/p&gt;
&lt;p&gt;Next, we set up a way to add jobs to the queue, which looks something like this:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { env } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;@/env&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; IORedis &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;ioredis&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; ReactElement } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { render } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;@react-email/components&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { Queue } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;bullmq&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { EMAIL_QUEUE_NAME, &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; EmailJob } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;makeConnection&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;IORedis&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;host: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;REDIS_HOST&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;port: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;REDIS_PORT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;maxRetriesPerRequest: &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;makeQueue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;emailQueue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Queue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;EmailJob&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;EMAIL_QUEUE_NAME&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, { connection });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;queueEmail&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;cc&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;ReactElement&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;cc&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; emailQueue.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;send-mail&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;to,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;cc,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;subject,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;html: &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(message),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;text: &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(message, { plainText: &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; }),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; QueueEmail;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { env } from &amp;#x22;@/env&amp;#x22;;import IORedis from &amp;#x22;ioredis&amp;#x22;;import { type ReactElement } from &amp;#x22;react&amp;#x22;;import { render } from &amp;#x22;@react-email/components&amp;#x22;;import { Queue } from &amp;#x22;bullmq&amp;#x22;;import { EMAIL_QUEUE_NAME, type EmailJob } from &amp;#x22;./config&amp;#x22;;export const makeConnection = () =&gt;  new IORedis({    host: env.REDIS_HOST,    port: env.REDIS_PORT,    maxRetriesPerRequest: null,  });export const makeQueue = () =&gt; {  const emailQueue = new Queue&lt;EmailJob&gt;(EMAIL_QUEUE_NAME, { connection });  async function queueEmail({    message,    to,    subject,    cc,  }: {    message: ReactElement;    subject: string;    to: string[];    cc?: string[];  }) {    return await emailQueue.add(&amp;#x22;send-mail&amp;#x22;, {      to,      cc,      subject,      html: await render(message),      text: await render(message, { plainText: true }),    });  }  return QueueEmail;};&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is basically just a convenient wrapper around &lt;code&gt;emailQueue.add&lt;/code&gt;, though there are a few things to note. First, we don’t construct anything here at the top level. Instead, it’s all factories. I do it this way so that if we never use the queue, we never try to connect to redis. In the real app, we have an environment variable that lets you turn the queue on and off, and if we construct the redis connection without a valid host and port, it dumps a bunch of very annoying errors into the log. This way, we only attempt to connect if it’s necessary, and we can avoid the noise.&lt;/p&gt;
&lt;p&gt;Second, it renders the reactEmail templates before sending off the job. We do it this way around for few reasons; firstly, it avoids any potential problems we might run into serialising &lt;code&gt;ReactElement&lt;/code&gt;s in redis. Secondly, it helps keep our worker simple. The alternative might be sending all the data required to render the email in the payload, but that would make the payload type much uglier. Another advantage is that as it stands, this matches an existing interface of our app:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;SendMail&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; ({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;cc&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;ReactElement&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;cc&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;?:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;}) &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Promise&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;export type SendMail = ({  message,  to,  subject,  cc,}: {  message: ReactElement;  subject: string;  to: string[];  cc?: string[];}) =&gt; Promise&lt;void&gt;;&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is the underlying handler for sending mail. We have a class (called ‘Mailer’) that handles all the boilerplate related to emails, and it takes a &lt;code&gt;SendMail&lt;/code&gt; function in as an argument to its constructor. So using the new system is as simple as:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line highlight del&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fa96a1;--1:#952933&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c0a5f3;--1:#613aa9&quot;&gt;Mailer&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(sendMail,),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;Mailer&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#004fa9&quot;&gt;MAIL_USE_RATE_LIMIT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;===&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;ON&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#c2a8f3;--1:#613aa9&quot;&gt;makeQueue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;--0:#fb99a3;--1:#982934&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; sendMail,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line highlight ins&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;new Mailer(sendMail,),new Mailer(     env.MAIL_USE_RATE_LIMIT === &amp;#x22;ON&amp;#x22; ? makeQueue() : sendMail,)&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now we need a worker - something that can process these jobs. Unfortunately, this can’t just be attached to our NextJS server, and needs to live in a separate process. Next is designed to be able to work in serverless environments, where it spins up and down dynamically to handle requests. In such environments, there is no guarantee that Next is running at all at any given time - it just so happens that we want to run it locally in a container, and know that there will always be a live thread. A result of this is that Next does not have a mechanism for attaching long running side-car processes, so we need to roll our own.&lt;/p&gt;
&lt;p&gt;Fortunately, this isn’t particularly difficult. Lets start by actually writing the worker script:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;ts&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { env } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;@/env&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { Queue, Worker, &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; Job } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;bullmq&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; nodemailer &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;nodemailer&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { EMAIL_QUEUE_NAME, &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; EmailJob } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;./config&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { connection } &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;./redis-connection&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;emailQueue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Queue&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;EmailJob&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;EMAIL_QUEUE_NAME&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, { connection });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; emailQueue.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;setGlobalRateLimit&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_RATE_LIMIT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_RATE_LIMIT_PERIOD&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;transporter&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; nodemailer.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;createTransport&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;host: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_HOST&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;port: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_PORT&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;auth: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_PASSWORD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; { user: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_USER&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, pass: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_PASSWORD&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;      &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;undefined&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;{ from: { address: env.&lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;MAIL_USER&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, name: &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;SPA Support&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; } },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;mailWorker&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Worker&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#79B8FF;--1:#005CC5&quot;&gt;EMAIL_QUEUE_NAME&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;  &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--0:#FFAB70;--1:#AE4B07&quot;&gt;job&lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;Job&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;EmailJob&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;&gt;) &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; transporter.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;sendMail&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;(job.data),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span class=&quot;indent&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;{ connection },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;void&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; mailWorker.&lt;/span&gt;&lt;span style=&quot;--0:#B392F0;--1:#6F42C1&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;import { env } from &amp;#x22;@/env&amp;#x22;;import { Queue, Worker, type Job } from &amp;#x22;bullmq&amp;#x22;;import nodemailer from &amp;#x22;nodemailer&amp;#x22;;import { EMAIL_QUEUE_NAME, type EmailJob } from &amp;#x22;./config&amp;#x22;;import { connection } from &amp;#x22;./redis-connection&amp;#x22;;const emailQueue = new Queue&lt;EmailJob&gt;(EMAIL_QUEUE_NAME, { connection });void emailQueue.setGlobalRateLimit(  env.MAIL_RATE_LIMIT,  env.MAIL_RATE_LIMIT_PERIOD,);const transporter = nodemailer.createTransport(  {    host: env.MAIL_HOST,    port: env.MAIL_PORT,    auth: env.MAIL_PASSWORD      ? { user: env.MAIL_USER, pass: env.MAIL_PASSWORD }      : undefined,  },  { from: { address: env.MAIL_USER, name: &amp;#x22;SPA Support&amp;#x22; } },);const mailWorker = new Worker(  EMAIL_QUEUE_NAME,  async (job: Job&lt;EmailJob&gt;) =&gt; await transporter.sendMail(job.data),  { connection },);void mailWorker.run();&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Note that this is the point where we set the actual rate limit.&lt;/p&gt;
&lt;p&gt;As you can see, there really isn’t very much to it. It basically just grabs messages from the queue and sends them - very simple. The much more complicated part is setting this up to build. We decided to split it out to a separate container entirely. This helps keep things decoupled, so that if either the server or the worker dies, it doesn’t take the other one down with it.&lt;/p&gt;
&lt;p&gt;To get this working, you need a ts-to-js transpiler (we use &lt;a href=&quot;https://tsup.egoist.dev/&quot;&gt;tsup&lt;/a&gt;) and then a docker file. Ours looks something like this:&lt;/p&gt;
&lt;div class=&quot;expressive-code&quot;&gt;&lt;figure class=&quot;frame&quot;&gt;&lt;figcaption class=&quot;header&quot;&gt;&lt;/figcaption&gt;&lt;pre data-language=&quot;dockerfile&quot;&gt;&lt;code&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;# Builder:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; --platform=$BUILDPLATFORM node:22-alpine &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;WORKDIR&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; /app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; package.json .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; pnpm-lock.yaml .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; corepack enable pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; pnpm i --frozen-lockfile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; tsconfig.json .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; src src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; pnpm run build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#99A0A6;--1:#616972&quot;&gt;#  Runner:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; node:22-alpine &lt;/span&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; runner&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;WORKDIR&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; /app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; corepack enable pnpm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;ENV&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; NODE_ENV=production&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; addgroup --system --gid 1001 nodejs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; adduser --system --uid 1001 mailworker&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; --from=builder /app/node_modules/ ./node_modules&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; --from=builder /app/build .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; --from=builder /app/package.json .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;COPY&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; --from=builder /app/pnpm-lock.yaml  .&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; mailworker&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;ec-line&quot;&gt;&lt;div class=&quot;code&quot;&gt;&lt;span style=&quot;--0:#F97583;--1:#BF3441&quot;&gt;CMD&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;pnpm&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;run&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--0:#9ECBFF;--1:#032F62&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span style=&quot;--0:#E1E4E8;--1:#24292E&quot;&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;copy&quot;&gt;&lt;button title=&quot;Copy to clipboard&quot; data-copied=&quot;Copied!&quot; data-code=&quot;# Builder:FROM --platform=$BUILDPLATFORM node:22-alpine AS builderWORKDIR /appCOPY package.json .COPY pnpm-lock.yaml .RUN corepack enable pnpmRUN pnpm i --frozen-lockfileCOPY tsconfig.json .COPY src srcRUN pnpm run build#  Runner:FROM node:22-alpine AS runnerWORKDIR /appRUN corepack enable pnpmENV NODE_ENV=productionRUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 mailworkerCOPY --from=builder /app/node_modules/ ./node_modulesCOPY --from=builder /app/build .COPY --from=builder /app/package.json .COPY --from=builder /app/pnpm-lock.yaml  .USER mailworkerCMD [&amp;#x22;pnpm&amp;#x22;, &amp;#x22;run&amp;#x22;, &amp;#x22;start&amp;#x22;]&quot;&gt;&lt;div&gt;&lt;/div&gt;&lt;/button&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And that’s all you really need. Connecting this in to the rest of the services with docker compose is pretty standard, so I won’t go into detail here - maybe another time.&lt;/p&gt;
&lt;p&gt;In the future, we might look into using this system to implement digest emails - i.e. emails which collate a bunch of information from a timeframe and send them. That will probably be more involved, but for now this simple solution is good enough.&lt;/p&gt;
&lt;p&gt;The code for all of this can be found in the &lt;a href=&quot;https://github.com/amps-kt/spa&quot;&gt;SPA repo&lt;/a&gt;, and the &lt;a href=&quot;https://github.com/amps-kt/mail-worker&quot;&gt;mail-worker repo&lt;/a&gt;. Hope it was interesting!&lt;/p&gt;</content:encoded></item></channel></rss>