CAD for Woodworking: Learning Both

Table of Contents

Over the years, I’ve done the odd woodworking project, like building a guitar speaker cabinet or putting some shelves on the wall. I’ve used wood glue before, and I have (had) an ever-growing collection of all kinds of wood screws in my toolbox, mostly leftovers from previous small wood-related tasks. Up until a few months ago, I didn’t have any woodworking power tools, such as a circular saw or a router. I hope that suffices to describe my woodworking skill level, which I would summarize as: Can glue and screw wooden things together that other people have cut to size.

One issue that bothered me with all these previous simple projects was the imperfection resulting from poor measuring and marking, as well as poor planning. I would sometimes work out the dimensions using only a folding rule (“Zollstock”), a pencil, and paper. Other times, I would use a vector drawing tool on my computer. You can find examples of some of my previous projects in these blog posts:

Obviously, I successfully built these things, but I was rarely satisfied with the results. I had to accept the imperfections and sometimes force parts into place to make them fit where they were supposed to go.

Getting inspired by Youtube

A couple of months ago, I wanted to upgrade my IKEA standing desk to something sturdier and with built-in presets. Since I got a nice tabletop for free from a nearby office that was closing down, I only needed a frame. Once the frame arrived, I had to figure out where to drill the holes in the tabletop to mount it properly. For that, I started looking for tutorials on how to drill straight holes into wood, which led me to a German YouTube channel by Jonas Winkler and down the woodworking rabbit hole.

The creator of the YouTube channel is neither your typical nor your traditional carpenter, and he is certainly not your typical German YouTuber. The way he looks and talks is unusual, but for me, that is a welcome contrast to the otherwise rather conventional and often boring German DIY content. On top of that, his humor made me chuckle many times, which is definitely a plus.

To keep this part of the story short, let’s just say that I subsequently managed to drill the holes and mount the tabletop, and I’m now happily typing this blog post at my new desk. Success!

But of course, the YouTube algorithm kept feeding me more videos from this newly discovered channel every day. And … I did the research: I exported my YouTube watch history, processed and searched the data, and found that I watched the first video on 2026-04-14. By 2026-07-16, I had watched … 395 videos from this channel alone, ranging from gear reviews to practical tutorials. Add another 87 videos from the other German channel, Let’s Bastel, and you get an idea of what I mean by “draining” a YouTube channel.

What I got out of this was an understanding of which tools these guys used to realize their projects, as well as which techniques they repeatedly relied on to achieve certain results. For example, I learned how they glued wood together, how they used circular saws, why both preferred plunge saws over conventional handheld circular saws, which tools they used for measuring and marking, and what not to do—both for safety and to avoid wasting money.

After this ingress of new information I had my first project in mind to try out some of the things I’ve learned. I was thinking about doing it like I always do, with pencil and paper or a vector drawing tool … or maybe this would be the perfect time for doing it with a CAD tool?

CAD: First Contact

I knew that CAD tools could be useful for woodworking and that they offered benefits such as technical drawings, exploded views, parts lists, and cut lists, which could in turn be fed into cut-optimization software for plywood sheets.

Long before I chose programming as a profession, I worked as a graphic designer, so I was very familiar with all the usual tools of the trade, such as Photoshop, FreeHand, Illustrator, and so on. However, I had never seriously used any 3D software, and I had never touched a proper CAD program at all.

After a brief search for a beginner-friendly CAD tool, I ended up trying Shapr3D. While it worked well enough for my first project, a Sortimo shelf, I quickly became annoyed by the limitations of the free version. To be fair, Shapr3D is designed to work on an iPad, so the focus is clearly on usability and the user experience on touch devices. I’d say they’ve done a great job in that regard.

I had heard of Onshape before, mostly through Youtube again and the many creators using it like Tom Stanton, rctestflight, Peter Sripol and many more. I created an account and was immediately intimidated as it looked far more complex than Shaper3D but with the help of youtube tutorials and $LLM guidance I quickly recreated my “design”. Compared to Shaper3D, the free version of Onshape lets you import and export, create parts and cut lists and I was able to use one of the cut optimizer websites (Cutlist Evo) to figure out a reasonable sheet size for the plywood which I would order right away.

