Làm thế nào để gửi email với Python?


169

Mã này hoạt động và gửi cho tôi một email tốt:

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

Tuy nhiên nếu tôi cố gắng bọc nó trong một chức năng như thế này:

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

và gọi nó là tôi nhận được các lỗi sau:

 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

Bất cứ ai có thể giúp tôi hiểu tại sao?


2
Làm thế nào để bạn gọi chức năng?
Jochen Ritzel

Là thụt lề bạn đã đăng giống như bạn có trong tập tin của bạn?
gddc

@gddc không Tôi chắc chắn đã thụt lề đúng cách, đó chỉ là cách tôi dán nó vào.
cloud311

Tôi gọi hàm bằng cách nhập nó vào mô-đun chính của mình và truyền các tham số mà tôi đã xác định vào nó.
đám mây311

4
Mặc dù đề xuất sử dụng gói email của @ Arrieta là cách tốt nhất để giải quyết vấn đề này, cách tiếp cận của bạn có thể hiệu quả. Sự khác biệt giữa hai phiên bản của bạn nằm trong chuỗi: (1) như @NickODell chỉ ra, bạn có khoảng trắng hàng đầu trong phiên bản hàm. Các tiêu đề không nên có không gian hàng đầu (trừ khi chúng được bọc). (2) trừ khi văn bản bao gồm một dòng trống hàng đầu, bạn đã mất dấu phân cách giữa các tiêu đề và nội dung.
Tony Meyer

Câu trả lời:


191

Tôi khuyên bạn nên sử dụng các gói tiêu chuẩn emailsmtplibcùng nhau gửi email. Vui lòng xem ví dụ sau (được sao chép từ tài liệu Python ). Lưu ý rằng nếu bạn làm theo cách tiếp cận này, thì tác vụ "đơn giản" thực sự đơn giản và các tác vụ phức tạp hơn (như đính kèm các đối tượng nhị phân hoặc gửi tin nhắn nhiều phần đơn giản / HTML) được thực hiện rất nhanh.

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

Để gửi email đến nhiều đích, bạn cũng có thể làm theo ví dụ trong tài liệu 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()

Như bạn có thể thấy, tiêu đề Totrong MIMETextđối tượng phải là một chuỗi bao gồm các địa chỉ email được phân tách bằng dấu phẩy. Mặt khác, đối số thứ hai cho sendmailhàm phải là một danh sách các chuỗi (mỗi chuỗi là một địa chỉ email).

Vì vậy, nếu bạn có ba địa chỉ email: person1@example.com, person2@example.com, và person3@example.com, bạn có thể làm như sau (phần rõ ràng bỏ qua):

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

các ",".join(to)phần tạo ra một chuỗi duy nhất ra khỏi danh sách, cách nhau bằng dấu phẩy.

Từ những câu hỏi của bạn tôi thu thập được rằng bạn chưa trải qua hướng dẫn Python - đó là PHẢI nếu bạn muốn đến bất cứ nơi nào trong Python - tài liệu chủ yếu là tuyệt vời cho thư viện chuẩn.


1
Cảm ơn bạn điều này hoạt động rất độc đáo từ bên trong một chức năng, tuy nhiên làm thế nào tôi có thể gửi cho nhiều người nhận? Vì tin nhắn [đến] trông giống như một khóa từ điển, tôi đã thử tách thông điệp [đến] bằng dấu chấm phẩy nhưng có vẻ như nó không hoạt động.
đám mây311

1
@ cloud311 chính xác như bạn có nó trong mã của bạn. Nó muốn một chuỗi được phân cách bằng dấu phẩy: ", ".join(["a@example.com", "b@example.net"])
Tim McNamara

3
Cũng lưu ý rằng tiêu đề To: có ngữ nghĩa khác với người nhận phong bì. Ví dụ: bạn có thể sử dụng '"Tony Meyer" <tony.meyer @ gmail>' làm địa chỉ trong tiêu đề To: nhưng người nhận phong bì phải chỉ là "tony.meyer @ gmail". Để tạo địa chỉ 'đẹp' thành:, hãy sử dụng email.utils.formataddr, như email.utils.formataddr ("Tony Meyer", "tony.meyer @ gmail").
Tony Meyer

