A couple of changes, on top of what @Adrian said...
UPDATE
ud1 -- @Adrian's change. Update the instance that you have already aliased.
SET
externalEmail = ud2.Email
FROM
@user_dupes AS ud1
INNER JOIN
@user_flat_emailtable_dupes AS ud2
ON ud1.UserName = ud2.UserName
WHERE
ud2.EmailType = 2 -- Removed sub-query, for layout, doubt it will help performance
AND ud2.Email IS NOT NULL
AND ud2.Email <> '' -- Removed the `LEN()` function
But possibly the most important past is to ensure you have indexes. The JOIN is necessary for this logic (or correlated sub-queries, etc), so you want the join to be performant.
An Index of (UserName) on @user_dupes, and an Index of (EmailType, Email, UserName) on @user_flat_emailtable_dupes. (This assumes ud2 is the smaller table, after the filtering)
With the indexes as specified, the change from LEN(Email) > 0 to Email <> '' may allow an index seek rather than scan. The larger your tables the more apparent this will be.