Skip to content

Case Study: Taking Over a WooCommerce Maintenance Project

By Jasper Frumau Case Studies

In July 2026, Henry Cutler emailed me. Henry owns WorkCycles, a bike shop and manufacturer in Amsterdam that builds cargo bikes and classic Dutch city bikes — the kind that get handed down rather than replaced. Their bakfietsen and custom builds are the real thing: durable, distinctive, built to actually carry kids and cargo through a Dutch winter rather than sit in a showroom. I’d built and looked after the WorkCycles WooCommerce site for Henry years earlier, back when I lived in Amsterdam myself. The developer who took it over from me had since moved on to other work, and rather than shopping around for someone new, Henry came back to whoever had done the job the first time and asked if I could take it on again.

That’s worth sitting with for a moment before getting into the technical work. A client coming back after nine years isn’t a referral or a cold lead — it’s someone who watched the original work hold up long after the invoice was paid, and decided it was worth the call. WorkCycles is also exactly the kind of business Imagewize works best with: a real, local Dutch company that makes and sells a physical product, where the website’s job is to support the business quietly rather than be the business. That distinction came up again and again over the two weeks that followed.

Quick Summary: A former client asked me to take back over maintenance on his WooCommerce site after his current developer moved on. Step one was a verified backup — nothing gets touched before that exists. On a large site and a CPU-throttled host, that meant splitting the archive across four parallel connections, cutting a projected 15-hour download to 39 minutes. The audit that followed found order notifications still pointed at an individual’s mailbox rather than a role address, a duplicate contact page holding the good URL, and PHP 8.0 (unsupported since November 2023) still in production. The most instructive fix was an order confirmation email that couldn’t just be switched back on: both payment methods are offline, so every order sits “on-hold,” and WooCommerce’s stock on-hold email makes a promise about payment this shop never takes — it had to be rewritten from scratch instead.

What I Found When I Logged In

Henry’s ask had two parts. He needed ordinary upkeep — the kind of on-call WordPress developer work that keeps a site running and secure without a retainer. And he needed content changes: WorkCycles is relocating, several product lines are being retired as sales centralize to one location, and a few pages need to disappear with proper redirects so customers and Google both land somewhere real instead of a dead end.

Before touching any of that, I wanted to know exactly what I was working with. Here’s the stack I found:

  • WordPress on an older WordPress 6 release
  • PHP 8.0 — unsupported since November 2023
  • An outdated Oxygen 4.x page builder, with Storefront underneath as a stock parent theme
  • Product Add-Ons Ultimate and Woo Variation Swatches layered on top of WooCommerce
  • Forminator handling appointment bookings
  • A FileMaker Pro ERP, sitting entirely outside WordPress

Plugins and themes were behind, debug logging was still switched on in production (it should only ever run there briefly, then get turned off), and some old files were still sitting on the server taking up space. None of that is unusual on a site that has been running quietly for years — it’s simply the backlog that accumulates whenever a site isn’t anyone’s active project.

One detail changes how you have to think about the rest of the site: WorkCycles isn’t really a webshop. It’s a WooCommerce catalog with pricing — you can browse and configure products — but no money actually changes hands on the website. Orders go into FileMaker Pro, and payment gets arranged with the client afterward, usually by bank transfer or card. The checkout mentions a 3.5% card fee, but nothing is charged on the site itself — payment happens afterward through a link Henry sends from the ERP, with the card fee folded into that amount by hand. The checkout option behind it is one of WooCommerce’s built-in offline methods, relabelled. We rewrote the wording to say exactly that: a payment link arrives by email, the card fee is already included in it, and no payment is taken on the website.

Getting a Backup I Could Actually Trust

Rule one of taking over someone else’s site: don’t fix anything until you have your own independent copy of it, stored somewhere the host doesn’t control. Everything else is reversible once that exists. Nothing is reversible before it.

That should have been the easy part. It wasn’t, because the site is large — 125,504 files — and the shared host throttles by CPU, not just bandwidth. A plain server-side backup command took 15 minutes for about 8GB with no network involved at all, which told me the slowdown was the host itself, not my connection. And a first attempt to download the finished archive crawled along at roughly 150 KB/s: a projected 15 hours for the whole thing.

The fix was to stop treating it as one download. I split the backup into four pieces and pulled all four down at once, over four separate connections. Each connection gets its own slice of the host’s throttle, so four of them move roughly four times the data. The 15-hour download finished in 39 minutes — and before touching anything else on the live site, I checked the result byte-for-byte against the copy left on the server (8,809,973,567 bytes, matching exactly on both ends).