A week later, the shelf was complete, and it was the most fun I had ever had building something out of wood. I achieved a level of precision I had never managed before, with the largest deviation in the entire assembly being just 0.5 mm. It was bliss to be able to refer back to my technical drawings and their precise measurements whenever needed. It was clear that this was only the beginning.

Sidenote: Yes, I cut the wood in my kitchen with a plunge saw. With the vacuum cleaner attached, I had minimal saw dust to deal with.

The next project: Polychrome

A few weeks after my first successful conversion from CAD to reality, another project snuck up on me when my partner mentioned something she was working on: some kind of light installation for a festival with an interactive element. The interaction was facilitated by tiny radar sensors that were meant to hang 5 m above the ground from six arms, each 3 m long and angled upwards at precisely 15°. The team was unsure how to build this out of wood using simple tools, as the budget was somewhat limited. Additionally, it needed to be stable and weatherproof enough to withstand wind and rain. The installation would also eventually undergo some form of safety inspection.

Fortunately, I had a long train ride coming up, and during the first six-hour leg, I decided to load up Onshape and try to come up with a design that could work. I had already learned a bunch of things the last time around, but I still had to learn a whole lot more, especially how to keep the design and its parts flexible, since the final dimensions would have to be determined later based on the available wood.

Again, $LLM was extremely helpful in explaining Onshape’s features and how to use them. Without it, I wouldn’t have been able to complete the first draft in six hours. This time, I designed each part separately, added custom mate connectors, and created subassemblies before putting everything together in the final assembly.

Click this link to view the CAD model yourself without an account. With an account you can copy and modify it. Just to be clear, I have no affiliation with Onshape – I just really enjoy this tool.

7 Days to make it real

A few weeks later, it was time to meet up and shop for some wood. Fortunately, we found a round disc with the perfect proportions, originally intended as a tabletop. Suitable wood for the arms followed shortly afterwards.

The mast was going to be provided by the festival, although its exact dimensions were not yet known. At that point, we also still had no clear idea what kind of wood to use for the wedges. I had a bunch of tools, but nothing that could cut them easily. Luckily, the festival had a dedicated woodworking crew with a modest workshop setup, including a proper table saw and miter saw. We therefore postponed choosing the wood for the wedges until we had spoken to them. If they lacked the appropriate tools, we would have to construct the wedges from several thinner layers instead.

Two weeks before the actual event, I packed up most of my tools and hauled them to the site. I was relieved to learn that the festival’s woodworking crew would indeed be able to cut the wedges to my specifications, and they were delighted when I handed them a technical drawing containing all the relevant measurements.

Sadly, I was unable to stick around and watch them make the cuts, but I assume they used their table saw with some kind of jig. Since my plunge saw can only cut about 5 cm deep with the guide rail attached, cutting the wedges to specification would have been quite a project in itself.

Similarly, we were quite lucky to find a tabletop with almost exactly the same dimensions as the one I had used in the CAD design. Otherwise, I would either have made a hexagonal “crown” base plate from thick plywood or attempted to cut a perfect circle with my router.

Especially during the early phase of gathering materials and filling in the unknowns about where and how we would obtain certain parts, it was incredibly useful that I had put in the time in CAD to make all the parts connect at the correct pivot or center points. This allowed us to quickly adjust the dimensions of individual parts without having to reassemble the entire design. Here is a quick demo video showing exactly that:

After three days on site and a few trips to the local “Baumarkt” ie. the home improvement store, we had all the parts and tools needed for the actual assembly. The one thing still missing was a wood drill bit long enough to drill through the entire base–wedge–arm–wedge assembly, so I could be sure that the metal bolts would pass through all of it smoothly.

The bolts, nuts, washers, and fasteners were only bought once we were certain of the final dimensions. Although I had planned to use M8 bolts from the start, the exact bolt length and the types of washers and fasteners we would need remained undecided until quite late in the process.

The decision to add a top wedge also came quite late. Initially, I thought the bolts, nuts, and washers would work fine even without parallel surfaces to bear against. However, once we had the wedges and I tried clamping everything together, it became apparent how useful those parallel surfaces would be.

Clamped arm assembly with top and bottom wedge

I went back to the on-site wood workshop and asked for a few more wedges, which they cut that same day. Finally, everything was ready for the preassembly of the arms, wedges, and base plate. I had to mark and number each arm and then do my best to drill straight holes through the entire clamped-together assembly. Getting everything into place, measuring, aligning, and clamping it took up most of the time on this project, but it was worth it.

