Benjamin-Loison / cpython

The Python programming language
https://www.python.org/
Other
0 stars 0 forks source link

Send email #46

Open Benjamin-Loison opened 3 weeks ago

Benjamin-Loison commented 3 weeks ago

The Stack Overflow answer 6270987

Python script: ```python # Import smtplib for the actual sending function import smtplib # Import the email modules we'll need from email.mime.text import MIMEText msg = MIMEText('My body') msg['Subject'] = 'My subject' FROM = 'algorithm@lemnoslife.com' msg['From'] = FROM TO = 'benjamin.loison@CENSORED.fr' msg['To'] = TO # Send the message via our own SMTP server, but don't include the # envelope header. s = smtplib.SMTP('localhost') s.sendmail(FROM, [TO], msg.as_string()) s.quit() ```
On my Linux Mint 22 Cinnamon Framework 13: ``` Traceback (most recent call last): File "", line 16, in s = smtplib.SMTP('localhost') File "/usr/lib/python3.12/smtplib.py", line 255, in __init__ (code, msg) = self.connect(host, port) File "/usr/lib/python3.12/smtplib.py", line 341, in connect self.sock = self._get_socket(host, port, self.timeout) File "/usr/lib/python3.12/smtplib.py", line 312, in _get_socket return socket.create_connection((host, port), timeout, File "/usr/lib/python3.12/socket.py", line 852, in create_connection raise exceptions[0] File "/usr/lib/python3.12/socket.py", line 837, in create_connection sock.connect(sa) ConnectionRefusedError: [Errno 111] Connection refused ```

On LemnosLife VPS no error or output is raised but do not receive an email on my personal email in a few minutes, even in spam.

Benjamin-Loison commented 3 weeks ago

s.sendmail returns {}.

Benjamin-Loison commented 3 weeks ago

The Stack Overflow answer 64890 solves this issue for my current needs it seems.

Benjamin-Loison commented 3 weeks ago

I verified with this method that can specify another email than the actual one for From for both Orange and Gmail.

Benjamin-Loison commented 2 days ago

Is:

conn.set_debuglevel(False)

useless?

