Come inviare un'e-mail con Python?


170

Questo codice funziona e mi invia un'e-mail bene:

import smtplib
#SERVER = "localhost"

FROM = 'monty@python.com'

TO = ["jon@mycompany.com"] # must be a list

SUBJECT = "Hello!"

TEXT = "This message was sent with Python's smtplib."

# Prepare actual message

message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)

# Send the mail

server = smtplib.SMTP('myserver')
server.sendmail(FROM, TO, message)
server.quit()

Tuttavia, se provo ad avvolgerlo in una funzione come questa:

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = """\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

e chiamalo ottengo i seguenti errori:

 Traceback (most recent call last):
  File "C:/Python31/mailtest1.py", line 8, in <module>
    sendmail.sendMail(sender,recipients,subject,body,server)
  File "C:/Python31\sendmail.py", line 13, in sendMail
    server.sendmail(FROM, TO, message)
  File "C:\Python31\lib\smtplib.py", line 720, in sendmail
    self.rset()
  File "C:\Python31\lib\smtplib.py", line 444, in rset
    return self.docmd("rset")
  File "C:\Python31\lib\smtplib.py", line 368, in docmd
    return self.getreply()
  File "C:\Python31\lib\smtplib.py", line 345, in getreply
    raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed

Qualcuno può aiutarmi a capire perché?


2
come si chiama la funzione?
Jochen Ritzel,

Il rientro che hai pubblicato è lo stesso che hai nel tuo file?
gddc,

@gddc no Mi sono assicurato di rientrare correttamente, è così che l'ho incollato.
cloud311

Chiamo la funzione importandola nel mio modulo principale e passando i parametri che ho definito in essa.
cloud311

4
Sebbene il suggerimento di @ Arrieta di utilizzare il pacchetto e-mail sia il modo migliore per risolverlo, il tuo approccio può funzionare. Le differenze tra le tue due versioni sono nella stringa: (1) come sottolinea @NickODell, hai uno spazio bianco nella versione della funzione. Le intestazioni non devono avere spazi iniziali (a meno che non siano avvolte). (2) a meno che TEXT non includa una riga vuota iniziale, hai perso il separatore tra intestazioni e corpo.
Tony Meyer,

Risposte:


192

Vi consiglio di utilizzare i pacchetti standard emaile smtplibinsieme per inviare e-mail. Guarda il seguente esempio (riprodotto dalla documentazione di Python ). Si noti che se si segue questo approccio, l'attività "semplice" è effettivamente semplice e le attività più complesse (come il collegamento di oggetti binari o l'invio di messaggi multipart HTML / semplici) vengono eseguite molto rapidamente.

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.mime.text import MIMEText

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
with open(textfile, 'rb') as fp:
    # Create a text/plain message
    msg = MIMEText(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()

Per inviare e-mail a più destinazioni, puoi anche seguire l'esempio nella documentazione di Python :

# Import smtplib for the actual sending function
import smtplib

# Here are the email package modules we'll need
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = 'Our family reunion'
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion'

# Assume we know that the image files are all in PNG format
for file in pngfiles:
    # Open the files in binary mode.  Let the MIMEImage class automatically
    # guess the specific image type.
    with open(file, 'rb') as fp:
        img = MIMEImage(fp.read())
    msg.attach(img)

# Send the email via our own SMTP server.
s = smtplib.SMTP('localhost')
s.sendmail(me, family, msg.as_string())
s.quit()

Come si può vedere, l'intestazione Toin MIMETextoggetto deve essere una stringa composta da indirizzi e-mail separati da virgole. D'altra parte, il secondo argomento della sendmailfunzione deve essere un elenco di stringhe (ogni stringa è un indirizzo e-mail).

Quindi, se si dispone di tre indirizzi e-mail: person1@example.com, person2@example.com, e person3@example.com, si può fare come segue (sezioni ovvie omessi):

to = ["person1@example.com", "person2@example.com", "person3@example.com"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

la ",".join(to)parte crea una singola stringa dall'elenco, separata da virgole.

Dalle vostre domande ho capito che non avete passato il tutorial di Python - è DEVE se volete arrivare ovunque in Python - la documentazione è per lo più eccellente per la libreria standard.


1
Grazie, funziona perfettamente all'interno di una funzione, tuttavia come posso inviare a più destinatari? Dato che msg [to] sembra una chiave di dizionario, ho provato a separare msg [to] con un punto e virgola ma non sembra funzionare.
cloud311

1
@ cloud311 esattamente come lo hai nel tuo codice. Vuole una stringa delimitata da virgole: ", ".join(["a@example.com", "b@example.net"])
Tim McNamara,

3
Si noti inoltre che l'intestazione A: ha una semantica diversa rispetto al destinatario della busta. Ad esempio, è possibile utilizzare "Tony Meyer" <tony.meyer@gmail.com> "come indirizzo nell'intestazione To: ma il destinatario della busta deve essere solo" tony.meyer@gmail.com ". Per creare un indirizzo "bello" per: usa email.utils.formataddr, come email.utils.formataddr ("Tony Meyer", "tony.meyer@gmail.com").
Tony Meyer,

2
Piccolo miglioramento: il file deve essere aperto utilizzando with: with open(textfile, 'rb') as fp:. La chiusura esplicita può essere eliminata, poiché il withblocco chiuderà il file anche se si verifica un errore al suo interno.
jpmc26,

1
Non specifico per questa risposta, ma quando ci si connette a qualsiasi server SMTP che non si controlla, è necessario considerare la possibilità che sia irraggiungibile, lento, rifiutando connessioni o quant'altro. Per quanto riguarda il codice otterrai un'eccezione, ma dovrai quindi trovare un modo per riprovare a inviare un po 'più tardi. Se parli con il tuo sendmail / postfix, allora si occuperà di quel rinvio per te.
Ralph Bolton,

67

Bene, vuoi avere una risposta moderna e aggiornata.

Ecco la mia risposta:

Quando ho bisogno di inviare e-mail in Python, utilizzo l' API Mailgun che mi dà molto fastidio con l'invio di messaggi ordinati. Hanno un'app / api meravigliosa che ti consente di inviare 10.000 e-mail al mese gratuitamente.

L'invio di un'e-mail sarebbe come questo:

def send_simple_message():
    return requests.post(
        "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
        auth=("api", "YOUR_API_KEY"),
        data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
              "to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

Puoi anche tenere traccia degli eventi e molto altro, consulta la guida rapida .

Spero che lo trovi utile!


3
Questo è davvero molto di più "di questa data". Sebbene utilizzi un'API esterna. Personalmente userei qualcosa come yagmail per mantenerlo interno.
PascalVKooten,

6
@PascalvKooten Assolutamente divertente seguire la tua costante pubblicità per yagmail (sì, signore, lo prenderò in considerazione la prossima volta, signore;). Ma trovo molto confuso che quasi nessuno sembra occuparsi del problema dei PO, ma suggerisce piuttosto soluzioni molto diverse. È come se chiedessi come cambiare le lampadine nella mia smart del 2009 e la risposta è: compra una vera Mercedes ...
flaschbier,

Fantastico che la mia missione abbia un certo valore di divertimento per alcuni.
PascalVKooten,

4
@flaschbier Il motivo per cui a nessuno importa del problema dei PO è perché il titolo è sbagliato. "Come inviare un'e-mail con Python?" è la vera ragione per cui le persone vengono a cercare quando fanno clic su quella domanda e si aspettano una risposta che yagmail può fornire: bella e breve. Ecco qua Altre pubblicità su yagmail.
PascalVKooten,

1
Solo per dire che per i clienti Mailgun, l'invio di posta tramite i servizi web è notevolmente più adatto alla larghezza di banda rispetto a via SMTP (soprattutto se si utilizzano allegati).
Ralph Bolton,

44

Vorrei aiutarti con l'invio di e-mail consigliando il pacchetto yagmail (sono il manutentore, mi dispiace per la pubblicità, ma penso che possa davvero aiutare!).

L'intero codice per te sarebbe:

import yagmail
yag = yagmail.SMTP(FROM, 'pass')
yag.send(TO, SUBJECT, TEXT)

Si noti che fornisco i valori predefiniti per tutti gli argomenti, ad esempio se si desidera inviare a te stesso, è possibile omettere TO, se non si desidera un argomento, è possibile ometterlo anche.

Inoltre, l'obiettivo è anche quello di rendere davvero semplice allegare codice HTML o immagini (e altri file).

Dove metti contenuti puoi fare qualcosa del tipo:

contents = ['Body text, and here is an embedded image:', 'http://somedomain/image.png',
            'You can also find an audio file attached.', '/local/path/song.mp3']

Caspita, quanto è facile inviare allegati! Ciò richiederebbe 20 righe senza yagmail;)

Inoltre, se lo configuri una volta, non dovrai mai più inserire la password (e conservarla in modo sicuro). Nel tuo caso puoi fare qualcosa del tipo:

import yagmail
yagmail.SMTP().send(contents = contents)

che è molto più conciso!

Ti inviterei a dare un'occhiata a Github o installarlo direttamente con pip install yagmail.


4
questa dovrebbe essere la soluzione migliore.
Richard de Ree,

1
OMG, questo è un modulo di posta elettronica dolce e SEMPLICE . Grazie!
pepoluan,

1
Posso usare yagmailaltro che Gmail? Sto cercando di utilizzare per il mio server SMTP.
Anirban Nag 'tintinmj'

@dtgq Grazie per la tua preoccupazione. Personalmente, non vedo il vettore di attacco. Se qualcuno sta per modificare il file nel percorso che si desidera inviare, non importa se si dispone di una Attachmentclasse; è sempre la stessa cosa. Se possono cambiare il tuo codice, possono comunque fare tutto ciò che vogliono (con / senza root, è lo stesso invio di email wrt). Questo mi sembra il tipico "è conveniente / magico, quindi deve essere meno sicuro". Sono curioso di sapere quale vera minaccia vedi?
PascalVKooten,

14

C'è un problema di rientro. Il codice seguente funzionerà:

import textwrap

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = textwrap.dedent("""\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT))
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()


