Regarding GIN Index in Postgresql
You are returning 1 million rows.  Returning 1 million rows is going to take some time.  Especially when it has to do a pattern matching recheck of each row.  How fast do you expect this to be?

Your best bet is fixing things on the application side.  Why do you need a million rows?  What are doing with them?  And if you do need to return that many, why do you need to do it so often that it matters if it takes an extra second to do?  But beyond that:


Get a faster computer.
Upgrade to a recent version of PostgreSQL and see if you can get parallel execution (that would depend on what percentage of your table is being returned by this query)
Get rid of the "lower" function and use "ilike" rather than "like".  Indexes based on gin_trgm_ops support "ilike" queries naturally with no special steps needed.
Use an ordinary btree index on reverse(lower(email)) so that the wild card is at the end rather than the beginning of the query string.  Better yet, store the email in that form to start with, so expression indexes are not needed.