class - smtplib

最後更新: 2016-07-25

smtplib 與 mail

 

目錄

  • smtplib - TLS
  • smtplib -SSL
  • smtplib -Debug TLS 及 SSL
  • smtplib - Exception
  • Date
  • mail - class
  • mail - Example 1~3
  • Embed image

 


TLS

TLS default port 587

#!/usr/bin/python

import smtplib

myserver = ""
myfrom = ""
myto = ""
loginuser = ""
loginpw = ""

print "Testing Server: " + myserver

msg = ("From: %s \r\nTo: %s \r\nSubject: test mail %s \r\n\r\nServer: %s"
                % (myfrom, myto, myserver, myserver))

# class smtplib.SMTP([host[, port[, local_hostname[, timeout]]]])
server = smtplib.SMTP(myserver, timeout=3)

# 非必要
server.set_debuglevel(1)

# 當有 login 時, 一定要用 ehlo()
server.ehlo()

# 非必要
server.starttls()

# You should then call ehlo() again.(非必要)
server.ehlo()

# 登入才 send 信
server.login(loginuser, loginpw)

# server.sendmail(fromaddr, toaddrs, msg)
server.sendmail(myfrom, myto, msg)

server.quit()

Remark

設定 hello name

SMTP.helo([hostname]) / SMTP.ehlo([hostname])

 


SSL

用 SSL 時要把

smtplib.SMTP('your_smtp_gw') 

改成

server = smtplib.SMTP_SSL(myserver, 465, timeout=3)
# If the timeout expires, "socket.timeout" is raised.

P.S.

# port: 465

 


Debug TLS 及 SSL

 

SSL:

openssl s_client -connect mail.server:465

TLS:

openssl s_client -connect server:587 -starttls smtp [–crlf]

-starttls protocol       # send the protocol-specific message(s) to switch to TLS for communication.

–crlf                        # line-terminator “<LF>” or “<CRLF>”

----

Troubleshoot 1

ssl.SSLError: [SSL: DH_KEY_TOO_SMALL] dh key too small (_ssl.c:727)

Server Log

...: warning: TLS library problem: 6477:error:14094410:SSL routines:SSL3_READ_BYTES:
              sslv3 alert handshake failure:s3_pkt.c:1257:SSL alert number 40:

只適用於 Python 3: 加 ssl 的 context

import ssl
...
#context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_1)
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
connection.starttls(context=context)

 


smtplib 的 Exception

 

smtplib.SMTPException                       # base
smtplib.SMTPConnectError
smtplib.SMTPServerDisconnected        # unexpectedly disconnects
smtplib.SMTPHeloError
smtplib.SMTPRecipientsRefused
smtplib.SMTPSenderRefused
smtplib.SMTPDataError
smtplib.SMTPAuthenticationError
smtplib.SMTPResponseException          # SMTP error code

i.e.

try:
    server.sendmail(myfrom, myto, msg)
except smtplib.SMTPException as e:
    print e
else:
    print "test OK"

output:

{'x@x': (554, '5.7.1 <x@x>: Recipient address rejected: Over send mail limit')}

 


E-Mail 內的 Date

 

# Format: RFC 2822 (Fri, 09 Nov 2001 01:08:47 -0000)

email.utils.formatdate([timeval[, localtime][, usegmt]])

Optional

timeval if given is a floating point time value as accepted by time.gmtime() and time.localtime() (Default: current time)

localtime is a flag that when True, interprets timeval, and returns a date relative to the local timezone instead of UTC (Default: False)

usegmt, outputs a date string with the timezone as an ascii string GMT, rather than a numeric -0000 (Default: False)

code

from email.utils import formatdate
formatdate(localtime=True)

out

'Wed, 12 Oct 2016 18:06:10 +0800'

 



mail class

 

MIMEBase - MIMENonMultipart (直接是信的內容)
                - MIMEMultipart - MIMEImage, MIMEText

 

class email.mime.base.MIMEBase

# base class for all the MIME-specific subclasses of Message
# Ordinarily you won’t create instances specifically of MIMEBaseclass

email.mime.nonmultipart.MIMENonMultipart()

# A subclass of MIMEBase
# prevent the use of the attach() method (MultipartConversionError exception is raised)

class email.mime.multipart.MIMEMultipart([_subtype[, boundary[, _subparts[, _params]]]])

# A subclass of MIMEBase
# this is an intermediate base class for MIME messages that are multipart.
- A Content-Type header of multipart/_subtype will be added to the message object
- A MIME-Version header will also be added.
defaults:
_subtype: mixed

