generate and send mail with python: tutorial

A

aspineux

Hi I have written a tutorial about how to generate and send emails
with python.

You can find it here http://blog.magiksys.net/generate-and-send-mail-with-python-tutorial

And here is the content. Enjoy.

This article follows two other articles (1, 2) about how to parse
emails in Python.
These articles describe very well mail usages and rules and can be
helpful for non Python developer too.

The goal here is to generate a valid email including internationalized
content, attachments and even inline images, and send it to a SMTP
server.

The procedure can be achieved in 3 steps :

compose the body of the email.
compose the header of the email.
send the email to a SMTP server

At the end you will find the sources.

The first rule to keep in mind all the time, is that emails are 7bits
only, they can contains us-ascii characters only ! A lot of RFCs
define rules to encode non us-ascii characters when they are required:
RFC2047 to encode headers, RFC2045 for encoding body using the MIME
format or RFC2231 for MIME parameters like the attachment filename.
The mail header

The header is composed of lines having a name and a value. RFC2047 let
you use quoted or binary encoding to encode non us-ascii characters in
it.
For example:

Subject: Courrier électronique en Français

become using the quoted format

Subject: =?iso-8859-1?q?Courrier_=E8lectronique_en_Fran=E7ais?=

or using the binary one.

Subject: =?iso-8859-1?b?Q291cnJpZXIg6GxlY3Ryb25pcXVlIGVuIEZyYW7nYWlz?=

Here the quoted format is readable and shorter. Python
email.header.Header object generates user friendly header by choosing
the quoted format when the encoding shares characters with us-ascii,
and the binary one for encoding like the Chinese big5 that requires
the encoding of all characters.
'=?iso-8859-1?q?Courrier_=E8lectronique_en_Fran=E7ais?='

Header values have different types (text, date, address, ...) that all
require different encoding rules. For example, in an address like :

Sender: Alain Spineux <[email protected]>

The email part <[email protected]> has to be in us-ascii and
cannot be encoded. The name part cannot contains some special
characters without quoting : []\()<>@,:;".
We can easily understand why "<>" are in the list, others have all
their own story.

For example, the use of the "Dr." prefix requires to quote the name
because of the '.':

Sender: "Dr. Alain Spineux" <[email protected]>

For a name with non us-ascii characters like (look at the "ï" in
Alaïn), the name must be encoded.

Sender: Dr. Alïan Spineux <[email protected]>

must be written :

Sender: =?iso-8859-1?q?Dr=2E_Ala=EFn_Spineux?=
<[email protected]>

Notice that the '.' in the prefix is replaced by "=2E", because Header
preventively encode all non alpha or digit characters to match the
most restrictive header rules.

The Python function email.utils.formataddr() quotes the special
characters but don't encode non us-ascii characters. On the other
hand, email.header.Header can encode non us-ascii characters but
ignore all specials rule about address encoding.
Let see how both work:
'Alain Spineux <[email protected]>'

This is a valid header value for a To: field
'Dr. Alain Spineux <[email protected]>'

'"\\"Alain\\" Spineux" <[email protected]>'

Here '"' is escaped using '\', this is fine.
u'Ala\xefn Spineux <[email protected]>'

formataddr() don't handle non us-ascii string, this must be done by
Header object
'=?utf-8?q?Ala=C3=AFn_Spineux_=3Calain=2Espineux=40gmail=2Ecom=3E?='

This is not valid because the address is also encoded and an old or
some recent MUA will't handle this. The good form here is :

=?utf-8?q?Ala=C3=AFn_Spineux?= <[email protected]>'

Function format_addresses(addresses, header_name=None, charset=None)
handle carefully the encoding of the addresses.
str(format_addresses([ (u'Ala\xefn Spineux', '(e-mail address removed)'), ('John', '(e-mail address removed)'), ], 'to', 'iso-8859-1'))
'=?iso-8859-1?q?Ala=EFn_Spineux?= <[email protected]> ,\n John
<[email protected]>'