As you can see in the pictures, the mast was slightly crooked so we had to adjust one of the heavy duty braces to make it as straight as we could. We pre-assembled as much as we could the night before because our allotted time with the crane was limited. In the end the actual assembly went quite smooth. We first mounted the base plate with a center screw on top of the mast. Then we added the heavy duty braces which made the connection between mast and base plate very stable. Lastly we mounted the arms one by one and drilled a hole to get the cables down to the platform.

In the end, I was super happy that my initial idea worked in reality. I’m glad I decided to build it in CAD first, as this allowed me to experiment with the dimensions of the individual parts and obtain precise measurements for the actual build.

Last but not least, I’m very thankful for all the content on Jonas Winkler’s YouTube channel. I bought many of my new tools based on his videos, and almost every tool I brought to the site proved useful in one way or another. An additional thank-you goes to Michael Truppe and his YouTube channel, “Lets-Bastel” which provided further context, inspiration, and motivation.

I hope there will be a new challenge like this in the not too distant future! I had a blast!

Appendix A: The Gear List

As if this post wasn’t long enough already … but some of you will ask about the tools I have used so here is a list of everything I used for this project and the shelf project that worked well, that I would buy again, and that I can recommend. I have no affiliation with any of the mentioned brands and shops.

Measurements and Marking

Clamps

For clamps there are two major brands that you can basically get any clamp type from. I’ve started using Piher (Spain) clamps and I’ve been very happy with them. There is another reputable brand called Bessey (Germany) which I’d also recommend.

Power Tools & Accessories

As you can see, I am a Makita enjoyer. There are crappy Makita tools but for each category, at a certain price point you get very good quality and once you’ve decided on a battery eco system it is more convenient to stay there. Plus Makita is one of 3 or 4 independent power tool companies which i sympathize with (see this info graphic). That being said – other people may choose different eco systems and thats ok too.

All links are to German stores and websites. But you should be able to find them on english websites with the provided model names.

Hand Tools

Portable Work Bench

This one deserves its own section. All the tools are almost useless if you don’t have a proper work surface and since I don’t own a house with a proper workshop and I want to be able to do projects where mobility is a decisive factor, I was looking for a mobile work bench. At first I was considering something like the Metabo MWB 100 but it seemed too heavy and bulky for stowing it away in my apartment. Then I stumbled across the Bora Centipede and eventually bought it.

This is one of the best purchases ever. It is so easy to move around, stow away and to set up. It is so incredibly solid once assembled. I can’t say enough good things about this thing. Of all the things this would be maybe my top recommendation if you’re looking for a portable yet stable temporary work surface.

Appendix B: How to find the exact center of a circle

For this project it was necessary to have equal spacing between all 6 arms. The math is quite simple: 360º / 6 = 60º and there is a neat little trick to divide a circle into 6 equal parts by tracing the radius along the edge and then connecting all intersections with the circle like so:

At small scales this works remarkably well and the slight imperfections don’t really add up to much. The center looks like a perfect center point. However using this technique on a 60cm diameter disk with rounded edges led to somewhat catastrophic results when attempting to determine the true center of the disc. Basically I ended up with a rather large triangle instead of a center point. With this technique, measuring errors compound quickly.

The method that I’ve used to precisely find the middle is as follows:

  1. Draw any line from edge to edge in the circle
  2. Determine the middle of that new line
  3. Draw a line from that middle point at a 90º angle
  4. Do all of the above at least one more time
  5. The center is where those 90º center lines meet

Appendix C: The Installation in Action

(will be added after this weekend)

Improved ADS-B Toolchain

After my recent post I did not stop tinkering and exploring. To my own surprise it didn’t take long to come across significant improvements to my toolchain.

Someone contacted me on mastodon shortly after and said that a friend of his had also recently gotten into SDRs and ADS-B and decided to write his on demodulator called stream1090. While this is actively in development and still early stages, this part from the README resonated with me.

“Most implementations look for the so-called preamble (a sequence of pulses anounncing a message). Stream1090 skips this step and maintains directly a set of shift registers. Based on the CRC sum and other criteria, messages are being identified. The hope is that in high traffic situations, a higher overall message rate can be achieved compared to a preamble based approach.”