email.mime.image.MIMEImage(_imagedata)

# A subclass of MIMENonMultipart

class email.mime.text.MIMEText(_text [, _subtype[, _charset]])

# A subclass of MIMENonMultipart
# _text is the string for the payload
# _subtype: defaults to plain
# _charset: default us-ascii

i.e. msg = MIMEText(texto.encode('utf-8'), 'plain', 'utf-8')

class email.message.Message

# Representing an email message

__setitem__(name, val)
 # Add a header to the message with field name name and value val (append)
 * not overwrite or delete any existing header with the same name

__delitem__(name)
 # Delete "all" occurrences of the field with name from the message’s headers
 * not present  => No exception is raised 

__getitem__(name)
 # Return the value of the named header field.
 * missing => None    * more than once => undefined

__len__()
 # total number of headers
 * including duplicates

__contains__(name)
 * Matching is done case-insensitively

 __str__()
 # Equivalent to as_string(unixfrom=True)

 attach(payload)
 # Add the given payload to the current payload,

 as_string([unixfrom=False, maxheaderlen=0, policy=None])
 # Return the entire message flattened as a string.
 # When unixfrom is True, the envelope header is included in the returned string.
 # envelope 就是這一行: "From nobody Mon Aug 18 16:21:59 2014"

 


Example 1: 設定 "From", "To" 及內容

import smtplib

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

COMMASPACE = ', '

me == sender@email
family = (A,B,C)

# Create the container (outer) email message.
msg = MIMEMultipart()

# 設定 from 及 to
msg['Subject'] = 'Our family reunion'
msg['From'] = me
msg['To'] = COMMASPACE.join(family)

# 設定內容
msg.attach(MIMEText("testing"))

# 特別內容, e-mail client 會 ignore 佢的
msg.preamble = '-===================-'

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

msg.preamble=""

allows for some text between the blank line following the headers, and the first multipart boundary string

Normally, this text is never visible in a MIME-aware mail reader, because it falls outside the standard MIME armor.

i.e

# Mail Header 有
Content-Type: multipart/mixed; boundary="===============1331787088682035175=="


# Mail body 有以下內容
# msg.attach(MIMEText("testing"))
# msg.preamble = '-===================-'
-===================-
--===============1331787088682035175==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit

testing
--===============1331787088682035175==--

Example5: Mail Header

Spam check

MISSING_DATE=1.36,
MISSING_MID=0.497

code

from email.mime.multipart import MIMEMultipart
from email.utils import formatdate
..
msg = MIMEMultipart()
msg['Message-ID']="<UUID_x@y>"
msg['Date']=formatdate(localtime=True)
...

Example 2: 只有 MIMEMultipart 才可以 call attach !!

import smtplib

from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage

....

msg = MIMEMultipart()
msg.attach(MIMEText(file("text.txt").read()))
msg.attach(MIMEImage(file("image.png").read()))

# to send
mailer = smtplib.SMTP()
mailer.connect()
mailer.sendmail(from, to, msg.as_string())
mailer.close()

Example3: Filename

filename = "lime.jpg"
myimg = file(filename, "rb").read()
myimg = MIMEImage(myimg)
myimg.add_header('Content-Disposition', 'attachment; filename="%s"'% filename)
msg.attach(myimg)

Example4: other type attachment

from email import Encoders
from email.MIMEBase import MIMEBase

filename="test.txt"

part = MIMEBase('application', "octet-stream")
part.set_payload( file(filename,"rb").read() )
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"'% filename)
msg.attach(part)

Encoders

Encodes the payload into base64 form and sets the Content-Transfer-Encoding header to base64

encode 前

Content-Type: application/octet-stream
MIME-Version: 1.0

測試

encode 後

Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64

tPq41Q==

相關的 mail function

  • set_payload(payload[, charset])
  • get_payload([i[, decode]])

P.S.

zip attachment

from email.mime.base import MIMEBase
from email import encoders

....................

filename = "test.zip"

part = MIMEBase('application', "octet-stream")
part.set_payload(file(filename, "rb").read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"'% filename)
msg.attach(part)

....................

raw

--===============8891299427962483695==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test.bin"

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
...........................

 


Embed image

 

Content-Type:

Content-Type: multipart/related;
 boundary="===============ID2=="

HTML:

--===============ID2==
<!-- cid (Content-ID) -->
<img src="cid:123456789" alt="">
......................

Resource:

--===============ID2==
Content-Type: image/png;
Content-ID: <123456789>

.......................
--===============ID2==--