Como enviar um email com Python?

170

Esse código funciona e me envia um email muito bem:

import smtplib
#SERVER = "localhost"

FROM = '[email protected]'

TO = ["[email protected]"] # 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()

No entanto, se eu tentar envolvê-lo em uma função como esta:

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 chamá-lo, recebo os seguintes erros:

 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

Alguém pode me ajudar a entender o porquê?

cloud311
fonte
2
como você chama a função?
Jochen Ritzel
O recuo que você postou é o mesmo do seu arquivo?
Gddc
@gddc não tenho a certeza de travessão corretamente, que é apenas a maneira que eu colei no.
cloud311
Eu chamo a função importando-a para o meu módulo principal e passando os parâmetros que eu defini nela.
precisa saber é o seguinte
4
Embora a sugestão da @ Arrieta de usar o pacote de email seja a melhor maneira de resolver isso, sua abordagem pode funcionar. As diferenças entre as duas versões estão na string: (1) como @NickODell aponta, você tem espaços em branco à esquerda na versão da função. Os cabeçalhos não devem ter espaço à esquerda (a menos que estejam agrupados). (2) a menos que TEXT inclua uma linha em branco à esquerda, você perdeu o separador entre cabeçalhos e corpo.
Tony Meyer

Respostas:

192

Eu recomendo que você use os pacotes padrão emaile smtplibjuntos para enviar email. Por favor, veja o exemplo a seguir (reproduzido da documentação do Python ). Observe que, se você seguir essa abordagem, a tarefa "simples" é realmente simples, e as tarefas mais complexas (como anexar objetos binários ou enviar mensagens de várias partes sem formatação / HTML) são realizadas muito 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()

Para enviar email para vários destinos, você também pode seguir o exemplo na documentação do 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()

Como você pode ver, o cabeçalho Todo MIMETextobjeto deve ser uma sequência que consiste em endereços de email separados por vírgulas. Por outro lado, o segundo argumento para a sendmailfunção deve ser uma lista de cadeias (cada cadeia é um endereço de email).

Então, se você tem três endereços de email: [email protected], [email protected]e [email protected], você pode fazer o seguinte (seções óbvias omitido):

to = ["[email protected]", "[email protected]", "[email protected]"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

a ",".join(to)peça cria uma única string fora da lista, separada por vírgulas.

A partir das suas perguntas, entendo que você não passou pelo tutorial do Python - é OBRIGATÓRIO se você quiser chegar a algum lugar no Python - a documentação é principalmente excelente para a biblioteca padrão.

Escualo
fonte
1
Obrigado, isso funciona muito bem de dentro de uma função; no entanto, como posso enviar para vários destinatários? Como msg [to] parece uma chave de dicionário, tentei separar a msg [to] com um ponto-e-vírgula, mas isso não parece funcionar.
cloud311
1
@ cloud311 exatamente como você está no seu código. Ela quer uma cadeia delimitada por vírgulas: ", ".join(["[email protected]", "[email protected]"])
Tim McNamara
3
Observe também que o cabeçalho Para: possui semântica diferente do destinatário do envelope. Por exemplo, você pode usar '"Tony Meyer" <[email protected]>' como um endereço no cabeçalho Para:, mas o destinatário do envelope deve ser apenas "[email protected]". Para criar um endereço 'nice' Para:, use email.utils.formataddr, como email.utils.formataddr ("Tony Meyer", "[email protected]").
Tony Meyer
2
Pequena melhoria: o arquivo deve ser aberto usando with: with open(textfile, 'rb') as fp:. O fechamento explícito pode ser eliminado, pois o withbloco fechará o arquivo mesmo que ocorra um erro dentro dele.
Jpmc26
1
Não é específico para esta resposta, mas ao conectar-se a qualquer servidor SMTP que você não controla, considere a possibilidade de que sejam inacessíveis, lentas, conexões rejeitadas ou qualquer outra coisa. Em termos de código, você receberá uma exceção, mas precisará encontrar uma maneira de tentar o envio um pouco mais tarde. Se você conversar com seu próprio sendmail / postfix, ele cuidará desse reenvio para você.
Ralph Bolton
67

Bem, você quer ter uma resposta atualizada e moderna.

Aqui está a minha resposta:

Quando preciso enviar e-mail em python, uso a API mailgun, que causa muitas dores de cabeça ao enviar e-mails resolvidos. Eles têm um maravilhoso aplicativo / API que permite enviar 10.000 e-mails por mês gratuitamente.

O envio de um email seria assim:

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": ["[email protected]", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

Você também pode acompanhar eventos e muito mais, consulte o guia de início rápido .

Espero que você ache isso útil!

Dennis Decoene
fonte
3
Este é realmente muito mais "desta data". Embora ele use uma API externa. Eu pessoalmente usaria algo como o yagmail para mantê-lo interno.
22816 PascalVKooten
6
@PascalvKooten É absolutamente divertido seguir sua publicidade constante para o yagmail (sim, senhor, considerarei isso da próxima vez, senhor;). Mas acho muito confuso que quase ninguém pareça se importar com o problema dos OPs, mas sugere soluções muito diferentes. É como se eu estou pedindo como mudar lâmpadas na minha 2,009 inteligente e a resposta é: Comprar um verdadeiro Mercedes ...
flaschbier
É ótimo que minha missão tenha algum valor divertido para alguns.
PascalVKooten
4
@flaschbier A razão pela qual ninguém se importa com a questão dos OPs é porque o título está errado. "Como enviar um email com Python?" é o motivo real pelo qual as pessoas procuram quando clicam nessa pergunta e esperam uma resposta que o yagmail possa fornecer: agradável e curta. Ai está. Mais propaganda do yagmail.
PascalVKooten
1
Só para dizer que, para os clientes Mailgun, o envio de e-mails por serviços da web é consideravelmente mais amigável à largura de banda do que via SMTP (especialmente se você estiver usando anexos).
Ralph Bolton
44

Gostaria de ajudá-lo a enviar e-mails, aconselhando o pacote yagmail (sou o mantenedor, desculpe a publicidade, mas acho que ela pode realmente ajudar!).

O código inteiro para você seria:

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

Observe que eu forneço padrões para todos os argumentos; por exemplo, se você deseja enviar para si mesmo, pode omitir TO; se não quiser um assunto, também pode omitir.

Além disso, o objetivo também é facilitar a anexação de códigos ou imagens html (e outros arquivos).

Onde você coloca o conteúdo, pode fazer algo como:

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']

Uau, como é fácil enviar anexos! Isso levaria 20 linhas sem o yagmail;)

