Essentially this looks like it boils down to making sure you can trace from a given build back to the source code that created it (and vice versa) - I have seen two general approaches to solving this
- The solution you mention - mark the code in the SCM with the build number (with tagging being the obvious way to do this)
- The opposite - store the revision number from the SCM inside the package produced by your build (for example have the build spit out a small text file containing the SCM revision number that gets included in the package).
I would say that both of these solutions work quite well, and the best one to use depends on how you intend to use this information. For example, if your motivation is that, when debugging an issue on a production system, you can easily check out the relevant source code for that software version, then approach (2) works well as it is easy to look up the SCM revision number on your production system and check out the code for that revision. However, you also have a number of good reasons for preferring option (1) listed in your question, so I would go with that.
To your point about controlling email spam - personally have never found a good way of stopping build systems from being spammy, I think the best strategy is to make it easy for people to filter this out if required (i.e. subject headers that can be used in email rules, or sending all mail to a shared email address specifically for build spam). Sorry I don't have a more useful suggestion.
Kudos for making your builds traceable back to the source code!