Bytes and unicode string can be mixed. Addresses must always be us-
ascii. Byte string must be encoded using charset or be us-ascii.
Unicode strings that cannot be encoded using charset will be encoded
using utf-8 automatically by Header.

For dates, use the email.utils.formatdate() this way.
'Wed, 10 Aug 2011 16:46:30 +0200'

The mail body

Depending of your need :

text and/or html version of the message
related inline images
attached files

the structure of the MIME email may vary, but the general one is as
follow:

multipart/mixed
|
+-- multipart/related
| |
| +-- multipart/alternative
| | |
| | +-- text/plain
| | +-- text/html
| |
| +-- image/gif
|
+-- application/msword

Un-needed parts will be removed by function gen_mail(text, html=None,
attachments=[], relateds=[]) regarding your parameters.
html=None, \
attachments=[ (u'Text attach
\xe8'.encode('iso-8859-1'), 'text', 'plain', 'filename.txt',
'iso-8859-1'), ] )
From nobody Thu Aug 11 08:05:14 2011
Content-Type: multipart/mixed; boundary="===============0992984520=="
MIME-Version: 1.0

--===============0992984520==
Content-Type: text/plain; charset="iso-8859-1"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable

Bonne journ=E8e
--===============0992984520==
Content-Type: text/plain; charset="iso-8859-1"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="filename.txt"

Text attach=E8
--===============0992984520==--

text and html are tuple of the form (content, encoding).
Items of the attachments list can be tuples of the form (content,
maintype, subtype, filename, charset) or can be MIME object inheriting
from MIMEBase. If maintype is 'text', charset must match the encoding
of the content, or if content is unicode, charset will be used to
encode it. For other value of maintype, charset is not used.
relateds is similar to attachments but content is related to the
message in HTML to allow embedding of images or other contents.
filename is replaced by content_id that must match the cid: references
inside the HTML message.