3
@geotheory la SERVERvariabile che viene passata nella funzione sono le credenziali dell'utente.
Utente

Non è consigliabile mettere insieme manualmente un messaggio SMTP valido con una semplice interpolazione di stringhe e la risposta ha un bug che lo illustra perfettamente. (Il corpo deve iniziare con una riga vuota perché questo sia un messaggio valido.) Per qualsiasi cosa che coinvolga set di caratteri diversi dal testo normale negli Stati Uniti-ASCII a 7 bit solo inglese degli anni '80 (allegati, internazionalizzazione e altro supporto MIME) voglio davvero usare una libreria che gestisce queste cose. La emaillibreria Python lo fa ragionevolmente bene (in particolare dalla 3.6) anche se richiede ancora una certa comprensione di ciò che stai facendo.
tripleee

7

Ecco un esempio su Python 3.x, molto più semplice di 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='xx@example.com'):
    # import smtplib
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = ', '.join(to_email)
    msg.set_content(message)
    print(msg)
    server = smtplib.SMTP(server)
    server.set_debuglevel(1)
    server.login(from_email, 'password')  # user & password
    server.send_message(msg)
    server.quit()
    print('successfully sent the mail.')

chiama questa funzione:

send_mail(to_email=['12345@qq.com', '12345@126.com'],
          subject='hello', message='Your analysis has done!')

