I'd posit, the same way you do all your runtime error handling?
If you did it "with classes and properties for encapsulation", you'd throw an exception (ie in the setter) that some code, somewhere higher up in the call chain, would have to watch out for. It's not your "classes and properties" that magically solve this, it's your discipline to throw and catch exceptions. In most any FP language you have a wide arsenal of representations for signaling erroneous values/inputs, from simple Maybe or more detailed Either (or whatever these are called in F# ;), to full-blown exceptions, to forcing immediate-halt-with-stderr-message. As suits the current app/lib context.
"making illegal states unrepresentable" in types is for pre-empting as many easy-to-make-in-the-heat-of-the-moment developer mistakes as the type system / compiler understands how-to: not user error.
There's of course academic explorations and research into how we can shift handling ever-more classes of bugs to the static (compilation) side, in Haskell there's LiquidHaskell a prominent example. But until you have a time-machine, you can't retroactively prevent compilation of your program if the input it reads once compiled is erroneous :D in other words, the only way to prevent wrong email addresses is to impose a GUI that cannot possibly let one through.