Sure, you're right, but only technically. Having an extra collaborator in the model is asking for trouble - there's nothing about a User that requires it to know anything about emailing. It violates all kinds of design principles. Putting your logic for "I should email the User when they sign up" into the User model is using "design by related words".
In traditional Rails design, the controller is exactly the right place for this. Another option is a service layer of some kind, if you do that, e.g. a DCI context, or just another Plain-Ol-Ruby-Object that is in charge of the logic of signing up a user.
The best part of separating out the logical business sequence of signing up a user into its own object (I can see this method in my head, something like save_new_user, sign_in_user, queue_welcome_email) is that it becomes massively easier to test - you can just mock the various calls to its collaborators' public methods because they should all have unit tests.
Ah, technically correct - the best kind of correct. :-)
Anyway, I agree with pretty much everything you say. As I mentioned elsewhere in this thread, we've gone down a pub-sub event system for handling our generic post-action tasks. The controller announces a :create_user event, and any interested listeners can respond appropriately. And yes, one of the biggest benefits of this has been the ability to test that event-handling logic in isolation; stubbing and mocking collaborators, and generally having the sort of testing fun you're normally not supposed to have with Rails.
In traditional Rails design, the controller is exactly the right place for this. Another option is a service layer of some kind, if you do that, e.g. a DCI context, or just another Plain-Ol-Ruby-Object that is in charge of the logic of signing up a user.
The best part of separating out the logical business sequence of signing up a user into its own object (I can see this method in my head, something like save_new_user, sign_in_user, queue_welcome_email) is that it becomes massively easier to test - you can just mock the various calls to its collaborators' public methods because they should all have unit tests.