Preferences have legal teeth
Treat preference checking as correctness with legal teeth. Sending marketing email to somebody who opted out breaks the law, not merely taste.
Model it as a matrix of category against channel. Transactional, security, social and marketing, crossed with push, email and messages, with security notifications not optional.
Put the check in your notification service, on every single send, reading from a cached store.
Never let callers assert that this person opted in, because that is exactly how compliance incidents happen. Callers cache stale answers.
Understand who your rate limits protect, because it is your users, from you. Product teams ship notifications independently, and the sum is fourteen pings a day until people switch notifications off entirely, which surveys repeatedly name as a leading reason for deleting an app.
Cap per person per category per day. Three social pushes a day, marketing weekly. Mechanically that is one counter keyed by person, category and day, incremented and compared per send, which is trivial to build.
Expect the organisational fight over whose notification gets cut to be the hard part. That is why mature systems evolve toward one central scheduler that batches, times and drops notifications per person across every producer, and at least one large company has published theirs.
Hang quiet hours and batching off the same decision point. Hold anything non-urgent during somebody's local night, and collapse five people liked your post into one digest.
Give both a scheduler with a store for delayed delivery, rather than pure pass-through queues.
Priority lanes
Divide your lanes by priority last. A password reset code and a sale announcement must never share a queue, because a campaign burst delays that code by eleven minutes exactly when somebody is staring at a login screen.
Run separate topics per tier with their own worker pools. Provision the transactional lanes with headroom for bursts, and throttle marketing to a drain rate that cannot starve anybody.
Tie those lanes back to your earlier burst arithmetic in an interview. That is what makes the whole design cohere rather than sounding like a list of features.
Worked example
An e-commerce app's uninstall rate climbs for two quarters, and exit surveys keep saying too many notifications. Ines audits a week of sends for a sample user: 26 notifications, including 4 for one order (confirmed, packed, shipped, delivered), 9 marketing pushes from three teams unaware of each other, and price alerts at 3 am for a user in Mumbai because the scheduler ran in UTC. She ships three changes: a per-user cap of 2 marketing pushes daily enforced with Redis counters keyed (user, category, day), quiet hours computed from the device timezone with holds released at local 9 am, and collapsing of order-status pushes so packed silently replaces confirmed if it is still unread. Marketing predicts a conversion drop; over the next quarter, notification opt-out rate falls from 31 to 22 percent and click-through per marketing push nearly doubles, because the pushes that survive are the ones users tolerate.