> sendToEmail sends to any kind of Email.
That's my point, you don't need a combinatorial explosion of behaviors for every possible most-specific-type, you can just reuse existing ones.
> And if the code wants to know if an Email is verified, it inspects the verified field.
That's exactly what you shouldn't do. Runtime "type" inspection is just bad overly distributed parsing. The point of typing is to move errors to compile-time.
emailLaunchCodes = (email: VerifiedEmail): Promise<void> => ...
This doesn't need to inspect anything, because the type signature guarantees that someone else has already handled that.
> Whether you centralize the verification code (or otherwise have a separation of concerns for it) or not is independent of whether you use the type system to express when an email is verified or some other mechanism.
Separating the verification code - which is table stakes, really - doesn't guarantee that you're not accidentally adding or losing "verification" elsewhere. Types can and should.