Attachments can have non us-ascii filename, but this is badly
supported by some MUA and then discouraged. Anyway, if you want to use
non us-ascii filename, RFC2231 need the encoding but also the
language. Replace filename, by a tuple of the form (encoding,
language, filename), for example, use ('iso-8859-1', 'fr', u'r
\xe9pertoir.png'.encode('iso-8859-1')) instead of 'filename.txt'.

The attached source code provide more samples. Look carefully in it
how I encode or not the unicode string and content before to use them.
Send your email

The Python smtplib library handles SSL, STARTLS, and authentication,
all you need to connect to any SMTP server. The library also reports
all errors carefully.

The send_mail(smtp_host, sender, recipients, subject, msg,
default_charset, cc=[], bcc=[], smtp_port=25, smtp_mode='normal',
smtp_login=None, smtp_password=None, message_id_string=None) function
first fill in the header about sender and recipients using
format_addresses() function, and send the email to the SMTP using the
protocols and credentials you have choose with smtp_* variables.
sender is a tuples and recipients, cc, bcc are list of tuple of the
form [ (name, address), .. ] like expected by format_addresses().
default_charset will be used as default encoding when generating the
email (to encode unicode string), and as default encoding for byte
string.
subject is the subject of the email.
msg is a MIME object like returned by gen_mail().
smtp_mode can be 'normal', 'ssl' or 'tls'.

For example if you want to use your GMAIL account to send your emails,
use this setup:

smtp_host='smtp.gmail.com'
smtp_port=587
smtp_mode='tls'
smtp_login='(e-mail address removed)'
smtp_password='yourpassword'
sender=('Your Name', smtp_login)

Most of the time you will just need to specify smtp_host
Source

#!/bin/env/python
#
# sendmail.py
# (c) Alain Spineux <[email protected]>
# http://blog.magiksys.net/sending-email-using-python-tutorial
# Released under GPL
#
import os, sys
import time
import base64
import smtplib
import email
import email.header
import email.utils
import email.mime
import email.mime.base
import email.mime.text
import email.mime.image
import email.mime.multipart

def format_addresses(addresses, header_name=None, charset=None):
"""This is an extension of email.utils.formataddr.
Function expect a list of addresses [ ('name',
'name@domain'), ...].
The len(header_name) is used to limit first line length.
The function mix the use Header(), formataddr() and check for
'us-ascii'
string to have valid and friendly 'address' header.
If one 'name' is not unicode string, then it must encoded using
'charset',
Header will use 'charset' to decode it.
Unicode string will be encoded following the "Header" rules : (
try first using ascii, then 'charset', then 'uft8')
'name@address' is supposed to be pure us-ascii, it can be
unicode
string or not (but cannot contains non us-ascii)

In short Header() ignore syntax rules about 'address' field,
and formataddr() ignore encoding of non us-ascci chars.
"""
header=email.header.Header(charset=charset,
header_name=header_name)
for i, (name, addr) in enumerate(addresses):
if i!=0:
# add separator between addresses
header.append(',', charset='us-ascii')
# check if address name is a unicode or byte string in "pure"
us-ascii
try:
if isinstance(name, unicode):
# convert name in byte string
name=name.encode('us-ascii')
else:
# check id byte string contains only us-ascii chars
name.decode('us-ascii')
except UnicodeError:
# Header will use "RFC2047" to encode the address name
# if name is byte string, charset will be used to decode
it first
header.append(name)
# here us-ascii must be used and not default 'charset'
header.append('<%s>' % (addr,), charset='us-ascii')
else:
# name is a us-ascii byte string, i can use formataddr
formated_addr=email.utils.formataddr((name, addr))
# us-ascii must be used and not default 'charset'
header.append(formated_addr, charset='us-ascii')

return header


def gen_mail(text, html=None, attachments=[], relateds=[]):
"""generate the core of the email message.
text=(encoded_content, encoding)
html=(encoded_content, encoding)
attachments=[(data, maintype, subtype, filename, charset), ..]
if maintype is 'text', data lmust be encoded using charset
filename can be us-ascii or (charset, lang, encoded_filename)
where encoded_filename is encoded into charset, lang can be empty
but
usually the language related to the charset.
relateds=[(data, maintype, subtype, content_id, charset), ..]
idem attachment above, but content_id is related to the "CID"
reference
in the html version of the message.
"""

main=text_part=html_part=None
if text:
content, charset=text
main=text_part=email.mime.text.MIMEText(content, 'plain',
charset)

if html:
content, charset=html
main=html_part=email.mime.text.MIMEText(content, 'html',
charset)

if not text_part and not html_part:
main=text_part=email.mime.text.MIMEText('', 'plain', 'us-
ascii')
elif text_part and html_part:
# need to create a multipart/alternative to include text and
html version
main=email.mime.multipart.MIMEMultipart('alternative', None,
[text_part, html_part])

if relateds:
related=email.mime.multipart.MIMEMultipart('related')
related.attach(main)
for part in relateds:
if not isinstance(part, email.mime.base.MIMEBase):
data, maintype, subtype, content_id, charset=part
if (maintype=='text'):
part=email.mime.text.MIMEText(data, subtype,
charset)
else:
part=email.mime.base.MIMEBase(maintype, subtype)
part.set_payload(data)
email.Encoders.encode_base64(part)
part.add_header('Content-ID', '<'+content_id+'>')
part.add_header('Content-Disposition', 'inline')
related.attach(part)
main=related

if attachments:
mixed=email.mime.multipart.MIMEMultipart('mixed')
mixed.attach(main)
for part in attachments:
if not isinstance(part, email.mime.base.MIMEBase):
data, maintype, subtype, filename, charset=part
if (maintype=='text'):
part=email.mime.text.MIMEText(data, subtype,
charset)
else:
part=email.mime.base.MIMEBase(maintype, subtype)
part.set_payload(data)
email.Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment',
filename=filename)
mixed.attach(part)
main=mixed

return main

def send_mail(smtp_host, sender, recipients, subject, msg,
default_charset, cc=[], bcc=[], smtp_port=25, smtp_mode='normal',
smtp_login=None, smtp_password=None, message_id_string=None):
"""

"""

mail_from=sender[1]
rcpt_to=map(lambda x:x[1], recipients)
rcpt_to.extend(map(lambda x:x[1], cc))
rcpt_to.extend(map(lambda x:x[1], bcc))

msg['From'] = format_addresses([ sender, ], header_name='from',
charset=default_charset)
msg['To'] = format_addresses(recipients, header_name='to',
charset=default_charset)
msg['Cc'] = format_addresses(cc, header_name='cc',
charset=default_charset)
msg['Subject'] = email.header.Header(subject, default_charset)
utc_from_epoch=time.time()
msg['Date'] = email.utils.formatdate(utc_from_epoch,
localtime=True)
msg['Messsage-Id'] =email.utils.make_msgid(message_id_string)

# Send the message
errmsg=''
failed_addresses=[]
try:
if smtp_mode=='ssl':
smtp=smtplib.SMTP_SSL(smtp_host, smtp_port)
else:
smtp=smtplib.SMTP(smtp_host, smtp_port)
if smtp_mode=='tls':
smtp.starttls()

if smtp_login and smtp_password:
# login and password must be encoded
# because HMAC used in CRAM_MD5 require non unicode string
smtp.login(smtp_login.encode('utf-8'),
smtp_password.encode('utf-8'))

ret=smtp.sendmail(mail_from, rcpt_to, msg.as_string())
smtp.quit()
except (socket.error, ), e:
errmsg='server %s:%s not responding: %s' % (smtp_host,
smtp_port, e)
except smtplib.SMTPAuthenticationError, e:
errmsg='authentication error: %s' % (e, )
except smtplib.SMTPRecipientsRefused, e:
# code, errmsg=e.recipients[recipient_addr]
errmsg='recipients refused: '+', '.join(e.recipients.keys())
except smtplib.SMTPSenderRefused, e:
# e.sender, e.smtp_code, e.smtp_error
errmsg='sender refused: %s' % (e.sender, )
except smtplib.SMTPDataError, e:
errmsg='SMTP protocol mismatch: %s' % (e, )
except smtplib.SMTPHeloError, e:
errmsg="server didn't reply properly to the HELO greeting: %s"
% (e, )
except smtplib.SMTPException, e:
errmsg='SMTP error: %s' % (e, )
except Exception, e:
errmsg=str(e)
else:
if ret:
failed_addresses=ret.keys()
errmsg='recipients refused: '+', '.join(failed_addresses)

return msg, errmsg, failed_addresses

And how to use it :

smtp_host='max'
smtp_port=25

if False:
smtp_host='smtp.gmail.com'
smtp_port='587'
smtp_login='(e-mail address removed)'
smtp_passwd='your.password'

sender=(u'Ala\xefn Spineux', '(e-mail address removed)')
sender=(u'Alain Spineux', u'(e-mail address removed)')

root_addr='(e-mail address removed)'
recipients=[ ('Alain Spineux', root_addr),
# (u'Alain Spineux', root_addr),
# ('Dr. Alain Sp<i>neux', root_addr),
# (u'Dr. Alain Sp<i>neux', root_addr),
# (u'Dr. Ala\xefn Spineux', root_addr),
# (u'Dr. Ala\xefn Spineux', root_addr),
# ('us_ascii_name_with_a_space_some_where_in_the_middle
to_allow_python_Header._split()_to_split_according_RFC2822',
root_addr),
# (u'This-is-a-very-long-unicode-name-with-one-non-ascii-
char-\xf4-to-force-Header()-to-use-RFC2047-encoding-and-split-in-multi-
line', root_addr),
#
('this_line_is_too_long_and_dont_have_any_white_space_to_allow_Header._split()_to_split_according_RFC2822',
root_addr),
# ('Alain Spineux', root_addr),
]

