I also had a look at How do I deal with side effects in Event Sourcing? but the solution wasn't clear to me.
If I store "EmailSent" event in the event stream, I might issue the external request to send the email again, thinking the previous send timed out, moments before the confirmation of the first email being successfully sent arrives.
However, if I never store that, and instead have all sent email IDs persistently stored by the email service, I will never know to stop bothering the email service with my old requests to send a particular email, despite it answering "this email has been previously sent successfully" every time.
Should I do both? This way the system will stop bothering the email service a lot of the time, but when it accidentally bothers it multiple times, only one email will be sent. (Yes, the email service can never be transactional, and I still have to choose "at most once" or "at least once" but having its limited scope and data locality it can give practical results much closer to "once".)