Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2@file
3@brief Functions to send emails
4"""
5import smtplib
6import os
8import mimetypes
9from email import encoders
10from email.mime.audio import MIMEAudio
11from email.mime.base import MIMEBase
12from email.mime.image import MIMEImage
13from email.mime.multipart import MIMEMultipart
14from email.mime.text import MIMEText
16COMMASPACE = ', '
19def compose_email(fr, to, subject, body_html=None, body_text=None, attachements=None, cc=None, bcc=None):
20 """
21 Composes an email as a string.
23 @param fr from
24 @param to receiver (or list of receivers)
25 @param subject subject
26 @param body_text body text
27 @param body_html body html
28 @param cc list of ccs
29 @param bcc list of bccs
30 @param attachements list of files to attach to the email
31 @return string
33 If the file is a text file, the filename can be replaced by (filename, encoding).
34 If *body_html* and *body_text* are filled, only the first one will be used.
35 """
36 if isinstance(to, str):
37 to = [to]
38 if isinstance(cc, str):
39 cc = [cc]
40 if isinstance(bcc, str):
41 bcc = [bcc]
42 if attachements is None:
43 attachements = []
45 global COMMASPACE # pylint: disable=W0602
46 outer = MIMEMultipart()
47 outer['Subject'] = subject
48 outer['To'] = COMMASPACE.join(to)
49 outer['From'] = fr
50 if cc is not None:
51 outer["CC"] = COMMASPACE.join(cc)
52 if bcc is not None:
53 outer["BCC"] = COMMASPACE.join(bcc)
55 if body_html is not None:
56 part2 = MIMEText(body_html, 'html')
57 outer.attach(part2)
58 elif body_text is not None:
59 part1 = MIMEText(body_text, 'plain')
60 outer.attach(part1)
61 else:
62 # no body
63 pass
65 outer.preamble = 'prepared by pymmails.\n'
67 for filename in attachements:
68 if isinstance(filename, tuple):
69 path = filename[0]
70 encoding = filename[1]
71 else:
72 path = filename
73 encoding = "ascii"
75 if not os.path.exists(path):
76 raise FileNotFoundError(path)
77 if not os.path.isfile(path):
78 raise FileNotFoundError(path + " is not a file")
80 ctype, encoding = mimetypes.guess_type(path)
81 if ctype is None or encoding is not None:
82 ctype = 'application/octet-stream'
84 maintype, subtype = ctype.split('/', 1)
85 if maintype == 'text':
86 with open(path, "r", encoding=encoding) as fp:
87 st = fp.read()
88 msg = MIMEText(st, _subtype=subtype)
89 elif maintype == 'image':
90 with open(path, 'rb') as fp:
91 msg = MIMEImage(fp.read(), _subtype=subtype)
92 elif maintype == 'audio':
93 with open(path, 'rb') as fp:
94 msg = MIMEAudio(fp.read(), _subtype=subtype)
95 else:
96 with open(path, 'rb') as fp:
97 msg = MIMEBase(maintype, subtype)
98 msg.set_payload(fp.read())
99 encoders.encode_base64(msg)
101 msg.add_header('Content-Disposition', 'attachment', filename=filename)
102 outer.attach(msg)
104 composed = outer.as_string()
105 return composed
108def create_smtp_server(host, username, password):
109 """
110 Creates a SMTP server and log into it.
112 @param host something like ``smtp.gmail.com:587``
113 @param username username
114 @param login login
115 @return server
117 You should call server.quit() when the server is not used anymore.
118 """
119 if host == "gmail":
120 host = 'smtp.gmail.com:587'
122 server = smtplib.SMTP(host)
123 server.starttls()
124 server.login(username, password)
125 return server
128def send_email(server, fr, to, subject, body_html=None, body_text=None,
129 attachements=None, delay_sending=False, cc=None, bcc=None):
130 """
131 Sends an email as a string.
133 @param server result from function @see fn create_smtp_server
134 @param fr from
135 @param to destination (or list of receivers)
136 @param cc cc
137 @param bcc bcc
138 @param subject subject
139 @param body_text body text
140 @param body_html body html
141 @param attachements list of files to attach to the email
142 @param delay_sending if True, the function returns a function which will send the mail if
143 executed
144 @return function (if *delay_sending*), return of
145 `sendmail <https://docs.python.org/3.5/library/smtplib.html#smtplib.SMTP.sendmail>`_
146 otherwise
148 .. exref::
149 :title: Send an email
151 ::
153 from pymmails import create_smtp_server, send_email
154 server = create_smtp_server("gmail", "somebody", "pwd")
155 send_email(server, "from.sender@gmail.com",
156 "to.receiver@else.com", "subject",
157 attachements = [ os.path.abspath(__file__) ])
158 server.quit()
160 .. faqref::
161 :title: Gmail does not allow to send or get emails with Python
163 By default, a Gmail account does not enable the IMAP access.
164 That explains why it is not possible to send or get messages from Gmail.
165 The following page
166 `Get started with IMAP and POP3 <https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018>`_
167 explains how to enable that option.
168 """
169 astring = compose_email(fr, to, subject, body_html=body_html, cc=cc, bcc=bcc,
170 body_text=body_text, attachements=attachements)
171 to = list(to) if isinstance(to, (list, tuple)) else [to]
172 if isinstance(cc, str):
173 to.append(cc)
174 elif isinstance(cc, (list, tuple)):
175 to.extend(cc)
176 if isinstance(bcc, str):
177 to.append(bcc)
178 elif isinstance(bcc, (list, tuple)):
179 to.extend(bcc)
181 if delay_sending:
182 def f(fr=fr, to=to, astring=astring): # pylint: disable=W0102
183 "local function"
184 try:
185 server.sendmail(fr, to, astring)
186 except Exception as e:
187 raise Exception(
188 "Unable to send mail to {0} from '{1}'".format(to, fr)) from e
189 return f
190 else:
191 try:
192 return server.sendmail(fr, to, astring)
193 except Exception as e:
194 raise Exception(
195 "Unable to send mail to {0} from '{1}'".format(to, fr)) from e