1
Cải thiện nhỏ: tập tin nên được mở bằng with: with open(textfile, 'rb') as fp:. Đóng rõ ràng có thể được loại bỏ, vì withkhối sẽ đóng tệp ngay cả khi có lỗi xảy ra bên trong nó.
jpmc26

1
Không cụ thể cho câu trả lời này, nhưng khi kết nối với bất kỳ máy chủ SMTP nào mà bạn không kiểm soát, bạn nên xem xét khả năng nó không thể truy cập được, chậm, từ chối các kết nối hoặc bất cứ điều gì khác. Thông thái mã bạn sẽ có một ngoại lệ, nhưng sau đó bạn cần tìm cách thử lại việc gửi một chút sau đó. Nếu bạn nói chuyện với sendmail / postfix của riêng bạn, thì nó sẽ đảm nhiệm việc gửi lại cho bạn.
Ralph Bolton

66

Vâng, bạn muốn có một câu trả lời cập nhật và hiện đại.

Đây là câu trả lời của tôi:

Khi tôi cần gửi thư bằng python, tôi sử dụng API mailgun sẽ khiến bạn rất đau đầu với việc gửi thư được sắp xếp. Họ có một ứng dụng / api tuyệt vời cho phép bạn gửi 10.000 email mỗi tháng miễn phí.

Gửi một email sẽ như thế này:

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!"})

Bạn cũng có thể theo dõi các sự kiện và nhiều hơn nữa, xem hướng dẫn bắt đầu nhanh .

Tôi hy vọng bạn thấy nó hữu dụng!


3
Đây thực sự là nhiều hơn "của ngày này". Mặc dù nó sử dụng API bên ngoài. Cá nhân tôi sẽ sử dụng một cái gì đó như yagmail để giữ cho nội bộ.
PascalVKooten

6
@PascalvKooten Hoàn toàn thú vị khi theo dõi quảng cáo liên tục của bạn cho yagmail (vâng, thưa ông, tôi sẽ xem xét nó vào lần tới, thưa ông;). Nhưng tôi thấy rất khó hiểu rằng hầu như không ai quan tâm đến vấn đề của OP, mà là gợi ý nhiều giải pháp khác nhau. Như thể tôi đang hỏi làm thế nào để thay đổi bóng đèn trong chiếc thông minh năm 2009 của mình và câu trả lời là: Mua một chiếc Mercedes thực sự ...
flaschbier

Thật tuyệt khi nhiệm vụ của tôi có một số giá trị giải trí cho một số.
PascalVKooten

4
@flaschbier Lý do không ai quan tâm đến vấn đề OP là vì tiêu đề sai. "Làm thế nào để gửi email với Python?" là lý do thực tế mà mọi người tìm đến khi họ nhấp vào câu hỏi đó và họ mong đợi một câu trả lời mà yagmail có thể cung cấp: hay và ngắn. Có bạn đi. Thêm quảng cáo yagmail.
PascalVKooten

1
Chỉ cần nói rằng đối với khách hàng của Mailgun, gửi thư bằng dịch vụ web thân thiện với băng thông hơn đáng kể so với qua SMTP (đặc biệt là nếu sử dụng bất kỳ tệp đính kèm nào).
Ralph Bolton

44

Tôi muốn giúp bạn gửi email bằng cách tư vấn gói yagmail (Tôi là người duy trì, xin lỗi vì quảng cáo, nhưng tôi cảm thấy nó thực sự có thể giúp!).

Toàn bộ mã cho bạn sẽ là:

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

Lưu ý rằng tôi cung cấp mặc định cho tất cả các đối số, ví dụ: nếu bạn muốn gửi cho chính mình, bạn có thể bỏ qua TO, nếu bạn không muốn một chủ đề, bạn cũng có thể bỏ qua nó.

Hơn nữa, mục tiêu cũng là làm cho nó thực sự dễ dàng để đính kèm mã html hoặc hình ảnh (và các tệp khác).

Nơi bạn đặt nội dung bạn có thể làm một cái gì đó như:

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

Wow, thật dễ dàng để gửi tệp đính kèm! Điều này sẽ mất 20 dòng mà không có yagmail;)

