OK, well I feel like an idiot now. My script changes didn't fix the timeout issue (the rest of it was worth doing though).
But this time I opened the vb_mailqueue table, found the last email address that was queued up. I found the userid of that account, and ran the following query:
UPDATE vb_user SET `emailDate`=0 WHERE userid>=9245
So the last 2000 accounts were set back to 0, and then I just ran the script again, this time it picked up from where it left off.
I wish there was a more elegant way of doing it, but unfortunately not without running a lot more SQL queries. And it's not a regular thing anyway.
What I might do to make it work next time is:
UPDATE vb_user SET emailDate=(YESTERDAY'S TIMESTAMP) WHERE userid<5000
UPDATE vb_user SET emailDate=(TODAY'S TIMESTAMP) WHERE userid>5000
That way 5000 members will be notified again in 29 days, and the rest (6000) will be notified in 30 days. It fails after about 9000, so this should be safe enough.
And after 37 days (1 week after the second email) I'll be pruning anyone who hasn't logged in in the last 67 days