A handy hint? or not. Blink without delay 3.0.

"Run every" implies that it should run N times in T time...

#define RUNEVERY(n) for (static unsigned long lasttime, unsigned long t = (n); millis() - lasttime > t; lasttime += t)

"Run after" implies that it should run after T milliseconds have elapsed...

#define RUNAFTER(n) for (static unsigned long lasttime; millis() - lasttime > (n); lasttime = millis())

The variable should be named to reduce the chances of a conflict...

#define RUNAFTER(n) for (static unsigned long _lasttime; millis() - _lasttime > (n); _lasttime = millis())

To be more Arduinoish the macro should be named in modified camel...

#define runAfter(n) for (static unsigned long _lasttime; millis() - _lasttime > (n); _lasttime = millis())

To eliminate drift, time should be sampled once...

#define runAfter(n) for (static unsigned long _lasttime, unsigned long _thistime = millis(); _thistime - _lasttime > (n); _lasttime = _thistime)

The error needs to be eliminated...

#define runAfter(n) for (static unsigned long _lasttime, unsigned long _thistime = millis(); _thistime - _lasttime >= (n); _lasttime = _thistime)

16 bit may be a better choice...

#define runAfter(n) for (static uint16_t _lasttime, uint16_t _thistime = millis(); (uint16_t)(_thistime - _lasttime) >= (uint16_t)(n); _lasttime = _thistime)

What did I forget?