Ngoài ra, nếu bạn thiết lập nó một lần, bạn sẽ không bao giờ phải nhập lại mật khẩu (và được lưu trữ an toàn). Trong trường hợp của bạn, bạn có thể làm một cái gì đó như:

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

mà ngắn gọn hơn nhiều!

Tôi muốn mời bạn xem qua github hoặc cài đặt trực tiếp pip install yagmail.


4
đây nên là giải pháp hàng đầu
Richard de Ree

1
OMG, đó là một mô-đun email SIMPLE ngọt ngào . Cảm ơn!
pepoluan

1
Tôi có thể sử dụng yagmailkhác ngoài gmail không? Tôi đang cố gắng sử dụng cho máy chủ SMTP của riêng tôi.
Anirban Nag 'tintinmj'

@dtgq Cảm ơn bạn đã quan tâm. Cá nhân, tôi không thấy vector tấn công. Nếu ai đó sẽ thay đổi tệp theo đường dẫn bạn muốn gửi, thì việc bạn có một Attachmentlớp học không thành vấn đề ; nó vẫn như vậy Nếu họ có thể thay đổi mã của bạn, thì dù sao họ cũng có thể làm bất cứ điều gì họ muốn (có / không có root, đó là cách gửi email tương tự). Điều này đối với tôi giống như "nó thuận tiện / huyền diệu nên nó phải kém an toàn hơn". Tôi tò mò không biết mối đe dọa thực sự nào?
PascalVKooten

14

Có vấn đề thụt đầu dòng. Mã dưới đây sẽ hoạt động:

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 SERVERbiến được truyền vào hàm là thông tin đăng nhập của người dùng.
Người dùng

Không nên ghép thủ công một thông điệp SMTP hợp lệ với phép nội suy chuỗi đơn giản và câu trả lời của bạn có một lỗi minh họa điều này một cách hoàn hảo. (Phần thân cần phải bắt đầu bằng một dòng trống để đây là một thông điệp hợp lệ.) Đối với mọi nội dung liên quan đến các bộ ký tự khác với văn bản thuần túy trong những năm 1980 chỉ có 7-bit US-ASCII (đính kèm, quốc tế hóa và hỗ trợ MIME khác) thực sự muốn sử dụng một thư viện xử lý công cụ này. emailThư viện Python thực hiện điều này một cách hợp lý (đặc biệt là từ 3.6) mặc dù nó vẫn đòi hỏi một số hiểu biết về những gì bạn đang làm.
tripleee

7

Đây là một ví dụ về Python 3.x, đơn giản hơn nhiều so với 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.')

gọi hàm này:

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

dưới đây chỉ có thể cho người dùng Trung Quốc:

Nếu bạn sử dụng 126/163, 网易, bạn cần đặt "端 授权", như dưới đây:

nhập mô tả hình ảnh ở đây

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


4

Trong khi thụt mã của bạn trong hàm (không sao), bạn cũng đã thụt dòng của chuỗi thông báo thô. Nhưng khoảng trắng hàng đầu ngụ ý việc gấp (nối) các dòng tiêu đề, như được mô tả trong phần 2.2.3 và 3.2.3 của RFC 2822 - Định dạng Thông điệp Internet :

Mỗi trường tiêu đề là một dòng ký tự duy nhất bao gồm tên trường, dấu hai chấm và thân trường. Tuy nhiên, để thuận tiện và để giải quyết các giới hạn ký tự 998/78 trên mỗi dòng, phần thân trường của trường tiêu đề có thể được chia thành một biểu diễn nhiều dòng; cái này được gọi là "gấp".

Trong dạng chức năng của sendmailcuộc gọi của bạn , tất cả các dòng bắt đầu bằng khoảng trắng và do đó được "mở ra" (nối) và bạn đang cố gắng gửi

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

Khác với suy nghĩ của chúng tôi, smtplibsẽ không hiểu To:Subject:tiêu đề nữa, bởi vì những tên này chỉ được nhận ra ở đầu một dòng. Thay vào đó smtplibsẽ giả sử một địa chỉ email người gửi rất dài:

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

Điều này sẽ không hoạt động và ngoại lệ của bạn cũng vậy.

Giải pháp rất đơn giản: Chỉ cần bảo tồn messagechuỗi như trước đây. Điều này có thể được thực hiện bởi một chức năng (như Zeeshan đề xuất) hoặc ngay trong mã nguồn:

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