Além disso, se você a configurar uma vez, nunca precisará digitar a senha novamente (e armazená-la com segurança). No seu caso, você pode fazer algo como:

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

o que é muito mais conciso!

Convido você a dar uma olhada no github ou instalá-lo diretamente com pip install yagmail.

PascalVKooten
fonte
4
essa deve ser a melhor solução.
Richard de Ree
1
OMG, esse é um módulo de e-mail simples e doce . Obrigado!
pepoluan
1
Posso usar yagmailoutro que não o gmail? Estou tentando usar para o meu próprio servidor SMTP.
Anirban Nag 'tintinmj'
@dtgq Obrigado pela sua preocupação. Pessoalmente, não vejo o vetor de ataque. Se alguém vai alterar o arquivo no caminho que você deseja enviar, não importa se você tem uma Attachmentclasse; ainda é a mesma coisa. Se eles puderem alterar o seu código, poderão fazer o que quiserem (com / sem raiz, é o mesmo envio de e-mail por escrito). Isso me parece o típico "é conveniente / mágico, portanto deve ser menos seguro". Estou curioso que ameaça real você vê?
PascalVKooten
14

Há um problema de recuo. O código abaixo funcionará:

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()

Zeeshan
fonte
3
@geotheory, a SERVERvariável que é passada para a função são as credenciais do usuário.
Utilizador
Não é recomendável reunir manualmente uma mensagem SMTP válida com interpolação simples de cadeia de caracteres e sua resposta possui um erro que ilustra isso perfeitamente. (O corpo precisa começar com uma linha vazia para que essa seja uma mensagem válida.) Para qualquer coisa que envolva conjuntos de caracteres que não sejam texto sem formatação nos anos 80 da década de 1980, somente em inglês US-ASCII (anexos, internacionalização e outro suporte MIME) em inglês de 7 bits realmente quer usar uma biblioteca que lida com essas coisas. A emailbiblioteca Python faz isso razoavelmente bem (principalmente desde a 3.6), embora ainda exija alguma compreensão do que você está fazendo.
tripleee 30/06
7

Aqui está um exemplo no Python 3.x, muito mais simples que 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='[email protected]'):
    # 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.')

chame esta função:

send_mail(to_email=['[email protected]', '[email protected]'],
          subject='hello', message='Your analysis has done!')

abaixo pode apenas para o usuário chinês:

Se você usar 126/163, 网易 邮箱, precisará definir "客户 端 授权 密码", como abaixo:

insira a descrição da imagem aqui

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

Belter
fonte
4

Ao recuar seu código na função (o que está ok), você também recuou as linhas da cadeia de mensagens brutas. Mas o espaço em branco à esquerda implica dobrar (concatenação) das linhas de cabeçalho, conforme descrito nas seções 2.2.3 e 3.2.3 da RFC 2822 - Internet Message Format :

