We could test for the negation of the pattern ("mismatches") by using negative look-behind and look-ahead assertions. They can be used to discard any matches of [a-z]{1,3} that are preceded or followed a character that would also match [a-z]:
matches = StringCases @ RegularExpression @
"(?<![a-z])[a-z]{1,3}(?![a-z])";
So then:
"abcdefg hi jkl mn opq rstuv w xy z " // matches
(* {hi,jkl,mn,opq,w,xy,z} *)
"abcdefg_hi_jkl_mn_opq_rstuv_w_xy_z_" // matches
(* {hi,jkl,mn,opq,w,xy,z} *)
If we would rather not repeat the target pattern three times, we could use named groups and backreferences (although for a short pattern like [a-z] this is probably overkill):
matches2 = StringCases @ RegularExpression @
"(?<!(?<p>[a-z]))(?&p){1,3}(?!(?&p))";
"abcdefg hi jkl mn opq rstuv w xy z " // matches2
(* {hi,jkl,mn,opq,w,xy,z} *)
"abcdefg_hi_jkl_mn_opq_rstuv_w_xy_z_" // matches2
(* {hi,jkl,mn,opq,w,xy,z} *)
All of this syntax is described by the PCRE pattern documentation. The WL documentation page Working With String Patterns states that PCRE is the library used to implement regular expressions.