Being only a demodulator means that the emitted data needs further processing. Additionally, this tool does not directly talk to your SDR, which makes the resulting pipeline seem a bit more complex at first — but it’s not as bad as it looks.

In my last post the toolchain consisted of two tools

  • readsb (read from SDR, demodulate, decode, write stats)
  • tar1090 (read stats, render map)

With the new toolchain I’m streaming demodulated data into readsb. For this to work, the readsb command has two additional flags for specifying the port on which to stream data into and another port for some graphs (more on that later).

The full command looks like this now:

./readsb --net-only \
  --net-ri-port 30001 \
    --net-bo-port 30005 \
    --interactive \
    --lat 52.5200 \
    --lon 13.4050 \
    --write-json ~/src/adsb/json \
    --write-json-every 1 \
    --db-file "$HOME/src/readsb/aircraft.csv.gz"

stream1090

Now it’s time to stream some data into readsb. As with most of these tools, none of them consider macOS as a target platform but luckily for me, I could compile all of them successfully albeit with some tinkering. In this case compilation was pretty effortless and I just followed the instructions from the README.

After that I started readsb with the command above and then used this command to get the data from the SDR, pipe it into stream1090 and then into readsb via socat.

rtl_sdr -g 49.6 -f 1090000000 -s 2400000 - | ./build/stream1090 | socat -u - TCP4:localhost:30001

Improved rendering and graph setup

The rendering setup thus far isn’t bad. Essentially I just started a python web server for tar1090 to render a nice map. However, I wanted more longterm statistics of my experiments and searched for some kind of graphing solution.

