Sending email? Why wait? ======================== As it turns out, there are a couple of things that you need to do if you want to send emails using python to someone on, say, Gmail. 1) You need SPF setup 2) You need DKIM setup 3) You need DMARC setup You probably also need MX records setup, but that's a bit out of scope for what I want to say. SPF === SPF doesn't stand for Sun Protection Factor. It stands for something else. Sender Policy Framework or something. Anyway, that tells people who care who are allowed to send emails from your domain. This is a TXT record in your DNS with a value that looks something like this: v=spf1 include:_spf.google.com include:something.example.com ~all I don't remember what the bits are all precisely but basically that will say: - Your domain - Everywhere that _spf.google.com says is a valid sender - Everywhere that something.example.com says is a valid sender You can add some other bits & bobs, but those are a couple that really matter. DKIM ==== DKIM is how you sign your emails to verify that they came from your server. You stick the public key in your DNS and then you sign the email with your private key. I got some great information from this guy: https://russell.ballestrini.net/quickstart-to-dkim-sign-email-with-python/ You generate your keys like this: openssl genrsa -out example.com.pem 2048 (at least 2048 - Google requires it) openssl rsa -in example.com.pem -out example.com.pub -pubout You might choose to add some kind of other scheme to your files like: openssl rsa -out example.com.20100814.pem 2048 If you wanted. Now you need to extract the inner contents of your public key and stick that in another TXT record. You can get that using this handy one-liner: python3 -c 'print("v=DKIM1; k=rsa;", "".join("".join(open("example.com.pub").readlines()[1:-1]).split()))' I mean, it's pretty gross, but it does the job. And if you know Python it's pretty easy. You could do this instead: with open('example.com.pub') as f: print("v=DKIM1; k=rsa;", "".join("".join(f.readlines()[1:-1]).split())) If you'd rather have a Python file. But that seems excessive. Anyway, stick that in your DNS and smoke it. I mean, as a TXT record. The key could be kind of whatever you want. I used `20100814._domainkey`. The first part is made up, the `._domainkey` is important. I also use Namecheap, which magics in the `.example.com` for you, so all in all that becomes: 20100814._domainkey.example.com Which will be useful to know if you're `dig`ging for these records. DMARC ===== Once you've got SPF and DKIM setup you're pretty much home free. Now you just need your DMARC record which is, you guessed it, another TXT record. You get there by making something like: v=DMARC1; p=reject; rua=mailto:postmaster@example.com and then you should get some xml emails sent to you... every some period? `p` is what you want to do with your emails that fail DKIM or SPF. `reject` rejects them, `quarantine` will hold them... somewhere? And `none` just delivers the emails as spam intended. Or whatever. Once you've got all that setup then you can send your emails like this: import smtplib import dns.resolver import dkim from email.message import EmailMessage from email.utils import getaddresses msg = EmailMessage() msg.set_content('This is a test email - completed with DKIM!') msg['Subject'] = 'This is a testing email, python is so cool!' msg['From'] = 'wango@example.com' msg['To'] = 'srilyk@gmail.com, srilyk@example.com' msg['Cc'] = 'fnord@example.com' with open('example.com.pem', 'rb') as f: dkim_private_key = f.read() headers = [b'To', b'From', b'Subject'] sig = dkim.sign( bytes(msg), selector=b'20100814', # This has to match whatever comes before the `._domainkey` in your DNS domain=b'example.com', # Duh? This is your domain - matched against your IP & whatnot privkey=dkim_private_key, include_headers=headers, ) msg['DKIM-Signature'] = ''.join(sig.decode().lstrip('DKIM-Signature: ').split('\r\n')) recipients = list(addr[1] for addr in getaddresses(msg.get_all('to', []) + msg.get_all('cc', []) + msg.get_all('bcc', []))) domains = set(recipient.rsplit('@', 1)[-1] for recipient in recipients) for domain in domains: servers = list(sorted( (rdata for rdata in resolver.query(domain, 'MX')), key=lambda x: x.preference )) for server in servers: with smtplib.SMTP(str(server.exchange)) as smpt: smtp.ehlo('example.com') # Try TLS try: smtp.starttls() smtp.ehlo('example.com') # You're supposed to re-ehlo after starttls except smtplib.SMTPNotSupportedError: print('Failed to start TLS, sending email unencrypted') # or you could hard stop here by raising your own exception finally: smtp.send_message(msg, to_addrs=recipients) break All of this should let you send emails! If you get stuck, feel free to drop me a line at wango@wangofett.com... I'm sure I'll be replying with some code that looks like this :)