If you’re building it just for you into one game, the GameState could be just fine.
If you’re building it into a plugin for use in multiple projects then the GameState becomes a less practical location because it requires a lot more setup to tie that specific game state type to the project specific classes.
Other people have suggested subsystems, and that’s definitely a good option (and it’s how I’ve implemented mine). There are multiple types of subsystems so you can pick the one that matches the lifetime you want for your Manager (like GameInstance vs World lifetimes).
A component is also possible solution (and would allow it to easily exist on a GameState). But then you have to contend with multiple instances possibly existing in your game (which may or may not be a deal breaker).
GameplayMessageSubsystem is worth a look at… does what youre describing already using gameplay tag events. In the Lyra > Plugins > GameMessageRouter plugin is the implementation.
Also AsyncMessageSystem is out there… I haven’t used it but looks similar and can be used across threads.