smile_png=base64.b64decode(
"""iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOBAMAAADtZjDiAAAAMFBMVEUQEAhaUjlaWlp7e3uMezGU
hDGcnJy1lCnGvVretTnn5+/3pSn33mP355T39+//
75SdwkyMAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAAB3RJTUUH2wcJDxEjgefAiQAAAAd0RVh0QXV0aG9yAKmuzEgAAAAMdEVYdERlc2NyaXB0aW9u
ABMJISMAAAAKdEVYdENvcHlyaWdodACsD8w6AAAADnRFWHRDcmVhdGlvbiB0aW1lADX3DwkAAAAJ
dEVYdFNvZnR3YXJlAF1w/
zoAAAALdEVYdERpc2NsYWltZXIAt8C0jwAAAAh0RVh0V2FybmluZwDA
G+aHAAAAB3RFWHRTb3VyY2UA9f+D6wAAAAh0RVh0Q29tbWVudAD2zJa/
AAAABnRFWHRUaXRsZQCo
7tInAAAAaElEQVR4nGNYsXv3zt27TzHcPup6XDBmDsOeBvYzLTynGfacuHfm/
x8gfS7tbtobEM3w
n2E9kP5n9N/oPZA+//7PP5D8GSCYA6RPzjlzEkSfmTlz
+xkgffbkzDlAuvsMWAHDmt0g0AUAmyNE
wLAIvcgAAAAASUVORK5CYII=
""")
angry_gif=base64.b64decode(
"""R0lGODlhDgAOALMAAAwMCYAAAACAAKaCIwAAgIAAgACAgPbTfoR/YP8AAAD/AAAA//
rMUf8A/wD/
//Tw5CH5BAAAAAAALAAAAAAOAA4AgwwMCYAAAACAAKaCIwAAgIAAgACAgPbTfoR/
YP8AAAD/AAAA
//rMUf8A/wD///Tw5AQ28B1Gqz3S6jop2sxnAYNGaghAHirQUZh6sEDGPQgy5/b9UI
+eZkAkghhG
ZPLIbMKcDMwLhIkAADs=
""")
pingu_png=base64.b64decode(
"""iVBORw0KGgoAAAANSUhEUgAAABoAAAATBAMAAAB8awA1AAAAMFBMVEUQGCE5OUJKa3tSUlJSrdZj
xu9rWjl7e4SljDGlnHutnFK9vbXGxsbWrTHW1tbv7+88a/
HUAAAACXBIWXMAAA7EAAAOxAGVKw4b
AAAAB3RJTUUH2wgJDw8mp5ycCAAAAAd0RVh0QXV0aG9yAKmuzEgAAAAMdEVYdERlc2NyaXB0aW9u
ABMJISMAAAAKdEVYdENvcHlyaWdodACsD8w6AAAADnRFWHRDcmVhdGlvbiB0aW1lADX3DwkAAAAJ
dEVYdFNvZnR3YXJlAF1w/
zoAAAALdEVYdERpc2NsYWltZXIAt8C0jwAAAAh0RVh0V2FybmluZwDA
G+aHAAAAB3RFWHRTb3VyY2UA9f+D6wAAAAh0RVh0Q29tbWVudAD2zJa/
AAAABnRFWHRUaXRsZQCo
7tInAAAA0klEQVR4nE2OsYrCUBBFJ42woOhuq43f4IfYmD5fsO2yWNhbCFZ21ovgFyQYG1FISCxt
Xi8k3KnFx8xOTON0Z86d4ZKKiNowM5QEUD6FU9uCSWwpYThrSQuj2fjLso2DqB9OBqqvJFiVllHa
usJedty1NFe2brRbs7ny5aIP8dSXukmyUABQ0CR9AU9d1IOO1EZgjg7n+XEBpOQP0E/
rUz2Rlw09
Amte7bdVSs/s7py5i1vFRsnFuW+gdysSu4vzv9vm57faJuY0ywFhAFmupO/zDzDcxlhVE/
gbAAAA
AElFTkSuQmCC
""")

