@cks@glyph@jalefkowit update: the combination of CTEs and windowing functions did the trick. The underlying SELECT to pick out the applicable customer records takes about 30s - big data set, huge JOIN - but the user_id to unused coupon mapping adds about 200ms above that, about O(n) in the number of unmapped records, and then mostly because of the row writes.
I got exactly the solution I needed and I understand the underlying new-to-me concepts. Thank you again!!