Hide keyboard shortcuts

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 

7 

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 

15 

16COMMASPACE = ', ' 

17 

18 

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. 

22 

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 

32 

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 = [] 

44 

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) 

54 

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 

64 

65 outer.preamble = 'prepared by pymmails.\n' 

66 

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" 

74 

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

79 

80 ctype, encoding = mimetypes.guess_type(path) 

81 if ctype is None or encoding is not None: 

82 ctype = 'application/octet-stream' 

83 

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) 

100 

101 msg.add_header('Content-Disposition', 'attachment', filename=filename) 

102 outer.attach(msg) 

103 

104 composed = outer.as_string() 

105 return composed 

106 

107 

108def create_smtp_server(host, username, password): 

109 """ 

110 Creates a SMTP server and log into it. 

111 

112 @param host something like ``smtp.gmail.com:587`` 

113 @param username username 

114 @param login login 

115 @return server 

116 

117 You should call server.quit() when the server is not used anymore. 

118 """ 

119 if host == "gmail": 

120 host = 'smtp.gmail.com:587' 

121 

122 server = smtplib.SMTP(host) 

123 server.starttls() 

124 server.login(username, password) 

125 return server 

126 

127 

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. 

132 

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 

147 

148 .. exref:: 

149 :title: Send an email 

150 

151 :: 

152 

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

159 

160 .. faqref:: 

161 :title: Gmail does not allow to send or get emails with Python 

162 

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) 

180 

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