Maybe you’ve already guessed it but of course there is a tool for it and the name is of course graph1090 (https://github.com/wiedehopf/graphs1090).

Compared to the other tools so far, this one is not as nice to install as it as a bunch of dependencies that I don’t want to install on my main workstation. Among them are classics like rrdtool, gnuplot and coreutils – with the first two having many more dependencies.

Docker / Apple Container / OCI solution

I was wondering if there might be a Docker image that I could use instead and if there wasn’t I’d rather make one myself to contain this zoo of dependencies.

Again luckily for me, there are (at least) two suitable docker images.

Both of these bundle a lot of ADS-B processing tools at the price of shoehorning all those config parameters of the various tools into the Docker domain.

I’ve started with the first image which I got running using Apple Containers

container run -d --name tar1090 \
  --publish 8080:80 \
  --env TZ=Europe/Berlin \
  --env BEASTHOST=10.0.0.80 \
  --env BEASTPORT=30005 \
  --env LAT=52.5200 \
  --env LONG=13.4050 \
  --volume ~/src/adsb/graphs:/var/lib/collectd \
  ghcr.io/sdr-enthusiasts/docker-tar1090:latest

With the the two previous commands already running I could then point my browser to localhost:8080 to see my familiar tar1090 map.

Screenshot of tar1090

Additionally I got a barely visible link to some stats which you can make permanent between machine / container restarts with the --volume flag.

Screenshot of ADS-B graphs

While the second image (ultrafeeder) looks even more complete and feature rich, I’ll stick with the first image for the time being.

Further enhancements

As you can see, my reception range has increased quite a bit. I noticed a slight improvement when switching to stream1090 and then I moved my little dipole telescope kit antenna as close to the window as possible.

Later this week I will receive a combined Low Noise Amplifier (LNA) and Filter specifically for ADS-B. I will add another post if that yields signficant reception improvements.

My minimal ADS-B Tracking Setup

For a while I was somewhat interested in playing around with a Software Defined Radio (SDR). Recently I bought myself an entry level SDR and started exploring.

One of the first things I was interested in was receiving and tracking ADS-B data. This is a specific radio transmission used by aircraft to broadcast positional data, among other things. This is what sites like flightradar24 are based on. Most information that is displayed there is based on ADS-B data received by probably thousands of individual receivers around the globe.

The SDR I bought was the ubiquitous RTL-SDR Blog v4 with a basic antenna kit which cost me 99€.

This is where I bought it: https://www.wimo.com/de/rtl-sdr-v4-kit

First steps

I am a total beginner when it comes to radio and specifically software defined radios apart from very general knowledge of how electro magnetic signals work. Therefore I had to do quite a lot of research before I received and decoded the first actual ADS-B message.

Question number one: What kind of antenna is needed to receive ADS-B?

The frequency that is being used can be easily looked up. It’s on 1090Mhz. But what is somewhat harder to find out is the optimal length and orientation of the antenna.

The kit which I bought contains a simple dipole antenna base with two different pairs of metal telescopic antennas. After a bit of googling I found out that there is a formula for calculating the optimal length of a dipole antenna for a given center frequency. Then I looked up a basic Dipole Calculator like this one: https://www.wireantennas.co.uk/dipole-calculator

I ended up with 13.086cm of total length for the dipole. Then I screwed on the two short telescopic antennas to the dipole base, connected everything and started SDR++ to have a look.

Unfortunately there wasn’t much to see but I also recognized the many options to configure your SDR in the software and that I barely knew what any of them meant or related to. So the search continued for “ads-b sdr settings” and the like. Eventually I found some youtube videos that had the crucial information:

ADS-B is vertically polarized which means that the antenna should be oriented vertically and switching SDR++ to “RAW” would prevent it from trying to “interpret” the signal since it’s digital in nature anyway.

(Bonus Tip: Also try both possible vertical orientations since one arm of the dipole is connected to ground / the shield and the other arm to the conductor – some people claim that the orientation can make a difference but I can’t neither confirm nor deny from my limited tests)

Unsure of whether this would work from within my apartment in the middle of the city, I started up SDR++, tuned it to 1090Mhz with autogain enabled and the antenna mounted to a photo tripod, I moved around to see if I could catch any signals. Eventually I found a spot where these patterns appeared.

SDR++ Screenshot for receiving ADS-B Data

Then I sat down the tripod and watched the signal come in.

How to decode and visualize the data?

Now that I was somewhat sure that I actually receive ADS-B data, I wondered how I could best decode and visualize it, ideally on my mac without much hassle. If you’re on Linux most of the steps are much simpler and sometimes even available as package.

I’ll spare you the details of my research which lasted several days and took me down various rabbit holes. At first I found a tool called dump1090 which you can even install via homebrew. There are many forks and permutations of this tool and long story short I’m currently using a toolchain which is very similar to websites like https://adsb.lol

The toolchain essentially consists of two tools

  • readsb (another fairly active dump1090 mutation)
  • tar1090 (the tool doing the visualization)

Building this on macOS was not super straight forward but with a little bit of LLM help decoding the compiler errors I got both to work.

For readsb I had to edit the Makefile and remove all mentions of Werror which didn’t work for some reason.

git clone https://github.com/wiedehopf/readsb.git
cd readsb
perl -pi -e 's/(^|\s)-Werror(\s|$)/ /g' Makefile
make RTLSDR=yes CPPFLAGS="-I/opt/homebrew/include" LDFLAGS="-L/opt/homebrew/lib"

That compiled successfully on my M4 mac running macOS 26.2 (Tahoe). You might need to install some dependencies via homebrew like librtlsdr. Check the project repo for the required packages.

After the compile worked, I could start readsb like this

mkdir -p ~/src/adsb/json

[~/src/readsb:dev*] ./readsb \
  --device-type rtlsdr \
  --lat 52.5200 --lon 13.4050 \
  --write-json ~/src/adsb/json \
  --write-json-every 1 \
  --db-file "$HOME/src/readsb/aircraft.csv.gz" \ (more on this flag in a bit)
  --net

Now you should see output in the terminal as well as in the json directory. All we need to do now is to render it with the tar1090 tool.

git clone https://github.com/wiedehopf/tar1090.git
cd tar1090
ln -s ~/src/adsb/json data 
python3 -m http.server 8080

This should allow you to open your browser and point it to localhost on port 8080. You should see something like this.

Screenshot of tar1090

This screenshot was made after leaving both tools running for a while and receiving a bunch of ADS-B data. The blue outline indicates from where I was able to receive data by correlating the messages from the aircrafts with their reported longitude and latitude.

Now the --db-file flag for the readsb tool allows you to match airplane registration codes with airplane models so if you want to see the airplane type you need to download the most recent db file from here:

https://github.com/wiedehopf/tar1090-db/raw/refs/heads/csv/aircraft.csv.gz

and set the --db-file flag accordingly.

Lastly you can edit the html/config.js file in the tar1090 folder and uncomment/set these 3 options:

// get flight route from routeApi service default setting (toggle via settings checkbox)
useRouteAPI = true;
// configure route display, possible values: iata, icao, city (can use multiple like this: iata+city)
routeDisplay = 'iata';
// which routeApi service to use
// routeApiUrl = "https://adsb.im/api/0/routeset";
routeApiUrl = "https://api.adsb.lol/api/0/routeset";
// routeApiUrl = ""; // to disable route API so it can't be enabled by a website visitor

Note that you might get API rate limited if you live in a busy area with many flights that need to be resolved for their route data.

But that is essentially it. You can see that from within my apartment I’m able to see a bunch of flights with no clear view to the horizon or the sky. Even some aircraft which were north of me. My apartment faces south and so is my antenna but I suspect the houses across the street might reflect some of the signals from the north back to me.

I also had the antenna out once and I could receive signals from A LOT further but for now I’m happy. I might try different antennas or maybe a filter + low noise amplifier specifically tuned for ADS-B but that is not needed to get going.

Put any questions or suggestions in the comments below. I hope this writeup saved some people with a similar beginner knowledge a lot of time.

Mobile Podcasting Setup 2025

I’ll be escaping the grey, dark, cold and wet Berlin for a month. Last year I did the same and I wanted to be able to record a Glitterbrains podcast , record guitar and actually take guitar lessons remotely.

Back then I brought my UA Apollo Twin with me so I could do some flexible audio routing including the fabled mix-minus (n-1) setup and also a Beyerdynamic headset.

Last year I’ve sold my Universal Audio interfaces and switched to an RME interface instead which is far superior when it comes to routing flexibility and software support.

So for this year’s workation, I needed a new mobile audio interface that allowed for flexible routing and so I bought the RME Babyface Pro FS. The investment hurt a little but it’s just so convenient to stay in the same eco system, carry over my TotalMix settings and rely on the great RME engineering. (I also think there is hardly a better audio interface for Podcasting / Streaming than the RME ones)

So without further ado, here is my mobile podcasting, guitar recording and guitar lesson setup:

Mobile Podcasting Equipment

It consists of:

(^ the above are affiliate links to Thomann)

I could definitely save some weight and space by using a headset again but there isn’t currently a model that I feel like investing money in. Therefore I went with a slightly more involved but also more flexible setup. Looking forward to try it out.

Tuning rspamd

For many years I’m running my own mailserver based on postfix and dovecot. To combat spam I’ve used spamassassin like everybody else back in the day but I was never quite satisfied with it. It came from a different era and as the spammers got more sophisticated and billions of people put poorly maintained and therefore hackable computers on the internet, our trusty old friend spamassassin wasn’t keeping up. 

Then in 2013 a new contender entered the scene, rspamd. I remember discovering it, probably a few moons after its initial release and feeling quite excited. It was not written in Perl but in C, promising much better performance and offering a ton of modern features to combat spam.

When I first tried it, its default config was almost enough to get rid of most of the spam that I was struggling to filter with spamassassin but again, over the years as the spammers got more sophisticated, more and more spam was reaching my inbox again which is why I spent a weekend recently to try and figure out what I can do to improve the situation.

The first thing that became obvious to me was that the configuration options and format of certain modules has changed and that certain modules were just not working or even enabled in the first place. 

But that was just the beginning of renovating my rspamd config. So here are a few suggestions for you if you have too much spam in your inbox. I will assume that you are familiar with common email and spamfilter related terms like greylisting and the principles behind it.

Check your config

Suggestion number one is pretty straight forward. Check your active configuration! You can do this by running 

rspamadm configdump

or

rspamadm configdump <module name>

Check if the modules and values are as you expect them to be. Rspamd has a hierarchical config overloading structure and if not fully understood it is easy to believe that what you’ve configured in the local.d folder is actually what is active but I’ve realized that a few of these did not work as expected due to the before mentioned changes in the configuration. 

Deal with the repetitive spam themes first

In my case, I’ve received a lot of similar looking spam. All german speakers probably have seen their fair share of spam mails with a subject like “Apotheke / Apo-theke / A-potheke”. There are many more “common” spam themes and topics and this is what I’ve tackled first because these categories of repetitive spam were very unlikely to produce false positives if I just blacklisted them. 

But if you’re unsure whether this is the right approach on a multiuser setup with varying interests then you can fall back to greylisting. To set this up you will need to edit local.d/multimap.conf and maybe take a look at the corresponding documentation: https://rspamd.com/doc/modules/multimap.html

I’d say this page is one of the most important pieces of documentation to leverage rspamd’s potential.

Subject Blocklist

The first thing in my multimap.conf file is the following block:

BAD_SUBJECT_BL {
  type = "header";
  header = "subject";
  regexp = true;
  map = "$LOCAL_CONFDIR/local.d/local_bl_subject_map.inc";
  description = "Blacklist for common spam subjects";
  score = 10; 
}

The content of that local_bl_subject_map.inc file is as follows:

/\bpsoriasis\b/i
/\bprostatitis\b/i
/\bderila\b/i
/\betf\b/i
/\bbitcoin\b/i
/\breich\b/i
/\bgeld\b/i
/\bki\b/i
/\baktien\b/i
/\bmakita\b/i
/\blotto|lottery\b/i
/\bmubi\b/i
/\bauto\b/i
/\bantihaftbeschichtung\b/i
/.*r[:.-]*?e[:.-]*?z[:.-]*?e[:.-]*?p[:.-]*?t[:.-]*?f[:.-]*?r[:.-]*?e[:.-]*?i/i
/\br[-_]?e[-_]?zept[-_]?frei\b/i
/zeptfrei/i
/\beinkommen\b/i
/\bnubuu\b/i
/\bnuubu\b/i
/\bentgiftungsprogramm\b/i
/\bgelenkschmerzen\b/i
/\bmädchen\b/i
/\bsprachübersetzer\b/i
/\bstabilisierung.+blutdrucks\b/i
/\bmüheloses.+reinigen\b/i
/\bpapillome\b/i
/\bküchenmesser\b/i
/\brendite\b/i
/\bgewichtsverlust\b/i
/\bpreissturz\b/i
/\bchance.+kostenlos\b/i
/\bhamorrhoiden\b/i
/\bhörvermögens\b/i
/\bmuama\b/i
/\bryoko\b/i
/\bbambusseide\b/i
/\bluxusseide\b/i
/\bHondrostrong\b/i
/\btabletten.+apotheke\b/i
/\bEinlegesohlen\b/i
/\bEinlegesohlen\b/i
/\btest\syour\siq\snow\b/i
/\bzukunft.+sauberkeit\b/i
/\bcbd\b/i
/\bharninkontinenz\b/i
/\bpillen\b/i
/\btabletten\b/i

This might seem surprisingly short but this list got rid of the majority of spam mails reaching my inbox. It’s dull, it’s simple but quite effective. Very rarely I have to add things to it these days and it especially effective for those mails that don’t have a lot of suspicious content and fail other spam identification methods.

Again, if you’re uncomfortable to use it as a block / blacklist you can either lower the associated score to be below your global spam threshold or you can convert this map into a prefilter and send the matching mails into greylisting which also gets rid of 95-99% of spam mails.

TLD Blocklist

Speaking of prefilters and greylisting, let’s talk about my most crude blocklist where I apply special treatment on mails coming from certain top level domains. Here is the corresponding entry in local.d/multimap.conf:

SENDER_TLD_FROM {
  type = "from";
  filter = 'email:domain:tld';
  prefilter = true;
  map = "$LOCAL_CONFDIR/local.d/local_bl_tld_from.map.inc";
  regexp = true;
  description = "Local tld from blacklist";
  action = "greylist";
}

And here is the list of “blocked” top level domains:

[.]tr$
[.]su$
[.]mom$
[.]mg$
[.]com\.py$
[.]af$
[.]ng$
[.]ro$
[.]ar$
[.]pro$

For whatever reason, a disproportionate amount of spam mails is coming from those top level domains. Equally for me personally, there is very little chance of false positives but since this is even cruder than the subject based blocking, I changed this to a prefilter which means that this is evaluated before all other checks. I’ve set the action to greylist which basically sends matching mails directly into greylisting and that does the job very well. In case a “good” mail is coming from those top level domains, it should make it through the greylisting and all other modules.

Other Blocklists

I do have a few more blocklists for display names, domains and names (the part of an email address before the @) but they are quite short. For example I get a lot of spam mails from email addresses starting with “firewall@” so again I take care of those. 

The multimap blocks for those look like this: 

SENDER_FROM {
  type = "header";
  header = "from";
  filter = 'email:domain';
  map = "$LOCAL_CONFDIR/local.d/local_bl_from.map.inc";
  description = "Local from blacklist";
  score = 7;
}

SENDER_USER_FROM {
  type = "header";
  header = "from";
  filter = 'email:user';
  map = "$LOCAL_CONFDIR/local.d/local_bl_user_from.map.inc";
  description = "Local user from blacklist";
  score = 7;
}

SENDER_USER_DISPLAY_FROM {
  type = "header";
  header = "from";
  filter = 'email:name';
  map = "$LOCAL_CONFDIR/local.d/local_bl_from_display.map.inc";
  description = "Local user from display name blacklist";
  regexp = true;
  score = 7;
}

As mentioned before, this takes care of a very large portion of spam that wasn’t detected otherwise but is my no means the only thing you can tune. 

Tuning Symbol Scores

While looking at the history tab of rspamd’s web interface, I noticed certain symbols being added to emails which didn’t have enough weight to get the score over the threshold which I thought should be weighted higher. You can also manually paste the mail source into the form field in the “Scan/Learn” tab of the web interface to scan spam mails that have slipped through the filter to see what score the mail gets and what symbols where added. If you spot certain symbols over and over again and feel like they should be weighted more in the overall score, then head over to the Symbols tab and add custom scores to them.

There are so many symbols that I don’t remember which ones I have changed because I have used the web interface. I should’ve done that in a config file right away but too late now. You can be smarter than me and add a file local.d/scores.conf and add symbols and your custom scores as follows:

ONCE_RECEIVED = 5.0; 
MANY_INVISIBLE_PARTS = 5.0;

etc etc. 

Check/Configure the Fuzzy and Neural Modules

These modules are a cornerstone of rspamd’s effectiveness and therefore it’s worthwhile to check if they are indeed enabled and working. To do this run 

rspamadm configdump neural
rspamadm configdump fuzzy_check

For recommended values check out the module documentation of both. 

Ask the Mail Cow

Another great tip for getting more inspiration on how to fight spam with rspamd is to look into the repository of mailcow, which is a dockerized and pre-configured mail server setup and many of their configuration choices are proven to be solid. 

For example you can take a look at the entire local.d folder and get inspiration, e.g. for tuning the fuzzy module. Also for your postfix and dovecot configs you could get useful settings that might have not occurred to you. What I did was to look at their configs and when I saw options that sounded interesting and which I didn’t know, I looked them up in the postfix/dovecot/rspamd documentation to see if they’d be suitable for me as well.

I wouldn’t blindly copy all their settings because many might not apply to your scenario and without understanding what they do, you can make your setup worse or break it entirely. Don’t change too many things at once. Do one change at a time, test and confirm that they are working as intended. Use rspamd’s web interface to scan and check mails and to feed the fuzzy and neural modules.

Auto Learn From Users Spam

This is another great option for training your spam filter. There are ways to auto scan junk boxes and auto feed them to the rspamd but I am not using this as all the previous methods already work well enough for me. Spam mails are usually quite distinguishable from “proper” mail with all the previous methods mentioned – but if you have a medium to large multiuser setup with a diverse user base (region, language, age) you might be receiving very diverse spam and auto learning from user classified spam might bring the last few percent. 

You could even implement it in a way like gmail, by flaggin mail in user mail boxes after delivery, when enough users have marked it the same mail as spam. However there is a lot more effort required when you want to preserve data privacy which means a bit of scripting – but it is possible.

I hope that helps some of you to drastically reduce your spam. It did for me and I was surprised that some of the dullest methods were the most effective ones.

Questions?

I’m sure I haven’t answered all your questions and it’s not easy to cover everything. The rspamd config documentation isn’t easy to consume and to understand in its entirety and I wouldn’t claim I’ve reached the pinnacle of understanding but what I’ve done is enough so that I don’t get a single spam email into my inbox for days in a row. Whenever one slips through the cracks, I adjust one of the modules mentioned above.

Feel free to ask if you have any remaining questions in the comments or via the usual channels and let me know what things you have tuned to great effect. Sharing is caring 🙂

Oh and of course feel free to correct any errors I might have made!

Special thanks to @leah@chaos.social who saved my sanity during my config debugging session where I tried to figure out which modules are actually active and working.