sotto può solo per l'utente cinese:

Se usi 126/163, 网易 邮箱, devi impostare "客户 端 授权 密码", come di seguito:

inserisci qui la descrizione dell'immagine

rif: https://stackoverflow.com/a/41470149/2803344 https://docs.python.org/3/library/email.examples.html#email-examples


4

Mentre rientri il codice nella funzione (che è ok), hai anche indentato le righe della stringa di messaggio non elaborata. Ma lo spazio bianco iniziale implica la piegatura (concatenazione) delle linee di intestazione, come descritto nelle sezioni 2.2.3 e 3.2.3 di RFC 2822 - Formato dei messaggi Internet :

Ogni campo di intestazione è logicamente una singola riga di caratteri che comprende il nome del campo, i due punti e il corpo del campo. Per comodità, tuttavia, e per gestire i limiti di carattere 998/78 per riga, la parte del corpo di campo di un campo di intestazione può essere suddivisa in una rappresentazione a più righe; questo si chiama "pieghevole".

Nel modulo di funzione della sendmailchiamata, tutte le linee iniziano con uno spazio bianco e quindi vengono "aperte" (concatenate) e si sta tentando di inviare

From: monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

A parte ciò che la nostra mente suggerisce, smtplibnon capirà più le intestazioni To:e Subject:, poiché questi nomi sono riconosciuti solo all'inizio di una riga. Invece smtplibassumerà un indirizzo e-mail del mittente molto lungo:

monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

Questo non funzionerà e quindi arriverà la tua eccezione.

La soluzione è semplice: basta conservare la messagestringa com'era prima. Questo può essere fatto da una funzione (come suggerito da Zeeshan) o subito nel codice sorgente:

import smtplib

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    """this is some test documentation in the function"""
    message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

Ora lo svolgersi non si verifica e si invia

From: monty@python.com
To: jon@mycompany.com
Subject: Hello!

This message was sent with Python's smtplib.

che è ciò che funziona e ciò che è stato fatto dal tuo vecchio codice.

