parseEmail : String -> Either EmailError ValidEmail
There is no problem using `ValidEmail` abstraction. The problem is type stability, when your program enters a stronger state at runtime (i.e. certain validations are performed at runtime) it's best to enter a strong state at compile time (stronger types) so that compiler can verify these conditions. If you remain at String, these validations (that a string is valid email) have no compile-time counterpart so there is no way for compiler to verify. So use `ValidEmail` instead.
Have you seen ArkType (https://github.com/arktypeio/arktype)? Similar parse-based approach to validation with a significantly more concise definition syntax:
const info = type({
name: "string>0",
email: "email"
})