For the developers: the exact backup commands

Archive on the server, excluding dead weight already found via du -sh:

ssh user@host '
cd ~/domains/example.com && \
tar -czf ~/site-backup-$(date +%F).tar.gz \
  --exclude=public_html/old-dead-backup.tar.gz \
  --exclude=public_html/wp-content/debug.log \
  public_html
'

Verify the archive isn’t truncated or corrupt before relying on it:

ssh user@host 'gzip -t ~/site-backup-2026-08-10.tar.gz && echo OK'
ssh user@host 'tar -tzf ~/site-backup-2026-08-10.tar.gz | wc -l'

wp db export failed outright because the host has PHP’s exec() disabled — wp-cli calls mysqldump under the hood, so I pulled the DB credentials with wp config get (reads wp-config.php directly, no exec() needed) and called mysqldump straight from the shell:

ssh user@host '
cd ~/domains/example.com/public_html
DB_NAME=$(wp config get DB_NAME)
DB_USER=$(wp config get DB_USER)
DB_PASS=$(wp config get DB_PASSWORD)
DB_HOST=$(wp config get DB_HOST)
mysqldump -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > ~/db-backup.sql
gzip ~/db-backup.sql
'

Split into four roughly-equal chunks without re-compressing (cheap on an already CPU-throttled host):

ssh user@host 'cd ~ && split -n 4 site-backup-2026-08-10.tar.gz part-'

Pull all four in parallel, each in the background, so they actually run concurrently:

cd ~/local/backups/
for p in aa ab ac ad; do
  rsync -av --partial -e "ssh -o BatchMode=yes" \
    user@host:~/part-$p ./ > rsync-$p.log 2>&1 &
done
wait

Reassemble in alphabetical order (guaranteed by split) and re-verify locally:

cat part-aa part-ab part-ac part-ad > site-backup-2026-08-10.tar.gz
ls -lh site-backup-2026-08-10.tar.gz
gzip -t site-backup-2026-08-10.tar.gz && echo OK

Then clean up the parts and intermediate files left on both ends.

Order Notifications Were Going to the Wrong Inbox

Before changing any settings, I checked where the site’s own alerts were actually going — and this is what turned up the most expensive problem in the whole audit. WooCommerce’s admin “New order” email, the one that tells the shop an order has come in, was going to an individual’s mailbox alongside the company’s shared info@workcycles.com. Nothing improper about that — it’s how most sites end up configured, because whoever is watching the site day to day is the obvious person to tell. It just quietly stops working the moment that person is no longer the one watching.

The cancelled-order and failed-order emails had no recipient configured at all, which meant they were quietly falling back to WordPress’s own admin email — also still set to an address on a different domain than the site’s own, and worth confirming was even still a working mailbox. On top of that sat a stale, years-old, unconfirmed request to change that address, leaving a permanent “pending change” banner in the admin screen that had never been cleared.

For a site whose entire commercial purpose is capturing orders, that’s about as expensive as a misconfiguration gets — not because anyone did anything wrong, but because the wiring simply outlived the arrangement it was built for. I repointed everything to the address Henry actually wanted (hallo@workcycles.com), cleared the stale pending change, and told him about the fix rather than making it quietly, since it changes who gets told about a new order. The general lesson is worth stating plainly: point system notifications at a role address the business controls, not at a person. People move between roles; info@ doesn’t.

The Contact Page Was the Wrong Contact Page

The site’s real contact page — 285 lifetime views, linked from the main navigation — was sitting at /contact-2/. The clean /contact/ address was occupied by an orphaned duplicate from 2019, with a single lifetime view and no links pointing to it from anywhere. It had been quietly sitting on the better URL for seven years, almost certainly a test page that never got cleaned up.

  1. Renamed the orphan off the /contact/ slug and trashed it
  2. Reslugged the real page onto /contact/
  3. Updated the navigation to point at the new URL
  4. Added a 301 redirect from /contact-2/ to /contact/

I checked the redirect actually worked (a clean 301 with the right destination) rather than assuming it did, and its hit log showed 3 real visitor hits the same day — confirmation the old URL was still in active circulation, not just a dead link nobody used anymore.

One thing worth flagging for anyone doing this kind of cleanup: renaming the orphan’s slug made the redirect plugin auto-generate a reverse redirect — /contact/ back to the trashed orphan’s new slug. It was created disabled, so it never went live. But had anyone enabled it later while tidying up a redirect list, it would have sent the freshly fixed contact page straight back into a dead end. I deleted it outright rather than leaving it sitting there looking harmless.