Nota che stavo anche preservando la linea vuota tra intestazioni e corpo per adattarsi alla sezione 3.5 dell'RFC (che è richiesta) e ho inserito l'inclusione all'esterno della funzione secondo la guida di stile Python PEP-0008 (che è facoltativa).


3

Probabilmente sta mettendo schede nel tuo messaggio. Stampa il messaggio prima di passarlo a sendMail.


1

Assicurati di avere autorizzato sia il mittente che il destinatario a inviare e-mail e ricevere e-mail da fonti sconosciute (fonti esterne) nell'account e-mail.

import smtplib

#Ports 465 and 587 are intended for email client to email server communication - sending email
server = smtplib.SMTP('smtp.gmail.com', 587)

#starttls() is a way to take an existing insecure connection and upgrade it to a secure connection using SSL/TLS.
server.starttls()

#Next, log in to the server
server.login("#email", "#password")

msg = "Hello! This Message was sent by the help of Python"

#Send the mail
server.sendmail("#Sender", "#Reciever", msg)

inserisci qui la descrizione dell'immagine


msgnon è un messaggio SMTP valido e sembrerà semplicemente scomparire nell'etere se il tuo server di posta lo accetta.
tripleee

0

Ho pensato di inserire qui i miei due pezzi da quando ho appena capito come funziona.

Sembra che non hai la porta specificata nelle impostazioni di connessione del SERVER, questo mi ha influenzato un po 'quando stavo cercando di connettermi al mio server SMTP che non utilizza la porta predefinita: 25.

Secondo i documenti smtplib.SMTP, la tua richiesta / risposta ehlo o helo dovrebbe essere gestita automaticamente, quindi non dovresti preoccuparti di questo (ma potrebbe essere qualcosa da confermare se tutto il resto fallisce).

Un'altra cosa da chiedersi è che hai autorizzato le connessioni SMTP sul tuo stesso server SMTP? Per alcuni siti come GMAIL e ZOHO devi effettivamente accedere e attivare le connessioni IMAP all'interno dell'account e-mail. Il tuo server di posta potrebbe non consentire connessioni SMTP che non provengono da 'localhost' forse? Qualcosa da esaminare.

L'ultima cosa è che potresti voler provare ad avviare la connessione su TLS. La maggior parte dei server ora richiede questo tipo di autenticazione.

Vedrai che ho bloccato due campi TO nella mia e-mail. Le voci del dizionario msg ['TO'] e msg ['FROM'] msg consentono di visualizzare le informazioni corrette nelle intestazioni dell'email stessa, che si vede alla fine della ricezione dell'email nei campi Da / Da (si potrebbe persino essere in grado di aggiungere un campo Rispondi a qui. I campi TO e FROM stessi sono ciò che richiede il server. So di aver sentito parlare di alcuni server di posta elettronica che rifiutano le e-mail se non dispongono delle intestazioni di posta elettronica appropriate.

Questo è il codice che ho usato, in una funzione, che funziona per me per e-mail il contenuto di un file * .txt utilizzando il mio computer locale e un server SMTP remoto (ZOHO come mostrato):

def emailResults(folder, filename):

    # body of the message
    doc = folder + filename + '.txt'
    with open(doc, 'r') as readText:
        msg = MIMEText(readText.read())

    # headers
    TO = 'to_user@domain.com'
    msg['To'] = TO
    FROM = 'from_user@domain.com'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('from_user@domain.com', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()

0

Vale la pena notare che il modulo SMTP supporta il gestore del contesto, quindi non è necessario chiamare manualmente quit (), questo garantirà che venga sempre chiamato anche se c'è un'eccezione.

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)

-1

Per quanto riguarda il tuo codice, non sembra esserci nulla di fondamentalmente sbagliato in esso, tranne questo, non è chiaro come stai effettivamente chiamando quella funzione. Tutto quello che posso pensare è che quando il tuo server non risponde, otterrai questo errore SMTPServerDisconnected. Se cerchi la funzione getreply () in smtplib (estratto di seguito), otterrai un'idea.

def getreply(self):
    """Get a reply from the server.

    Returns a tuple consisting of:

      - server response code (e.g. '250', or such, if all goes well)
        Note: returns -1 if it can't read response code.

      - server response string corresponding to response code (multiline
        responses are converted to a single, multiline string).

    Raises SMTPServerDisconnected if end-of-file is reached.
    """

controlla un esempio su https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py che utilizza anche una chiamata di funzione per inviare un'e-mail, se è quello che stai cercando di fare (approccio DRY).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.