For multiple emails, we'll need to define exactly whats going on to ensure this all makes sense.
Lets say I'm one of the 900 email people. So AstroTeg has a userID of say 145.
Now lets say you have 10 promos (starting from promID 1 up to promID 10). AstroTeg qualifies for 3 of them (they may get emailed out at any time or over a span of a huge amount of time - doesn't matter).
Your xref table might look like this:
userID | promID
145, 5
145, 9
145, 2
And this is fine. What's unclear in your description is when you say "receive a 2nd email". Is it possible AstroTeg will receive 2 emails for promo #5? (for example) If so, this will make things just a little more complex. In this case, you could add an auto increment ID to the xref table to allow for duplicate userID/promID combos. But without it, it ensures only one email goes out per person per promo, which usually is a good thing in this scenerio.
Addressing your issue of those who HAVE NOT received email fro promos 1 and 2, you could do the following:
SELECT userID FROM User LEFT JOIN xref_UserPromo ON xref_UserPromo.userID = User.userID WHERE xref_UserPromo.userID IS NULL AND (xref_UserPromo.promID = 1 or xref_UserPromo.promID = 2)
Notice here the use of the left join. Also notice because you already know the promIDs, we really don't need to join the Promo table (we could if we wanted or we needed to look up the promIDs by name or something). The query looks a little ugly, but should run great AND provide you data you're looking for.
2700 rows is nothing for a good database engine. MySQL won't have any problems with it. I have a couple MySQL tables with over 3 million rows and I have many tables with over 100,000 rows. The key is writing smart queries which run fast and get ONLY the data you need. The other trick is keeping your data normalized (which is sort of what we are doing versus trying to shove all the xref table data into an array which gets stored in a table column).
Maybe think about it this way:
Lets say you do decide to store the xref table data as an array in a table column (bad - but lets use it as an example).
For each record, you have a fixed width field. Its data is comma delimited. The array would go into a VARCHAR field. The max length is 255 characters. What do you do if you exceed the 255 characters? You're stuck. Also, you've chewed up 255 bytes.
Now you use the xref table approach and have 2 integer fields. Each integer field requires 4 bytes of storage, so 8 bytes will be used to associate 1 person to 1 promo. At 2700 rows in that xref table, you're using about 21,600 bytes of disk space (doesn't take into account space needed for indexing). Play with some of the numbers and you'll find 2700 isn't a scarey row count at all.