Most teams add a suppression list after their first complaint. The list is a table of addresses, and within a month nobody remembers why half of them are on it. That is a compliance problem, not a tidiness problem.
Every entry needs a reason and a scope
A hard bounce, a spam complaint, a manual block and an unsubscribe are four different facts with four different lifetimes. A hard bounce is permanent for that address. An unsubscribe is permanent for that channel but may not cover a transactional password reset somebody explicitly requested. A complaint usually means permanent for marketing and a manual review before anything else.
If the table only has (address, created_at) you cannot answer 'why is this blocked' in a support ticket, and you cannot safely unblock anything.
Normalise before you compare
Local parts are case sensitive in theory and case insensitive in practice for every provider your customers actually use. Lowercase the whole address for the suppression lookup, but store the address as the customer wrote it so the UI is not confusing. If you support plus-addressing, decide whether a suppression on user+tag@example.com blocks user@example.com. Most senders say yes and most customers expect no.
Check at send time, not at enqueue time
A message can sit in a queue for an hour. If suppression was checked when the job was created, an unsubscribe that arrived in between is ignored. Re-check immediately before the SMTP conversation, and treat the check as part of the send transaction.
Volanea keeps suppression scoped per project with the reason attached, and re-checks it at delivery time rather than at enqueue.

