---
title: "What We Learned Clearing 65 Expired Domains From the Backlog"
canonical: https://dxdev.com/blog/automated-domain-teardown-engine/
datePublished: 2026-06-24
---
There were **65 expired domains** sitting in the backlog when we shipped the teardown process on June 24. Clearing them was the visible part. The harder part was deciding exactly when the system should act, what could be allowed to fail, and which person should receive the message when it did.

A domain expiry is not one row changing state. In our system it touches Cloudflare, the relay, and the database. If we remove one piece and leave the other two alive, we create a future support problem that is harder to spot because it is only partly visible.

The first version of the idea was simple: find expired sites, remove their domain resources, notify the customer. The backlog forced the real questions. What counts as expired when a scheduled process runs around a date boundary? Does a mail problem stop the lifecycle monitor? And which contact field represents the person accountable for a site?

## Making teardown wait a day

Our first instinct was to make a site eligible for teardown the moment it crossed its expiry time. That condition looks tidy in a query, and it is wrong for the customer.

A domain can expire on a day when someone is still answering renewal notices, sorting out a card failure, or reading email in a different time zone. Tearing down the same day turns the expiry timestamp into a trapdoor.

We changed the rule to run **the day after** a site expires. The expiration flow warns the customer about the domain. The automated teardown handles the expired site on the following day. The delay gives the warning a real window to be seen, and it makes the selection rule easy to read when we inspect it later.

We looked at two alternatives. Same-day teardown lost because the notice and the destructive action would be effectively concurrent. A longer grace period lost because it would keep dead infrastructure alive with no policy reason in the actual expiry process. The next-day rule was specific enough to monitor and conservative enough not to surprise someone whose domain had just crossed the line.

There was no error message on this one. What we found was a gap between a technically valid condition and the behavior it would cause. "Expired at or before now" looked complete until we traced it through the email timing and the scheduler.

## Cloudflare cleanup cannot wait on a mailbox

The teardown has three concrete jobs: remove the Cloudflare configuration, remove the relay configuration, and update the database. Sending an email is not one of them.

The lifecycle monitor has one primary obligation: when a site is past the threshold, tear it down. If the mail provider has a transient failure, a bad recipient record, or any other delivery problem, that must not leave the domain active or stop the monitor from reaching the next expired site.

So we made notification **best effort**. The monitor completes the teardown across Cloudflare, the relay, and the database. Then it attempts to email the owner, and the mail outcome is recorded as part of the run instead of being a dependency that can invalidate the cleanup.

The other design was a single all-or-nothing transaction: no notification, no completed teardown. It had an appealing shape, but it puts the least controllable integration in charge of the thing we need to guarantee. A mail failure should be observable and repairable. It should not leave a domain active with a broken lifecycle state.

The test we apply to automation like this is to ask which failure is allowed to delay the system's central obligation. Here, failed cleanup has to be loud because resources may still be serving. Failed email has to be loud too, but it cannot reverse the cleanup decision or freeze the monitor.

## Picking the recipient

The last problem looked like field selection and turned out to be an ownership question.

The system had a primary contact and an owner. It would have been easy to notify the primary contact, since that field is often present and familiar. But the first contact record is not necessarily the person accountable for the site and its domain, and for a destructive lifecycle event familiarity is not enough.

The teardown email goes to the **owner**, the record that represents responsibility for the site. Billing, support, operations, and ownership can all exist on the same account. An automated action has to name the role it is addressing and then use the matching field. Picking the most convenient field is how a notification ends up with a recipient nobody was responsible for.

## The backlog as the first production run

Once the new path was in place, we cleared the 65 already-expired domains through it. That was more than housekeeping. The backlog exercised the whole sequence repeatedly: identify the expired site, remove Cloudflare, remove the relay, update the database, and notify the owner without making notification a gate.

We did not treat the cleared backlog as proof that the scheduler was done. We left a follow-up check for June 27 to confirm that the automatic run fired correctly on its own. A manual clearance shows the teardown path can work. It says nothing about whether the scheduled trigger will select the right records on the right day.

There is also a separate follow-up for the downgrade-at-checkout case. That is the kind of adjacent lifecycle path that can bypass a clean implementation if we pretend every domain reaches expiry through the same sequence.

The final system is not complicated. A site expires and the customer is warned. The next day, the monitor tears down Cloudflare, relay, and database state. The owner receives a best-effort notice. Each part has a distinct job and a distinct failure mode, and when automation deletes something, those are the three things worth checking: when it fires relative to the warning, whether a failed email can block the cleanup, and which contact record gets the notice.