text_utf8="""This is the the text part.
With a related picture: cid:smile.png
and related document: cid:related.txt

Bonne journ\xc3\xa9ee.
"""
utext=u"""This is the the text part.
With a related picture: cid:smile.png
and related document: cid:related.txt
Bonne journ\xe9e.
"""
data_text=u'Text en Fran\xe7ais'
related_text=u'Document relatif en Fran\xe7ais'

html="""<html><body>
This is the html part with a related picture: <img
src="cid:smile.png" />
and related document: <a href="cid:related.txt">here</a><br>
Bonne journ&eacute;e.
</body></html>
"""
relateds=[ (smile_png, 'image', 'png', 'smile.png', None),
(related_text.encode('iso-8859-1'), 'text', 'plain',
'related.txt', 'iso-8859-1'),
]

pingu_att=email.mime.image.MIMEImage(pingu_png, 'png')
pingu_att.add_header('Content-Disposition', 'attachment',
filename=('iso-8859-1', 'fr', u'ping\xfc.png'.encode('iso-8859-1')))

pingu_att2=email.mime.image.MIMEImage(pingu_png, 'png')
pingu_att2.add_header('Content-Disposition', 'attachment',
filename='pingu.png')

attachments=[ (angry_gif, 'image', 'gif', ('iso-8859-1', 'fr',
u'\xe4ngry.gif'.encode('iso-8859-1')), None),
(angry_gif, 'image', 'gif', 'angry.gif', None),

(data_text.encode('iso-8859-1'), 'text', 'plain',
'document.txt', 'iso-8859-1'),
pingu_att,
pingu_att2]