help(conn.set_debuglevel)
Output: ``` Help on method set_debuglevel in module smtplib: set_debuglevel(debuglevel) method of smtplib.SMTP_SSL instance Set the debug output level. A non-false value results in debug messages for connection and for all messages sent to and received from the server. ```
Benjamin-Loison commented 2 days ago
help(conn)
Output: ``` Help on SMTP_SSL in module smtplib object: class SMTP_SSL(SMTP) | SMTP_SSL(host='', port=0, local_hostname=None, *, timeout=, source_address=None, context=None) | | This is a subclass derived from SMTP that connects over an SSL | encrypted socket (to use this class you need a socket module that was | compiled with SSL support). If host is not specified, '' (the local | host) is used. If port is omitted, the standard SMTP-over-SSL port | (465) is used. local_hostname and source_address have the same meaning | as they do in the SMTP class. context also optional, can contain a | SSLContext. | | Method resolution order: | SMTP_SSL | SMTP | builtins.object | | Methods defined here: | | __init__(self, host='', port=0, local_hostname=None, *, timeout=, source_address=None, context=None) | Initialize a new instance. | | If specified, `host` is the name of the remote host to which to | connect. If specified, `port` specifies the port to which to connect. | By default, smtplib.SMTP_PORT is used. If a host is specified the | connect method is called, and if it returns anything other than a | success code an SMTPConnectError is raised. If specified, | `local_hostname` is used as the FQDN of the local host in the HELO/EHLO | command. Otherwise, the local hostname is found using | socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host, | port) for the socket to bind to as its source address before | connecting. If the host is '' and port is 0, the OS default behavior | will be used. | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | default_port = 465 | | ---------------------------------------------------------------------- | Methods inherited from SMTP: | | __enter__(self) | | __exit__(self, *args) | | auth(self, mechanism, authobject, *, initial_response_ok=True) | Authentication command - requires response processing. | | 'mechanism' specifies which authentication mechanism is to | be used - the valid values are those listed in the 'auth' | element of 'esmtp_features'. | | 'authobject' must be a callable object taking a single argument: | | data = authobject(challenge) | | It will be called to process the server's challenge response; the | challenge argument it is passed will be a bytes. It should return | an ASCII string that will be base64 encoded and sent to the server. | | Keyword arguments: | - initial_response_ok: Allow sending the RFC 4954 initial-response | to the AUTH command, if the authentication methods supports it. | | auth_cram_md5(self, challenge=None) | Authobject to use with CRAM-MD5 authentication. Requires self.user | and self.password to be set. | | auth_login(self, challenge=None) | Authobject to use with LOGIN authentication. Requires self.user and | self.password to be set. | | auth_plain(self, challenge=None) | Authobject to use with PLAIN authentication. Requires self.user and | self.password to be set. | | close(self) | Close the connection to the SMTP server. | | connect(self, host='localhost', port=0, source_address=None) | Connect to a host on a given port. | | If the hostname ends with a colon (`:') followed by a number, and | there is no port specified, that suffix will be stripped off and the | number interpreted as the port number to use. | | Note: This method is automatically invoked by __init__, if a host is | specified during instantiation. | | data(self, msg) | SMTP 'DATA' command -- sends message data to server. | | Automatically quotes lines beginning with a period per rfc821. | Raises SMTPDataError if there is an unexpected reply to the | DATA command; the return value from this method is the final | response code received when the all data is sent. If msg | is a string, lone '\r' and '\n' characters are converted to | '\r\n' characters. If msg is bytes, it is transmitted as is. | | docmd(self, cmd, args='') | Send a command, and return its response code. | | ehlo(self, name='') | SMTP 'ehlo' command. | Hostname to send for this command defaults to the FQDN of the local | host. | | ehlo_or_helo_if_needed(self) | Call self.ehlo() and/or self.helo() if needed. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | | expn(self, address) | SMTP 'expn' command -- expands a mailing list. | | getreply(self) | Get a reply from the server. | | Returns a tuple consisting of: | | - server response code (e.g. '250', or such, if all goes well) | Note: returns -1 if it can't read response code. | | - server response string corresponding to response code (multiline | responses are converted to a single, multiline string). | | Raises SMTPServerDisconnected if end-of-file is reached. | | has_extn(self, opt) | Does the server support a given SMTP service extension? | | helo(self, name='') | SMTP 'helo' command. | Hostname to send for this command defaults to the FQDN of the local | host. | | help(self, args='') | SMTP 'help' command. | Returns help text from server. | | login(self, user, password, *, initial_response_ok=True) | Log in on an SMTP server that requires authentication. | | The arguments are: | - user: The user name to authenticate with. | - password: The password for the authentication. | | Keyword arguments: | - initial_response_ok: Allow sending the RFC 4954 initial-response | to the AUTH command, if the authentication methods supports it. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | This method will return normally if the authentication was successful. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | SMTPAuthenticationError The server didn't accept the username/ | password combination. | SMTPNotSupportedError The AUTH command is not supported by the | server. | SMTPException No suitable authentication method was | found. | | mail(self, sender, options=()) | SMTP 'mail' command -- begins mail xfer session. | | This method may raise the following exceptions: | | SMTPNotSupportedError The options parameter includes 'SMTPUTF8' | but the SMTPUTF8 extension is not supported by | the server. | | noop(self) | SMTP 'noop' command -- doesn't do anything :> | | putcmd(self, cmd, args='') | Send a command to the server. | | quit(self) | Terminate the SMTP session. | | rcpt(self, recip, options=()) | SMTP 'rcpt' command -- indicates 1 recipient for this mail. | | rset(self) | SMTP 'rset' command -- resets session. | | send(self, s) | Send `s' to the server. | | send_message(self, msg, from_addr=None, to_addrs=None, mail_options=(), rcpt_options=()) | Converts message to a bytestring and passes it to sendmail. | | The arguments are as for sendmail, except that msg is an | email.message.Message object. If from_addr is None or to_addrs is | None, these arguments are taken from the headers of the Message as | described in RFC 2822 (a ValueError is raised if there is more than | one set of 'Resent-' headers). Regardless of the values of from_addr and | to_addr, any Bcc field (or Resent-Bcc field, when the Message is a | resent) of the Message object won't be transmitted. The Message | object is then serialized using email.generator.BytesGenerator and | sendmail is called to transmit the message. If the sender or any of | the recipient addresses contain non-ASCII and the server advertises the | SMTPUTF8 capability, the policy is cloned with utf8 set to True for the | serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. | If the server does not support SMTPUTF8, an SMTPNotSupported error is | raised. Otherwise the generator is called without modifying the | policy. | | sendmail(self, from_addr, to_addrs, msg, mail_options=(), rcpt_options=()) | This command performs an entire mail transaction. | | The arguments are: | - from_addr : The address sending this mail. | - to_addrs : A list of addresses to send this mail to. A bare | string will be treated as a list with 1 address. | - msg : The message to send. | - mail_options : List of ESMTP options (such as 8bitmime) for the | mail command. | - rcpt_options : List of ESMTP options (such as DSN commands) for | all the rcpt commands. | | msg may be a string containing characters in the ASCII range, or a byte | string. A string is encoded to bytes using the ascii codec, and lone | \r and \n characters are converted to \r\n characters. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. If the server does ESMTP, message size | and each of the specified options will be passed to it. If EHLO | fails, HELO will be tried and ESMTP options suppressed. | | This method will return normally if the mail is accepted for at least | one recipient. It returns a dictionary, with one entry for each | recipient that was refused. Each entry contains a tuple of the SMTP | error code and the accompanying error message sent by the server. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | SMTPRecipientsRefused The server rejected ALL recipients | (no mail was sent). | SMTPSenderRefused The server didn't accept the from_addr. | SMTPDataError The server replied with an unexpected | error code (other than a refusal of | a recipient). | SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' | but the SMTPUTF8 extension is not supported by | the server. | | Note: the connection will be open even after an exception is raised. | | Example: | | >>> import smtplib | >>> s=smtplib.SMTP("localhost") | >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] | >>> msg = '''\ | ... From: Me@my.org | ... Subject: testin'... | ... | ... This is a test ''' | >>> s.sendmail("me@my.org",tolist,msg) | { "three@three.org" : ( 550 ,"User unknown" ) } | >>> s.quit() | | In the above example, the message was accepted for delivery to three | of the four addresses, and one was rejected, with the error code | 550. If all addresses are accepted, then the method will return an | empty dictionary. | | set_debuglevel(self, debuglevel) | Set the debug output level. | | A non-false value results in debug messages for connection and for all | messages sent to and received from the server. | | starttls(self, *, context=None) | Puts the connection to the SMTP server into TLS mode. | | If there has been no previous EHLO or HELO command this session, this | method tries ESMTP EHLO first. | | If the server supports TLS, this will encrypt the rest of the SMTP | session. If you provide the context parameter, | the identity of the SMTP server and client can be checked. This, | however, depends on whether the socket module really checks the | certificates. | | This method may raise the following exceptions: | | SMTPHeloError The server didn't reply properly to | the helo greeting. | | verify(self, address) | SMTP 'verify' command -- checks for address validity. | | vrfy = verify(self, address) | | ---------------------------------------------------------------------- | Data descriptors inherited from SMTP: | | __dict__ | dictionary for instance variables | | __weakref__ | list of weak references to the object | | ---------------------------------------------------------------------- | Data and other attributes inherited from SMTP: | | debuglevel = 0 | | does_esmtp = False | | ehlo_msg = 'ehlo' | | ehlo_resp = None | | file = None | | helo_resp = None | | sock = None ```
help(conn)
Output: ``` ... | debuglevel = 0 ... ```
grep -in 'debug' output.txt
Output: ``` 284: | set_debuglevel(self, debuglevel) 285: | Set the debug output level. 287: | A non-false value results in debug messages for connection and for all 324: | debuglevel = 0 ```
Benjamin-Loison commented 2 days ago
    conn = SMTP(SMTP_SERVER)
    print(f'{conn.debuglevel=}')
