Skip to main content
 首页 » 编程设计

python之通过 Postfix 向多个收件人发送电子邮件仅由列表中的第一个地址接收

2025年05月04日27arxive

我在这个问题的底部使用 Python 代码将目录的内容发送到定义文件中保存的收件人列表。在此文件中,各个地址由 ; 分隔并列在一个字符串中。提交命令时要求所有内容都已发送,没有任何记录问题,但发现电子邮件仅发送到列表中第一个指定的电子邮件地址。

经过一些挖掘,似乎 Postfix 的 main.cf 文件包含收件人限制,默认值为 1,这可以限制收件人数量。我已尝试对整行进行哈希处理,并将限制增加到 200,但都没有任何影响。

# dovecot 1.1.1 
dovecot_destination_recipient_limit = 200 

当您从单个收件人的角度查看电子邮件时,一切似乎都很好,所以我不得不认为是 Postfix/Dovecot sendmail 部分导致了问题?查看消息输出到文件而不是发送到 smtp 的示例

Content-Type: multipart/mixed; boundary="===============7543504478351047681==" 
MIME-Version: 1.0 
Subject: Malware submission 
To: xxxxx@gmail.com;xxxxx@hotmail.com 
From: me@yu.com 
 
You will not see this in a MIME-aware mail reader. 
 
--===============7543504478351047681== 
Content-Type: application/zip 
MIME-Version: 1.0 
Content-Transfer-Encoding: base64 
Content-Disposition: attachment; filename="sample.zip" 
 
UEsDBAoAAAAAAHGfsT4AAAAAAAAAAAAAAAAGABwAdmlydXMvVVQJAAOVxdJNKJnSTXV4CwABBPUB 
AAAEFAAAAFBLAQIeAwoAAAAAAHGfsT4AAAAAAAAAAAAAAAAGABgAAAAAAAAAEADtQQAAAAB2aXJ1 
cy9VVAUAA5XF0k11eAsAAQT1AQAABBQAAABQSwUGAAAAAAEAAQBMAAAAQAAAAAAA 
--===============7543504478351047681==-- 

后缀邮件日志包含以下内容

May 17 21:10:41 MacBook-Pro-2 postfix/qmgr[3816]: 3FB902C186A: from=<chris.parker@email.co.uk>, size=1004, nrcpt=1 (queue active) 
May 17 21:10:42 MacBook-Pro-2 postfix/smtp[3855]: 3FB902C186A: to=<xxxxo@gmail.com>, relay=gmail-smtp-in.l.google.com[209.85.143.27]:25, delay=1.3, delays=0.01/0.01/0.57/0.75, dsn=2.0.0, status=sent (250 2.0.0 OK 1305662986 k6si1621545wej.25) 
May 17 21:10:42 MacBook-Pro-2 postfix/qmgr[3816]: 3FB902C186A: removed 

请帮忙...

    #!/usr/bin/env python 
 
"""Send the contents of a directory as a MIME message.""" 
 
import os 
import sys 
import smtplib 
# For guessing MIME type based on file name extension 
import mimetypes 
 
from optparse import OptionParser 
 
from email import encoders 
from email.message import Message 
from email.mime.audio import MIMEAudio 
from email.mime.base import MIMEBase 
from email.mime.image import MIMEImage 
from email.mime.multipart import MIMEMultipart 
from email.mime.text import MIMEText 
 
COMMASPACE = ', ' 
 
def main(): 
parser = OptionParser(usage="""\ 
Send the contents of a directory as a MIME message. 
 
Usage: %prog [options] 
 
Unless the -o option is given, the email is sent by forwarding to your local 
SMTP server, which then does the normal delivery process. Your local machine 
must be running an SMTP server. 
""") 
parser.add_option('-d','--directory', 
type='string', action='store') 
parser.add_option('-o', '--output', 
type='string', action='store', metavar='FILE', 
help="""Print the composed message to FILE instead of 
sending the message to the SMTP server.""") 
parser.add_option('-s', '--sender', 
type='string', action='store', metavar='SENDER', 
help='The value of the From: header (required)') 
parser.add_option('-r', '--recipient', 
type='string', action='append', metavar='RECIPIENT', 
default=[], dest='recipients'), 
parser.add_option('-f', '--recipientfile', 
type='string', action='store', metavar='RECIPIENT_FILE', 
dest='recipient_file', default="", 
help='A To: header value (a file containing this)') 
 
opts, args = parser.parse_args() 
if not opts.sender or not (opts.recipient_file or opts.recipients): 
    parser.print_help() 
    sys.exit(1) 
directory = opts.directory 
if not directory: 
    directory = '.' 
# Create the enclosing (outer) message 
 
try: 
    rec_file = open(opts.recipient_file) 
    recipients = rec_file.read() 
    rec_file.close() 
except IOError: 
    print "/!\ Bad file. Falling back to recipent -r option" 
    recipients = COMMASPACE.join(opts.recipients) 
 
outer = MIMEMultipart() 
outer['Subject'] = 'Malware submission' 
outer['To'] = recipients 
outer['From'] = opts.sender 
outer.preamble = 'You will not see this in a MIME-aware mail reader.\n' 
 
for filename in os.listdir(directory): 
    path = os.path.join(directory, filename) 
    if not os.path.isfile(path): 
        continue 
    # Guess the content type based on the file's extension. Encoding 
    # will be ignored, although we should check for simple things like 
    # gzip'd or compressed files. 
    ctype, encoding = mimetypes.guess_type(path) 
    if ctype is None or encoding is not None: 
    # No guess could be made, or the file is encoded (compressed), so 
    # use a generic bag-of-bits type. 
        ctype = 'application/octet-stream' 
    maintype, subtype = ctype.split('/', 1) 
    if maintype == 'text': 
        fp = open(path) 
    # Note: we should handle calculating the charset 
        msg = MIMEText(fp.read(), _subtype=subtype) 
        fp.close() 
    elif maintype == 'image': 
        fp = open(path, 'rb') 
        msg = MIMEImage(fp.read(), _subtype=subtype) 
        fp.close() 
    elif maintype == 'audio': 
        fp = open(path, 'rb') 
        msg = MIMEAudio(fp.read(), _subtype=subtype) 
        fp.close() 
    else: 
        fp = open(path, 'rb') 
        msg = MIMEBase(maintype, subtype) 
        msg.set_payload(fp.read()) 
        fp.close() 
    # Encode the payload using Base64 
    encoders.encode_base64(msg) 
    # Set the filename parameter 
    msg.add_header('Content-Disposition', 'attachment', filename=filename) 
    outer.attach(msg) 
# Now send or store the message 
composed = outer.as_string() 
if opts.output: 
    fp = open(opts.output, 'w') 
    fp.write(composed) 
    fp.close() 
else: 
    #print "Sender : " + opts.sender + ", Recipients : " + recipients #DEBUG :- Check send and recipients are correct 
    s = smtplib.SMTP('localhost') 
    s.sendmail(opts.sender, recipients, composed) 
    s.quit() 
 
if __name__ == '__main__': 
main() 

生成邮件的Python代码

请您参考如下方法:

定义消息时,应使用逗号分隔的收件人列表设置To列表:

recipients = 'foo, bar' 
outer['To'] = recipients 

但是,当您调用 sendmail() 时,您需要将其作为列表传递给收件人:

rcpts = [r.strip() for r in recipients.split(',') if r] 
s.sendmail(sender, rcpts, composed)