A report's grain describes what one row represents. In shop.orders, one row is an order. In shop.customers, it's a customer. Joining those tables can repeat a customer's details, so counting the joined rows answers a different question from counting customers.
Missing values need their own rules. COUNT(*) counts rows, while COUNT(email) skips missing emails. COUNT(DISTINCT customer_id) counts people once even when they have several orders. Use IS NULL to test a missing value; equality against NULL doesn't return true.
A left join preserves customers without orders, but a filter on the order side can remove them afterward. Put an order restriction in ON when the report must keep every customer. Before writing a total, say its grain and decide what a missing match should contribute.