See the topic "Using Messages and Message Queues" in MSDN (under Win32 and COM Development > User Interface > Windows User Experience > Windows Management > Windows User Interface > Windowing > Messages and Message Queues; you'll probably need to take a look at the other articles and samples in the same section). Quick summary, omitting error handling and using C syntax rather than C# for reasons discussed below:
RegisterClass(...);
CreateWindow(...);
ShowWindow(...); // probably not in your case
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
As you can see from the window setup boilerplate, this still relies on "silent windows," albeit created and message-pumped via the Win32 API rather than through WinForms. So you're not really gaining anything by doing this way. Hence my feeling there's not much point translating this stuff into C# -- if the only solution to your problem is an invisible window, you may as well use an invisible Windows Form and all the friendly wrappers that come with that platform.
However, if you're not actually using a Windows Forms control like the poster of the linked question, then you can quite happily use .NET events in a console application. The restriction to STA and the need for a message pump is specific to receiving events from WinForms and ActiveX controls like the WebBrowser (or messages from Win32 HWNDs, though that doesn't necessarily require STA).