Snapshots have arbitrary names. And zfs send -i [snapshot1] [snapshot2] can send the difference between any two snapshots. You can make use of that to have two (or more) sets of snapshots with different retention policies.
e.g. have one set of snapshots with names like @snap.$timestamp (where $timestamp)$timestamp is whatever date/time format works for you (time_t is easiest to do calculations with, but not exactly easy to read for humans. @snap.%s.%Y%M%D%H%M%S provides both). Your hourly/daily/weekly/monthly snapshot deletion code should ignore all snapshots that don't begin with @snap.
The second set, you can call @offsite.$timestamp. It should have whatever snapshot retention/deletion policy makes sense for that task, and the code used to manage it should ignore all snapshots that don't begin with @offsite.
BTW, this may be spelling out the obvious, but you can use this for your hourly, daily, weekly, monthly snapshots, so each can have different retention policies. e.g. @hourly.$timestamp, @daily.$timestamp etc instead of just @snap.$timestamp.
Also obviously, this will use more disk space because blocks used by datasets are not freed up until there are NO snapshots left that reference them.