55
55
from email .base64mime import body_encode as encode_base64
56
56
57
57
__all__ = ["SMTPException" , "SMTPNotSupportedError" , "SMTPServerDisconnected" , "SMTPResponseException" ,
58
- "SMTPSenderRefused" , "SMTPRecipientsRefused" , "SMTPDataError" ,
58
+ "SMTPSenderRefused" , "SMTPRecipientsRefused" , "SMTPDataError" , "LMTPDataError" ,
59
59
"SMTPConnectError" , "SMTPHeloError" , "SMTPAuthenticationError" ,
60
60
"quoteaddr" , "quotedata" , "SMTP" ]
61
61
@@ -129,6 +129,18 @@ def __init__(self, recipients):
129
129
class SMTPDataError (SMTPResponseException ):
130
130
"""The SMTP server didn't accept the data."""
131
131
132
+ class LMTPDataError (SMTPResponseException ):
133
+ """The LMTP server didn't accept the data.
134
+
135
+ The errors for each recipient are accessible through the attribute
136
+ 'recipients', which is a dictionary of exactly the same sort as
137
+ SMTP.sendmail() returns.
138
+ """
139
+
140
+ def __init__ (self , recipients ):
141
+ self .recipients = recipients
142
+ self .args = (recipients ,)
143
+
132
144
class SMTPConnectError (SMTPResponseException ):
133
145
"""Error during connection establishment."""
134
146
@@ -827,6 +839,9 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
827
839
SMTPDataError The server replied with an unexpected
828
840
error code (other than a refusal of
829
841
a recipient).
842
+ LMTPDataError The server replied with an unexpected
843
+ error code (other than a refusal of
844
+ a recipient) for ALL recipients.
830
845
SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8'
831
846
but the SMTPUTF8 extension is not supported by
832
847
the server.
@@ -869,12 +884,15 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
869
884
else :
870
885
self ._rset ()
871
886
raise SMTPSenderRefused (code , resp , from_addr )
887
+ rcpts = []
872
888
senderrs = {}
873
889
if isinstance (to_addrs , str ):
874
890
to_addrs = [to_addrs ]
875
891
for each in to_addrs :
876
892
(code , resp ) = self .rcpt (each , rcpt_options )
877
- if (code != 250 ) and (code != 251 ):
893
+ if (code == 250 ) or (code == 251 ):
894
+ rcpts .append (each )
895
+ else :
878
896
senderrs [each ] = (code , resp )
879
897
if code == 421 :
880
898
self .close ()
@@ -883,13 +901,26 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
883
901
# the server refused all our recipients
884
902
self ._rset ()
885
903
raise SMTPRecipientsRefused (senderrs )
886
- (code , resp ) = self .data (msg )
887
- if code != 250 :
888
- if code == 421 :
889
- self .close ()
890
- else :
904
+ if hasattr (self , 'multi_data' ):
905
+ rcpt_errs_size = len (senderrs )
906
+ for rcpt , code , resp in self .multi_data (msg , rcpts ):
907
+ if code != 250 :
908
+ senderrs [rcpt ] = (code , resp )
909
+ if code == 421 :
910
+ self .close ()
911
+ raise LMTPDataError (senderrs )
912
+ if rcpt_errs_size + len (rcpts ) == len (senderrs ):
913
+ # the server refused all our recipients
891
914
self ._rset ()
892
- raise SMTPDataError (code , resp )
915
+ raise LMTPDataError (senderrs )
916
+ else :
917
+ code , resp = self .data (msg )
918
+ if code != 250 :
919
+ if code == 421 :
920
+ self .close ()
921
+ else :
922
+ self ._rset ()
923
+ raise SMTPDataError (code , resp )
893
924
#if we got here then somebody got our mail
894
925
return senderrs
895
926
@@ -1097,6 +1128,27 @@ def connect(self, host='localhost', port=0, source_address=None):
1097
1128
self ._print_debug ('connect:' , msg )
1098
1129
return (code , msg )
1099
1130
1131
+ def multi_data (self , msg , rcpts ):
1132
+ """SMTP 'DATA' command -- sends message data to server
1133
+
1134
+ Differs from data in that it yields multiple results for each
1135
+ recipient. This is necessary for LMTP processing and different
1136
+ from SMTP processing.
1137
+
1138
+ Automatically quotes lines beginning with a period per rfc821.
1139
+ Raises SMTPDataError if there is an unexpected reply to the
1140
+ DATA command; the return value from this method is the final
1141
+ response code received when the all data is sent. If msg
1142
+ is a string, lone '\\ r' and '\\ n' characters are converted to
1143
+ '\\ r\\ n' characters. If msg is bytes, it is transmitted as is.
1144
+ """
1145
+ yield (rcpts [0 ],) + super ().data (msg )
1146
+ for rcpt in rcpts [1 :]:
1147
+ (code , msg ) = self .getreply ()
1148
+ if self .debuglevel > 0 :
1149
+ self ._print_debug ('connect:' , msg )
1150
+ yield (rcpt , code , msg )
1151
+
1100
1152
1101
1153
# Test the sendmail method, which tests most of the others.
1102
1154
# Note: This always sends to localhost.
0 commit comments