Getting Off an Unsupported PHP Version

Production was still running PHP 8.0, which stopped receiving security patches back in November 2023. Any vulnerability discovered in PHP since then simply stays open on a site still running it.

Upgrading PHP under an old plugin stack is exactly where sites break, so I didn’t do it blind. I tested each candidate version against a local copy of the site first, running the same requests against all six main pages with error logging switched on:

PHP VersionFatal ErrorsDeprecation Notices
8.30144
8.40187
8.50197

Zero fatal errors on every version I tried. I chose PHP 8.4 rather than the newest 8.5, on purpose: nearly all of those warnings were coming from plugins frozen at old versions — Advanced Custom Fields Pro and Oxygen Classic among them — and staying one release ahead of frozen code leaves more room than staying three ahead. Before rolling it out to production, I also walked through the admin side by hand: dashboard, plugins, field groups, products, WooCommerce settings, the page editor, and the Oxygen builder itself. All clean.

Testing Everything Locally First

Rather than testing plugin and core updates on the live site, I rebuilt WorkCycles locally with Laravel Valet — a lightweight local WordPress setup, and the right tool here since this is a plain WordPress install rather than the Bedrock/Trellis setup I use on other projects. I matched the live site’s exact WordPress version first, so I was reproducing the actual current state before changing anything, then overlaid the real plugins, theme and uploads, and imported the database.

With a working local copy in place, I updated everything that was behind, aiming for the latest WordPress release. Advanced Custom Fields Pro was the interesting case: stuck three years out of date, not from neglect but because its license field was empty. Without a license, ACF Pro quietly stops receiving updates — including security ones — and nothing on the dashboard tells you that’s happening. I applied my own developer license to bring it current until Henry has one of his own. WooCommerce Product Add-Ons Ultimate needed the same kind of care: its plugin header only claimed support up to an older WooCommerce release, so I emailed the vendor to confirm real compatibility rather than just testing locally and hoping. I also found and switched off a leftover multilingual plugin that had been running for years with no multilingual system left behind it — 25 orphaned database tables and one active language, still loading on every page.

Removing Old Pages, the Right Way

Some pages and products needed to come down as part of the relocation and the move to selling from one location. Wherever something was removed, the old address now redirects somewhere real, using the Redirection plugin already installed on the site — which also shows how often each redirect actually fires, useful for catching a broken link nobody’s reported yet.

The Order Email That Had to Be Written, Not Turned On

Alongside the updates, Henry reported a real bug: customers placing an order got nothing back at all. No confirmation, no acknowledgment, nothing. The obvious diagnosis — a broken mail setup — turned out to be wrong. Mail was leaving the server fine. The customer-facing order emails had simply been switched off in WooCommerce’s settings, while the shop’s own notification stayed on. The site was doing exactly what it had been configured to do.

Turning the customer emails back on looked like the fix. It wasn’t, and understanding why meant learning something WooCommerce’s own documentation doesn’t spell out clearly: which payment method is used decides the order’s status, and the order’s status decides which email — if any — actually fires. WorkCycles takes no payment online at all. Both of its checkout options are offline methods, so every order goes straight to “on-hold” and stays there while the real transaction happens through Henry’s FileMaker system, not through WooCommerce.

That one fact rules out three of WooCommerce’s four built-in customer emails before you even start:

  • Processing order needs an online payment to have been received. With no online gateway, this store has never had a single order reach that status.
  • Completed order only fires when an order is marked complete by hand — which last happened in December 2024, when that part of the workflow moved into the ERP.
  • Order details (formerly “Customer invoice”) is manual-only. It doesn’t hook into any status change, so there isn’t even a toggle to switch it on.
  • Order on-hold is the only one that fires automatically at checkout. On a store like this, it is the order confirmation.

So the field narrows to one candidate — and that candidate says something untrue. WooCommerce’s own template hardcodes the line “It’s on-hold until we confirm that payment has been received,” for a payment WooCommerce is never going to take. That’s precisely why the previous developer had switched it off in the first place: honest silence, chosen over a dishonest email.

Rewording that one hardcoded sentence would normally mean overriding WooCommerce’s email template inside a child theme — except this site, built on Oxygen, doesn’t have one. Rather than force that, I wrote a small, separate email that hooks into the same on-hold moment but says something true: that a person will follow up within a working day or two, and that nothing is reserved until they do. It pulls its main text from the payment gateway’s own instructions field, so there’s exactly one place to keep it up to date, and it shows up in WooCommerce’s own settings screen so Henry can edit the wording himself without touching code.

For the developers: two implementation traps worth knowing