conn.debuglevel=0

so it seems pretty useless.

Benjamin-Loison commented 2 days ago
Python script: ```python import sys # this invokes the secure SMTP protocol (port 465, uses SSL) from smtplib import SMTP_SSL as SMTP from email.mime.text import MIMEText SMTP_SERVER = 'smtp.CENSORED.fr' SENDER = 'benjamin.loison@CENSORED.fr' DESTINATION = 'benjamin.loison@CENSORED.fr' USERNAME = 'CENSORED' PASSWORD = 'CENSORED' # typical values for text_subtype are plain, html, xml TEXT_SUBTYPE = 'plain' content = '''\ Test message ''' subject = 'Sent from Python' try: msg = MIMEText(content, TEXT_SUBTYPE) msg['Subject'] = subject # some SMTP servers will do this automatically, not all msg['From'] = SENDER conn = SMTP(SMTP_SERVER) conn.login(USERNAME, PASSWORD) try: sendmailResult = conn.sendmail(SENDER, DESTINATION, msg.as_string()) if sendmailResult != {}: sys.exit(f'{sendmailResult=}') except: conn.quit() sys.exit('Sendmail error!') finally: conn.quit() except: sys.exit('mail failed; CUSTOM_ERROR') ```
Benjamin-Loison commented 2 days ago
try:
    print('Try')
    1 / 0
except:
    print('Except')
finally:
    print('Finally')
Try
Except
Finally
Benjamin-Loison commented 1 day ago

Can't send multiple emails without SMTP(SMTP_SERVER) and .login(USERNAME, PASSWORD)?

Benjamin-Loison commented 1 day ago
Python script sending multiple emails thanks to a function: ```python import sys # this invokes the secure SMTP protocol (port 465, uses SSL) from smtplib import SMTP_SSL as SMTP from email.mime.text import MIMEText SMTP_SERVER = 'smtp.CENSORED.fr' SENDER = 'benjamin.loison@CENSORED.fr' USERNAME = 'bloison' PASSWORD = 'CENSORED' # typical values for text_subtype are plain, html, xml TEXT_SUBTYPE = 'plain' SUBJECT = 'Email subject' def sendEmail(email, name): content = f'''Hello {name}!''' msg = MIMEText(content, TEXT_SUBTYPE) msg['Subject'] = SUBJECT # some SMTP servers will do this automatically, not all msg['From'] = SENDER try: conn = SMTP(SMTP_SERVER) conn.login(USERNAME, PASSWORD) sendmailResult = conn.sendmail(SENDER, email, msg.as_string()) if sendmailResult != {}: sys.exit(f'{sendmailResult=}') except: # [Benjamin-Loison/cpython/issues/54](https://github.com/Benjamin-Loison/cpython/issues/54) conn.quit() sys.exit('Sendmail error!') finally: conn.quit() USERS = [ { 'name': 'User A', 'email': 'user.a@example.com', }, { 'name': 'User B', 'email': 'user.b@example.com', }, ] for user in USERS: print(f'Sending email to {user["name"]}...') sendEmail(user['email'], user['name']) ```

I verified this script with my school email address for the sender and @orange.fr and @protonmail.com recipients.