What you're doing is somewhat secure.
I would refer to your salt though as a key - you are generating a keyed hash. Ensure that you generate a key with sufficient entropy. I would recommend a strength of 128 bits generated by a CSPRNG.
Some keyed hashes generated in this manner are vulnerable to a length extension attack. That is, if an attacker has generated a validation token for foo@example.com
then they will be able to work out the hash for foo@example.com.example.org
. This is because the output of a hash algorithm also betrays its state. To mitigate this, you could use the HMAC algorithm.
Your current approach also has the limitation that an email address always has the same token. If an email address expires (say Bob Smith with email bobs@example.org
is fired from his job at Example Organisation, he will know the verification code that the next Bob S. will get when he starts working for Example Organisation). Whether this is any risk to your application is for you to decide. To mitigate this, you could use JWTs instead, which will enable you to put an expiry date into the token that can be validated. JWT's HS256 algorithm also uses an HMAC, solving that problem too.
Using keyed hashes should be efficient, and doesn't have the storage, maintenance and overheads of database lookups.
By UID do you mean UUID?
Remember that:
the purpose of a [UUID] is to be globally unique, not to be unguessable.
and
[UUIDs] are designed for uniqueness, not for security.
You would be better off generating an 128 bit token on the fly using a secure source of entropy (say another CSPRNG). You may want to hash these (without salt) on the server-side using SHA-256 to prevent any data leakage vulnerability from meaning an attacker can validate any email address.