mail=gen_mail((utext.encode('iso-8859-1'), 'iso-8859-1'), (html, 'us-
ascii'), attachments, relateds)

msg, errmsg, failed_addresses=send_mail(smtp_host, \
sender, \
recipients, \
u'My Subject', \
mail, \
default_charset='iso-8859-1',
cc=[('Gama', root_addr), ],
bcc=[('Colombus', root_addr), ],
smtp_port=smtp_port,
)

print msg
print errmsg
print failed_addresses
 
D

Dennis Lee Bieber

Hi I have written a tutorial about how to generate and send emails
with python.

Is that really such a difficult task?

Okay, I didn't really use Python for the entire creation of the
message... But my first real Python program (using 1.4 or 1.5, whatever
was included in the first "Programming Python" disk, for AmigaOS) was a
rudimentary outgoing SMTPd which had to parse headers from message
files "queued" by an ARexx script from an Amiga version of ELM, then
handshake with the ISP SMTPd to relay the message onward. It took less
than a week after buying the book that I had this ARexx/Python hybrid
working -- and it worked better than either C-language programs I'd
downloaded (at that period of time, Amiga email programs ONLY read from
local spool and queued to local spool; separate programs had to be used
to read POP3 and send SMTP... My first SMTP utility presumed 1) direct
connection to destination address, 2) created an email file for each
address -- problem: if a destination did not have a receiving SMTPd [ie,
one needed to do an MX lookup instead] it would hang trying to send that
message, and never process others... The second program used ISP relay
-- but it only parsed the "TO:" header, and thereby failed to handshake
CC and BCC destinations)
 
M

maedox

Hi I have written a tutorial about how to generate and send emails
with python.

Is that really such a difficult task?

Okay, I didn't really use Python for the entire creation of the
message... But my first real Python program (using 1.4 or 1.5, whatever
was included in the first "Programming Python" disk, for AmigaOS) was a
rudimentary outgoing SMTPd which had to parse headers from message
files "queued" by an ARexx script from an Amiga version of ELM, then
handshake with the ISP SMTPd to relay the message onward. It took less
than a week after buying the book that I had this ARexx/Python hybrid
working -- and it worked better than either C-language programs I'd
downloaded (at that period of time, Amiga email programs ONLY read from
local spool and queued to local spool; separate programs had to be used
to read POP3 and send SMTP... My first SMTP utility presumed 1) direct
connection to destination address, 2) created an email file for each
address -- problem: if a destination did not have a receiving SMTPd [ie,
one needed to do an MX lookup instead] it would hang trying to send that
message, and never process others... The second program used ISP relay
-- but it only parsed the "TO:" header, and thereby failed to handshake
CC and BCC destinations)