First, WooCommerce builds its status-transition hook names from the status slug, so the action is spelled with a hyphen, not an underscore:

// Fires.
add_action( 'woocommerce_order_status_pending_to_on-hold_notification', ... );

// Attaches cleanly, reports has_action() === true, and never fires.
add_action( 'woocommerce_order_status_pending_to_on_hold_notification', ... );

Second, WC_Emails::send_transactional_email() wraps everything in a try/catch. A fatal error inside a custom email class is just swallowed — no mail, no error, no log line, just nothing happening. Debug by calling trigger() directly rather than through a status change, or you’ll be staring at an empty mail log wondering which of the two problems you actually have.

Where Things Stand Now

This is an ongoing engagement, not a finished project, so it would be dishonest to present it as a bow-tied success story. What’s done and live:

  • Verified independent backup, byte-for-byte checked, off the host
  • PHP 8.0 (end of life) upgraded to 8.4
  • Plugin stack current; ACF Pro back on a license and receiving security updates
  • Order notifications reaching the company’s own inbox
  • Contact page on the correct URL with a clean 301 behind it
  • Debug logging and stale config artifacts removed from production
  • Dead multilingual plugin deactivated
  • Order-confirmation email live, matching how the business actually takes payment

The relationship with WorkCycles continues on a per-task basis, billed as work completes rather than a fixed monthly retainer — around 12 hours so far across the first two weeks. The next round is the content updates that started this engagement in the first place: explaining the relocation, retiring a few product lines, and redirecting the pages that go with them.

What This Kind of Handover Actually Looks Like

If you’re evaluating a developer to take over an existing site — WooCommerce or not — this is roughly the order that holds up:

  1. Get your own verified backup first, stored somewhere the host doesn’t control. Everything else is reversible once it exists.
  2. Check where the site’s own alerts go before changing anything else. A person’s address in that field instead of a role address is a real, cheap-to-fix risk.
  3. Check the platform’s expiry dates: PHP version, plugin licenses, plugin compatibility. Software doesn’t announce that it’s stopped updating — it just quietly does.
  4. Test upgrades against a copy that matches production exactly, not whatever version happens to be on your laptop.
  5. Ask before “fixing” anything that looks switched off on purpose. What looks like neglect is sometimes a decision holding the business together.

WorkCycles is a good example of the kind of client this approach works best for: a real, local Dutch business with a physical product and a workshop behind it, where the website’s job is to support the business quietly rather than run it. If that sounds like your shop, the questions below cover what people usually ask first.

Frequently Asked Questions

  • What should I do first when taking over a WordPress site from another developer? Take your own independent, verified backup before changing anything: files and database, stored somewhere the host does not control. Plugin backups sitting on the same server as the site do not count, because a server-level problem takes them down with the site.
  • How do you back up a large WordPress site on a throttled shared host? Archive the site on the server, split the archive into parts, and download the parts concurrently over separate SSH connections. Each connection gets its own throttle allowance. On this site that took the transfer from a projected 15 hours to 39 minutes.
  • Is it safe to upgrade PHP on an older WooCommerce site? Usually, but only if you test first on a local copy pinned to production’s current PHP version, then step it up one version at a time and check both the front end and the admin. Upgrading blind on a site with old paid plugins is where sites break.
  • How much does it cost to take over an existing WooCommerce site? Imagewize works hourly at €65 for this kind of work, or on a fixed-price quote once the scope is clear. The first two weeks on this site — backup, audit, PHP upgrade, plugin updates, and the fixes above — came to around 12 hours.
  • My site works fine. Why does an unmaintained WordPress site matter? Because the failures are silent. An end-of-life PHP version, an unlicensed plugin that has stopped receiving security updates, and order notifications going to an unread mailbox all look exactly like a working site right up until they don’t.
  • Do you only work with online-only stores, or with local, brick-and-mortar businesses too? Local brick-and-mortar and manufacturing businesses are a core part of who Imagewize works with, not an exception. WorkCycles — an Amsterdam manufacturer selling through its own workshop as much as online — is a typical client: a real business with a physical product, where the website supports day-to-day operations rather than replacing them.

Need a WooCommerce Developer for Your Store?

We build and optimize WooCommerce stores for SMEs — from custom checkout flows and payment integrations to performance tuning and ongoing maintenance. Fixed-price quotes available.

  • Custom checkout and cart optimization
  • Payment gateway integration (Stripe, Mollie, PayPal)
  • WooCommerce performance and speed optimization
  • Ongoing store maintenance and support

Leave a Reply

Your email address will not be published.