Cada campo de cabeçalho é logicamente uma única linha de caracteres que compreende o nome do campo, os dois pontos e o corpo do campo. Por conveniência, porém, e para lidar com as limitações de 998/78 caracteres por linha, a parte do corpo do campo de um campo de cabeçalho pode ser dividida em uma representação de várias linhas; isso é chamado de "dobrável".

Na forma de função da sua sendmailchamada, todas as linhas começam com espaço em branco e, portanto, são "desdobradas" (concatenadas) e você está tentando enviar

From: [email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Além do que nossa mente sugere, smtplibnão entenderá mais os cabeçalhos To:e Subject:, porque esses nomes são reconhecidos apenas no início de uma linha. Em vez disso smtplib, assumirá um endereço de email do remetente muito longo:

[email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Isso não vai funcionar e a exceção é sua.

A solução é simples: basta preservar a messagestring como era antes. Isso pode ser feito por uma função (como Zeeshan sugeriu) ou imediatamente no código-fonte:

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()

Agora o desdobramento não ocorre e você envia

From: [email protected]
To: [email protected]
Subject: Hello!

This message was sent with Python's smtplib.

que é o que funciona e o que foi feito pelo seu código antigo.

Observe também que eu estava preservando a linha vazia entre os cabeçalhos e o corpo para acomodar a seção 3.5 da RFC (que é necessária) e colocar o include fora da função, de acordo com o guia de estilo Python PEP-0008 (que é opcional).

flaschbier
fonte
3

Provavelmente, está colocando abas na sua mensagem. Imprima a mensagem antes de passá-la para sendMail.

Nick ODell
fonte
1

Verifique se você concedeu permissão ao remetente e ao destinatário para enviar e-mail de fontes desconhecidas (fontes externas) na conta de 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)

insira a descrição da imagem aqui

Devanand Sharma
fonte
msgnão é uma mensagem SMTP válida e simplesmente parecerá desaparecer no éter se o seu servidor de email aceitar.
tripleee 30/06
0

Pensei em colocar meus dois bits aqui desde que acabei de descobrir como isso funciona.

Parece que você não tem a porta especificada nas configurações de conexão do SERVIDOR, isso me afetou um pouco quando eu estava tentando conectar-me ao meu servidor SMTP que não está usando a porta padrão: 25.

De acordo com os documentos smtplib.SMTP, sua solicitação / resposta ehlo ou helo deve ser atendida automaticamente, para que você não precise se preocupar com isso (mas pode ser algo para confirmar se tudo falhar).

Outra coisa a se perguntar é que você permitiu conexões SMTP no próprio servidor SMTP? Para alguns sites como GMAIL e ZOHO, você precisa realmente ativar e ativar as conexões IMAP na conta de email. Seu servidor de email pode não permitir conexões SMTP que não provêm de 'localhost', talvez? Algo para investigar.

A última coisa é que você pode tentar iniciar a conexão no TLS. A maioria dos servidores agora exige esse tipo de autenticação.

Você verá que eu coloquei dois campos TO no meu e-mail. Os itens de dicionário msg ['TO'] e msg ['FROM'] msg permitem que as informações corretas sejam exibidas nos cabeçalhos do próprio email, que são vistas na extremidade de recebimento do email nos campos Para / De (você pode até ser capaz de adicionar um campo Responder a aqui.Os campos TO e FROM são o que o servidor exige.Eu sei que ouvi alguns servidores de email rejeitando emails se eles não tiverem os cabeçalhos de email adequados.

Este é o código que eu usei, em uma função, que funciona para eu enviar por e-mail o conteúdo de um arquivo * .txt usando meu computador local e um servidor SMTP remoto (ZOHO, como mostrado):

def emailResults(folder, filename):

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

    # headers
    TO = '[email protected]'
    msg['To'] = TO
    FROM = '[email protected]'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('[email protected]', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()
ntk4
fonte
0

Vale ressaltar que o módulo SMTP suporta o gerenciador de contexto, portanto, não há necessidade de chamar manualmente quit (), isso garantirá que ele sempre seja chamado, mesmo que haja uma exceção.

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

No que diz respeito ao seu código, não parece haver nada de fundamentalmente errado, exceto que não está claro como você está realmente chamando essa função. Tudo o que consigo pensar é que, quando o servidor não está respondendo, você receberá esse erro SMTPServerDisconnected. Se você procurar a função getreply () no smtplib (trecho abaixo), terá uma ideia.

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.
    """

verifique um exemplo em https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py que também usa uma chamada de função para enviar um email, se é isso que você está tentando fazer (abordagem DRY).

rreddy
fonte