Bây giờ việc mở ra không xảy ra và bạn gửi

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

This message was sent with Python's smtplib.

đó là những gì hoạt động và những gì đã được thực hiện bởi mã cũ của bạn.

Lưu ý rằng tôi cũng đang duy trì dòng trống giữa các tiêu đề và phần thân để phù hợp với phần 3.5 của RFC (bắt buộc) và đặt bao gồm bên ngoài chức năng theo hướng dẫn kiểu Python PEP-0008 (tùy chọn).


3

Có lẽ nó đặt các tab vào tin nhắn của bạn. In ra tin nhắn trước khi bạn chuyển nó đến sendMail.


1

Đảm bảo rằng bạn đã cấp quyền cho cả Người gửi và Người nhận gửi email và nhận email từ các nguồn không xác định (Nguồn bên ngoài) trong Tài khoản email.

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)

nhập mô tả hình ảnh ở đây


msgkhông phải là một tin nhắn SMTP hợp lệ và đơn giản là sẽ biến mất trong ether nếu máy chủ thư của bạn chấp nhận nó.
tripleee

0

Tôi nghĩ rằng tôi đã đặt hai bit của mình ở đây vì tôi vừa tìm ra cách thức hoạt động của nó.

Có vẻ như bạn không có cổng được chỉ định trong cài đặt kết nối SERVER của mình, điều này ảnh hưởng đến tôi một chút khi tôi đang cố gắng kết nối với máy chủ SMTP không sử dụng cổng mặc định: 25.

Theo tài liệu smtplib.SMTP, yêu cầu / phản hồi về ehlo hoặc helo của bạn sẽ tự động được quan tâm, vì vậy bạn không cần phải lo lắng về điều này (nhưng có thể là điều cần xác nhận nếu tất cả đều thất bại).

Một điều khác để tự hỏi mình là bạn đã cho phép kết nối SMTP trên chính máy chủ SMTP của mình chưa? Đối với một số trang web như GMAIL và ZOHO, bạn phải thực sự truy cập và kích hoạt các kết nối IMAP trong tài khoản email. Máy chủ thư của bạn có thể không cho phép các kết nối SMTP không đến từ 'localhost'? Một cái gì đó để xem xét.

Điều cuối cùng là bạn có thể muốn thử và bắt đầu kết nối trên TLS. Hầu hết các máy chủ hiện nay yêu cầu loại xác thực này.

Bạn sẽ thấy tôi đã kẹt hai trường TO vào email của mình. Các mục từ điển của tin nhắn ['TO'] và tin nhắn ['TỪ'] cho phép thông tin chính xác hiển thị trong các tiêu đề của chính email, cái mà người ta nhìn thấy ở đầu nhận email trong trường Đến / Từ (bạn thậm chí có thể thêm trường Trả lời vào đây. Các trường TO và TỪ chính là những gì máy chủ yêu cầu. Tôi biết rằng tôi đã nghe nói về một số máy chủ email từ chối email nếu họ không có tiêu đề email thích hợp.

Đây là mã tôi đã sử dụng, trong một chức năng, hoạt động để tôi gửi email nội dung của tệp * .txt bằng máy tính cục bộ của tôi và máy chủ SMTP từ xa (ZOHO như được hiển thị):

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

Điều đáng chú ý là mô-đun SMTP hỗ trợ trình quản lý bối cảnh, do đó không cần phải gọi thủ công thoát (), điều này sẽ đảm bảo nó luôn được gọi ngay cả khi có ngoại lệ.

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

-1

Đối với mã của bạn có liên quan, dường như không có bất cứ điều gì sai về cơ bản với nó ngoại trừ điều đó, không rõ bạn thực sự gọi hàm đó như thế nào. Tất cả những gì tôi có thể nghĩ là khi máy chủ của bạn không phản hồi thì bạn sẽ gặp lỗi SMTPServerDisconnected này. Nếu bạn tra cứu hàm getreply () trong smtplib (đoạn trích dưới đây), bạn sẽ có một ý tưởng.

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

kiểm tra một ví dụ tại https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py cũng sử dụng lệnh gọi hàm để gửi email, nếu đó là những gì bạn đang cố gắng thực hiện (phương pháp DRY).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.