Nice story bro...
 
M

maedox

Hi I have written a tutorial about how to generate and send emails
with python.

Is that really such a difficult task?

Okay, I didn't really use Python for the entire creation of the
message... But my first real Python program (using 1.4 or 1.5, whatever
was included in the first "Programming Python" disk, for AmigaOS) was a
rudimentary outgoing SMTPd which had to parse headers from message
files "queued" by an ARexx script from an Amiga version of ELM, then
handshake with the ISP SMTPd to relay the message onward. It took less
than a week after buying the book that I had this ARexx/Python hybrid
working -- and it worked better than either C-language programs I'd
downloaded (at that period of time, Amiga email programs ONLY read from
local spool and queued to local spool; separate programs had to be used
to read POP3 and send SMTP... My first SMTP utility presumed 1) direct
connection to destination address, 2) created an email file for each
address -- problem: if a destination did not have a receiving SMTPd [ie,
one needed to do an MX lookup instead] it would hang trying to send that
message, and never process others... The second program used ISP relay
-- but it only parsed the "TO:" header, and thereby failed to handshake
CC and BCC destinations)

Nice story bro...
 
A

aspineux

        Is that really such a difficult task?

Yes it's difficult to make an _universal_ mail parser, able to handle
mails from different MUA, using different charsets.
Some emails that break RFC's requires at least some background
to know which rules can be missed by some MUA !

Handle mail from a unique source or similar sources is easy.
Using try and error method is fine for this kind of job, you don't
even need to care if the email you're parsing match the RFC's or not.

My "parser" functions have been tested over 20.000 mails from all
around the world using some human and automated verifications !

Generating emails is not easy, when mixing internationalized
addresses,
subject and content. Integrating text, html, attachment and embedded
images in one single email looks to me difficult enough to require
some explanations.

At least read carefully all my articles to get an idea about the
difficulty,
this is what I try to explain.

Regards.

        Okay, I didn't really use Python for the entire creation of the
message... But my first real Python program (using 1.4 or 1.5, whatever
was included in the first "Programming Python" disk, for AmigaOS) was a
rudimentary outgoing SMTPd which had to parse headers from  message
files "queued" by an ARexx script from an Amiga version of ELM, then
handshake with the ISP SMTPd to relay the message onward. It took less
than a week after buying the book that I had this ARexx/Python hybrid
working -- and it worked better than either C-language programs I'd
downloaded (at that period of time, Amiga email programs ONLY read from
local spool and queued to local spool; separate programs had to be used
to read POP3 and send SMTP... My first SMTP utility presumed 1) direct
connection to destination address, 2) created an email file for each
address -- problem: if a destination did not have a receiving SMTPd [ie,
one needed to do an MX lookup instead] it would hang trying to send that
message, and never process others... The second program used ISP relay
-- but it only parsed the "TO:" header, and thereby failed to handshake
CC and BCC destinations)
 
S

Steven D'Aprano

aspineux said:
Hi I have written a tutorial about how to generate and send emails
with python.
[...]

Thank you, that is an extremely detailed tutorial.
 
A

aspineux

Ben Finney said:
What is the process if the OP, or someone to whom the OP delegates
authority, wants to [contribute their work to the Python
documentation]?

The answer is partly at <URL:http://docs.python.org/documenting/>:

    If you’re interested in contributing to Python’s documentation […]
    Send an e-mail to (e-mail address removed) or open an issue on the tracker..

One should, before doing so, follow the above document on the
documentation style conventions for Python.

I'm using python for long now, and just discovered these HowTo today :-
(
I don't thing rewriting these articles into another format
will improve the "message". I will not rewrite them.

You are free to do it, just keep my name as the original author.
I have no other original source than the HTML one you can have on my
blog.

I appreciate your interest for my work.

I think to put all the sources together to create a library.
I thing about the name of pyezmail for "python easy mail".
Any comment ?

Regards
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top