One way you can reduce your query rows is to apply the filters you are testing for in the first for
loop.
However, you want to avoid field != null
queries as they are very expensive and result in a full table scan.
One thing you can do is make three calculation fields on your contact object
eg Work_Email_Not_Blank__c
and define it something like:
if (isBlank(con.npe01__WorkEmail__c),FALSE,TRUE)
THEN you need to use a workflow or process to copy these fields into static versions eg Work_Email_Not_Blank__c -> Work_Email_Not_Blank_Static__c
Then you can change your top query to something like:
SELECT Id,Email,npe01__WorkEmail__c,npe01__HomeEmail__c
FROM Contact
WHERE Work_Email_Not_Blank__cWork_Email_Not_Blank_Static__c = true
AND Email_Not_Blank__cWork_Email_Not_Blank_Static__c
AND Home_Email_Not_Blank__cWork_Email_Not_Blank_Static__c