The following are 30
code examples of smtplib.SMTPException().
You can vote up the ones you like or vote down the ones you don’t like,
and go to the original project or source file by following the links above each example.
You may also want to check out all available functions/classes of the module
smtplib
, or try the search function
.
Example #1
def close(self): """Closes the connection to the email server.""" if self.connection is None: return try: try: self.connection.quit() except (ssl.SSLError, smtplib.SMTPServerDisconnected): # This happens when calling quit() on a TLS connection # sometimes, or when the connection was already disconnected # by the server. self.connection.close() except smtplib.SMTPException: if self.fail_silently: return raise finally: self.connection = None
Example #2
def _try_send_message(self, *args, **kwargs): message_sent = False while not message_sent and not self.should_stop.is_set(): for i in range(0, 3): try: self.send_message(*args, **kwargs) message_sent = True break except smtplib.SMTPServerDisconnected: self.logger.warning('failed to send message, the server has been disconnected') self.tab_notify_status('Failed to send message, the server has been disconnected') self.tab_notify_status('Sleeping for 5 seconds before attempting to reconnect') if self._sleep(5): break self.smtp_connection = None self.server_smtp_reconnect() except smtplib.SMTPException as error: self.tab_notify_status("Failed to send message (exception: {0})".format(error.__class__.__name__)) self.logger.warning("failed to send message (exception: smtplib.{0})".format(error.__class__.__name__)) self._sleep((i + 1) ** 2) if not message_sent: self.server_smtp_disconnect() if not self.process_pause(True): return False self.server_smtp_reconnect() return True
Example #3
def user_post_save(sender, instance, created, **kwargs): if created: User = get_user_model() recipient_list = [item[0] for item in User.objects.filter(is_superuser=True).values_list('email')] notification_type = NotificationType.USER_CREATED context = {'user': instance} if len(recipient_list) == 0: logger.warning("No recipients for notification type '%s'" % notification_type, extra={'event': instance}) return try: rendered_notification = render_notification_template(notification_type, context) except NotificationTemplateException as e: logger.error(e, exc_info=True) return try: send_mail( rendered_notification['subject'], rendered_notification['body'], 'noreply@%s' % Site.objects.get_current().domain, recipient_list, html_message=rendered_notification['html_body'] ) except SMTPException as e: logger.error(e, exc_info=True, extra={'user': instance})
Example #4
def _send_notification(self, notification_type, recipient_list, request=None): if len(recipient_list) == 0: logger.warning("No recipients for notification type '%s'" % notification_type, extra={'event': self}) return context = {'event': self} try: rendered_notification = render_notification_template(notification_type, context) except NotificationTemplateException as e: logger.error(e, exc_info=True, extra={'request': request}) return try: send_mail( rendered_notification['subject'], rendered_notification['body'], 'noreply@%s' % Site.objects.get_current().domain, recipient_list, html_message=rendered_notification['html_body'] ) except SMTPException as e: logger.error(e, exc_info=True, extra={'request': request, 'event': self})
Example #5
def test_wraps_mail_server_exceptions(self, mailer, smtp): import smtplib from dallinger.notifications import MessengerError smtp.login.side_effect = smtplib.SMTPException("Boom!") with pytest.raises(MessengerError) as ex_info: mailer.send( subject="Some subject", sender="from@example.com", recipients=["to@example.com"], body="Somenbody", ) assert ex_info.match("SMTP error") smtp.login.side_effect = Exception("Boom!") with pytest.raises(MessengerError) as ex_info: mailer.send( subject="Some subject", sender="from@example.com", recipients=["to@example.com"], body="Somenbody", ) assert ex_info.match("Unknown error")
Example #6
def sender(request, subject, template_name, context, to): site = get_current_site(request) context.update({'site_name': site.name, 'domain': site.domain, 'protocol': 'https' if request.is_secure() else 'http'}) message = render_to_string(template_name, context) from_email = "%(site_name)s <%(name)s@%(domain)s>" % {'name': "noreply", 'domain': site.domain, 'site_name': site.name} if len(to) > 1: kwargs = {'bcc': to, } else: kwargs = {'to': to, } email = EmailMessage(subject, message, from_email, **kwargs) try: email.send() except SMTPException as err: logger.error(err)
Example #7
def is_send_email(to_list, subject, body): """ Tries to send email. If email is sent successfully, returns True else False If running app in Debug mode, do not try to send email :param to_list: :param subject: :param body: :return: Is sending email success """ if DEBUG: return True try: send_mail(subject, body, DEFAULT_EMAIL_SENDER, to_list, fail_silently=False) except SMTPException: return False return True
Example #8
def starttls(self, keyfile=None, certfile=None): self.ehlo_or_helo_if_needed() if not self.has_extn("starttls"): raise smtplib.SMTPException("server doesn't support STARTTLS") response, reply = self.docmd("STARTTLS") if response == 220: with ca_certs(self._ca_certs) as certs: self.sock = ssl.wrap_socket( self.sock, certfile=certfile, keyfile=keyfile, ca_certs=certs, cert_reqs=ssl.CERT_REQUIRED ) cert = self.sock.getpeercert() match_hostname(cert, self._host) self.file = smtplib.SSLFakeFile(self.sock) self.helo_resp = None self.ehlo_resp = None self.esmtp_features = {} self.does_esmtp = 0 return response, reply
Example #9
def _connect(self, host, port, retry_interval=60.0): server = None while server is None: self.log.info(u"Connecting to SMTP server {0!r} port {1}".format(host, port)) try: server = yield idiokit.thread( SMTP, host, port, ca_certs=self.smtp_ca_certs, timeout=self.smtp_connection_timeout ) except (socket.error, smtplib.SMTPException) as exc: self.log.error(u"Failed connecting to SMTP server: {0}".format(utils.format_exception(exc))) else: self.log.info(u"Connected to the SMTP server") break self.log.info(u"Retrying SMTP connection in {0:.2f} seconds".format(retry_interval)) yield idiokit.sleep(retry_interval) idiokit.stop(server)
Example #10
def send_confirmation_email(user: PolarisUser, account: PolarisStellarAccount): """ Sends a confirmation email to user.email In a real production deployment, you would never want to send emails as part of the request/response cycle. Instead, use a job queue service like Celery. This reference server is not intended to handle heavy traffic so we are making an exception here. """ args = urlencode({"token": account.confirmation_token, "email": user.email}) url = f"{settings.HOST_URL}{reverse('confirm_email')}?{args}" try: send_mail( _("Reference Anchor Server: Confirm Email"), # email body if the HTML is not rendered _("Confirm your email by pasting this URL in your browser: %s") % url, server_settings.EMAIL_HOST_USER, [user.email], html_message=render_to_string( "confirmation_email.html", {"first_name": user.first_name, "confirmation_url": url}, ), ) except SMTPException as e: logger.error(f"Unable to send email to {user.email}: {e}")
Example #11
def test_bad_email_configuration_for_accounts_home(self) -> None: """ Make sure we redirect for SMTP errors. """ email = self.nonreg_email('newguy') smtp_mock = patch( 'zerver.views.registration.send_confirm_registration_email', side_effect=smtplib.SMTPException('uh oh'), ) error_mock = patch('logging.error') with smtp_mock, error_mock as err: result = self.client_post('/accounts/home/', {'email': email}) self._assert_redirected_to(result, '/config-error/smtp') self.assertEqual( err.call_args_list[0][0], ('Error in accounts_home: %s', 'uh oh'), )
Example #12
def test_bad_email_configuration_for_create_realm(self) -> None: """ Make sure we redirect for SMTP errors. """ email = self.nonreg_email('newguy') smtp_mock = patch( 'zerver.views.registration.send_confirm_registration_email', side_effect=smtplib.SMTPException('uh oh'), ) error_mock = patch('logging.error') with smtp_mock, error_mock as err: result = self.client_post('/new/', {'email': email}) self._assert_redirected_to(result, '/config-error/smtp') self.assertEqual( err.call_args_list[0][0], ('Error in create_realm: %s', 'uh oh'), )
Example #13
def test_smtplib_init_fail(mock_smtplib): """ API: Test exception handling when calling smtplib.SMTP() """ # Disable Throttling to speed testing plugins.NotifyBase.request_rate_per_sec = 0 obj = Apprise.instantiate( 'mailto://user:pass@gmail.com', suppress_exceptions=False) assert isinstance(obj, plugins.NotifyEmail) # Support Exception handling of smtplib.SMTP mock_smtplib.side_effect = RuntimeError('Test') assert obj.notify( body='body', title='test', notify_type=NotifyType.INFO) is False # A handled and expected exception mock_smtplib.side_effect = smtplib.SMTPException('Test') assert obj.notify( body='body', title='test', notify_type=NotifyType.INFO) is False
Example #14
def _deliver(self, mailfrom, rcpttos, data): import smtplib refused = {} try: s = smtplib.SMTP() s.connect(self._remoteaddr[0], self._remoteaddr[1]) try: refused = s.sendmail(mailfrom, rcpttos, data) finally: s.quit() except smtplib.SMTPRecipientsRefused as e: print('got SMTPRecipientsRefused', file=DEBUGSTREAM) refused = e.recipients except (OSError, smtplib.SMTPException) as e: print('got', e.__class__, file=DEBUGSTREAM) # All recipients were refused. If the exception had an associated # error code, use it. Otherwise,fake it with a non-triggering # exception code. errcode = getattr(e, 'smtp_code', -1) errmsg = getattr(e, 'smtp_error', 'ignore') for r in rcpttos: refused[r] = (errcode, errmsg) return refused
Example #15
def _deliver(self, mailfrom, rcpttos, data): import smtplib refused = {} try: s = smtplib.SMTP() s.connect(self._remoteaddr[0], self._remoteaddr[1]) try: refused = s.sendmail(mailfrom, rcpttos, data) finally: s.quit() except smtplib.SMTPRecipientsRefused as e: print('got SMTPRecipientsRefused', file=DEBUGSTREAM) refused = e.recipients except (OSError, smtplib.SMTPException) as e: print('got', e.__class__, file=DEBUGSTREAM) # All recipients were refused. If the exception had an associated # error code, use it. Otherwise,fake it with a non-triggering # exception code. errcode = getattr(e, 'smtp_code', -1) errmsg = getattr(e, 'smtp_error', 'ignore') for r in rcpttos: refused[r] = (errcode, errmsg) return refused
Example #16
def _send(self, connection, email_message): """A helper method that does the actual sending.""" if not email_message.recipients(): return False from_email = sanitize_address(email_message.from_email, email_message.encoding) recipients = [sanitize_address(addr, email_message.encoding) for addr in email_message.recipients()] message = email_message.message() try: connection.sendmail(from_email, recipients, message.as_bytes(linesep='rn')) return 1 except smtplib.SMTPException: self.app.logger.exception('Error while sending message', extra={'mail': True}) return 0
Example #17
def _deliver(self, mailfrom, rcpttos, data): import smtplib refused = {} try: s = smtplib.SMTP() s.connect(self._remoteaddr[0], self._remoteaddr[1]) try: refused = s.sendmail(mailfrom, rcpttos, data) finally: s.quit() except smtplib.SMTPRecipientsRefused as e: print('got SMTPRecipientsRefused', file=DEBUGSTREAM) refused = e.recipients except (OSError, smtplib.SMTPException) as e: print('got', e.__class__, file=DEBUGSTREAM) # All recipients were refused. If the exception had an associated # error code, use it. Otherwise,fake it with a non-triggering # exception code. errcode = getattr(e, 'smtp_code', -1) errmsg = getattr(e, 'smtp_error', 'ignore') for r in rcpttos: refused[r] = (errcode, errmsg) return refused
Example #18
def send_email_report(report): from_email = gmail_login to_email = [gmail_dest] # ['me@gmail.com', 'bill@gmail.com'] subject = "Virus Total Hunting Report - " + str(now) text = report message = 'Subject: {}nn{}'.format(subject, text) try: server = smtplib.SMTP_SSL(smtp_serv, smtp_port) server.ehlo() server.login(from_email, gmail_pass) # Send the mail server.sendmail(from_email, to_email, message) server.quit() print("[*] Report have been sent to your email!") except smtplib.SMTPException as e: print("[!] SMTP error: " + str(e)) sys.exit() # Connect to VT
Example #19
def trigger(self, event, probe, action_config_d): email_from = self.config_d['email_from'] recipients = [] for group_name in action_config_d['groups']: for contact_d in contact_groups[group_name]: contact_email = contact_d.get('email', None) if contact_email: recipients.append(contact_email) if not recipients: return msg = MIMEText(event.get_notification_body(probe)) msg['Subject'] = ' - '.join(event.get_notification_subject(probe).splitlines()) msg['From'] = email_from msg['To'] = ",".join(recipients) try: self._open() self.conn.sendmail(email_from, recipients, msg.as_string()) self._close() except SMTPException: logger.exception("SMTP exception")
Example #20
def send_admin_notification_callback(sender, **kwargs): """ Callback for notifying admin of a user in the 'pending' state. """ user = kwargs['user'] studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '') context = {'user_name': user.username, 'user_email': user.email} subject = render_to_string('emails/course_creator_admin_subject.txt', context) subject = ''.join(subject.splitlines()) message = render_to_string('emails/course_creator_admin_user_pending.txt', context) try: send_mail( subject, message, studio_request_email, [studio_request_email], fail_silently=False ) except SMTPException: log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email)
Example #21
def change_activity_status(request, event_slug, activity_id, status, justification=None): event = get_object_or_404(Event, event_slug=event_slug) activity = get_object_or_404(Activity, id=activity_id) activity.status = status activity.start_date = None activity.end_date = None activity.room = None activity.justification = justification activity.save() try: utils_email.send_activity_email(event, activity, justification) except SMTPException as error: logger.error(error) messages.error(request, _("The email couldn't sent successfully, please retry later or contact a organizer")) safe_continue = reverse("activity_detail", args=[event_slug, activity.pk]) return goto_next_or_continue(request.GET.get('next'), safe_continue)
Example #22
def login(self, username, password): try: return self.server.login(username, password) except smtplib.SMTPAuthenticationError: logger.error('The server did not accept the username/password combination.') return False except smtplib.SMTPNotSupportedError: logger.error('The AUTH command is not supported by the server.') exit(1) except smtplib.SMTPException: logger.error('Encountered an error during authentication.') exit(1)
Example #23
def send_qq_mail(from_addr, password, to_addr, content, subject='', files=None, host=('smtp.qq.com', 465)): """这个一个邮箱发送函数.默认是qq邮箱 :param from_addr: 发送方邮箱 :param password: 填入发送方邮箱的授权码 :param to_addr: 收件人为多个收件人,通过;为间隔的字符串,比如: xx@qq.com;yy@qq.com :param content: 正文 :param subject: 主题 :param files: 附加 :param host: 邮件传输协议 :return: bool类型.打印成功和失败 """ text = MIMEText(content, _charset='utf-8') m = MIMEMultipart() if files: import os from email import encoders file_name = os.path.basename(files) # 获得文件名字 file = MIMEApplication(open(files, 'rb').read()) file.add_header('Content-Disposition', 'attachment', filename=('GBK', '', file_name)) encoders.encode_base64(file) # 解决文件名乱码问题 m.attach(file) m['Subject'] = subject m['From'] = from_addr m['To'] = to_addr m.attach(text) server = None try: server = smtplib.SMTP_SSL(*host) # 安全模式 server.login(from_addr, password) # 登陆 server.sendmail(from_addr, to_addr, m.as_string()) # 发送 return True except smtplib.SMTPException as e: print(e) return False finally: if not server: server.quit() # jfciwswgxlmedjei9
Example #24
def notification(self, html): """ Send notification use by mail :param html: :return: """ # 随机挑选一个邮箱来发送,避免由于发送量过大导致被封 mails = get('mail', 'mails').split(',') mail = random.choice(mails) msg = MIMEMultipart() msg['Subject'] = self.subject msg['From'] = '{0} <{1}>'.format(mail, get('mail', 'from')) # 支持多用户接收邮件 msg['To'] = self.to msg['Cc'] = self.cc text = MIMEText(html, 'html', 'utf-8') msg.attach(text) host = get('mail', 'host').strip() port = get('mail', 'port').strip() try: if port == '465': port = int(port) s = smtplib.SMTP_SSL(host, port) else: s = smtplib.SMTP(host, port) s.ehlo() s.starttls() s.ehlo() s.login(mail, get('mail', 'password')) s.sendmail(mail, self.to.split(',')+self.cc.split(','), msg.as_string()) s.quit() return True except SMTPException: logger.critical('Send mail failed') traceback.print_exc() return False
Example #25
def open(self): """ Ensures we have a connection to the email server. Returns whether or not a new connection was required (True or False). """ if self.connection: # Nothing to do if the connection is already open. return False connection_class = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP # If local_hostname is not specified, socket.getfqdn() gets used. # For performance, we use the cached FQDN for local_hostname. connection_params = {'local_hostname': DNS_NAME.get_fqdn()} if self.timeout is not None: connection_params['timeout'] = self.timeout if self.use_ssl: connection_params.update({ 'keyfile': self.ssl_keyfile, 'certfile': self.ssl_certfile, }) try: self.connection = connection_class(self.host, self.port, **connection_params) # TLS/SSL are mutually exclusive, so only attempt TLS over # non-secure connections. if not self.use_ssl and self.use_tls: self.connection.ehlo() self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile) self.connection.ehlo() if self.username and self.password: self.connection.login(self.username, self.password) return True except smtplib.SMTPException: if not self.fail_silently: raise
Example #26
def _send(self, email_message): """A helper method that does the actual sending.""" if not email_message.recipients(): return False from_email = sanitize_address(email_message.from_email, email_message.encoding) recipients = [sanitize_address(addr, email_message.encoding) for addr in email_message.recipients()] message = email_message.message() try: self.connection.sendmail(from_email, recipients, message.as_bytes(linesep='rn')) except smtplib.SMTPException: if not self.fail_silently: raise return False return True
Example #27
def _send_mail(self, receivers_list, subject, body, log_files=None): """Sends a mail with `subject` and `body` and optional log_file attachments to all members of `receivers_list`.""" if not receivers_list: self.log.info('no valid addresses in requested addresses. Doing nothing') return self.log.info('sending notification to %s ...', receivers_list) if log_files: msg = MIMEMultipart() msg.attach(MIMEText(body)) for entry in log_files: log_mime = MIMEBase('application', "octet-stream") log_file = entry[0] # Output.file log_file.seek(0) log_mime.set_payload(log_file.read()) encoders.encode_base64(log_mime) log_mime.add_header('Content-Disposition', 'attachment; filename="{}"'.format(entry[1]['filename'])) msg.attach(log_mime) else: msg = MIMEText(body) msg['Subject'] = subject msg['From'] = self.from_address msg['To'] = ', '.join([x.strip() for x in receivers_list]) s = None try: s = get_smtp_session(self.workflow, self.smtp_fallback) s.sendmail(self.from_address, receivers_list, msg.as_string()) except (socket.gaierror, smtplib.SMTPException): self.log.error('Error communicating with SMTP server') raise finally: if s is not None: s.quit()
Example #28
def test_send_mail(self, throws_exception): class WF(object): exit_results = {} plugin_workspace = {} workflow = WF() workflow.plugin_workspace[ReactorConfigPlugin.key] = {} smtp_map = { 'from_address': 'foo@bar.com', 'host': 'smtp.bar.com', } workflow.plugin_workspace[ReactorConfigPlugin.key] = {} workflow.plugin_workspace[ReactorConfigPlugin.key][WORKSPACE_CONF_KEY] = ReactorConfig({'version': 1, 'smtp': smtp_map}) add_koji_map_in_workflow(workflow, hub_url='/', root_url='', ssl_certs_dir='/certs') p = SendMailPlugin(None, workflow, from_address='foo@bar.com', smtp_host='smtp.spam.com') class SMTP(object): def sendmail(self, from_addr, to, msg): pass def quit(self): pass smtp_inst = SMTP() flexmock(smtplib).should_receive('SMTP').and_return(smtp_inst) sendmail_chain = (flexmock(smtp_inst).should_receive('sendmail'). with_args('foo@bar.com', ['spam@spam.com'], str)) if throws_exception: sendmail_chain.and_raise(smtplib.SMTPException, "foo") flexmock(smtp_inst).should_receive('quit') if throws_exception: with pytest.raises(SMTPException) as e: p._send_mail(['spam@spam.com'], 'subject', 'body') assert str(e.value) == 'foo' else: p._send_mail(['spam@spam.com'], 'subject', 'body')
Example #29
def alarm_email(SERVER,USER,PASSWORT,STARTTLS,FROM,TO,SUBJECT,MESSAGE): logger.info(u'Send mail!') from smtplib import SMTP from smtplib import SMTPException from email.mime.text import MIMEText as text if STARTTLS: port=587 else: port=25 try: s = SMTP(SERVER,port) if STARTTLS: s.starttls() s.login(USER,PASSWORT) m = text(MESSAGE, 'plain', 'UTF-8') m['Subject'] = SUBJECT m['From'] = FROM m['To'] = TO s.sendmail(FROM,TO, m.as_string()) s.quit() logger.debug(u'Alert Email has been sent!') except SMTPException as error: sendefehler = u'Error: unable to send email: {err}'.format(err=error) logger.error(sendefehler) except: #TODO err undefined! sendefehler = u'Error: unable to resolve host (no internet connection?) : {err}' logger.error(sendefehler)
Example #30
def safe_smtp_quit(self, smtp): try: smtp.quit() except (smtplib.SMTPException, IOError): logging.warn('error during smtp.quit(), ignoring', exc_info=True) # ignore pass
Python 3.3
21.17. smtplib — SMTP protocol client
Source code: Lib/smtplib.py
The smtplib module defines an SMTP client session object that can be used
to send mail to any Internet machine with an SMTP or ESMTP listener daemon. For
details of SMTP and ESMTP operation, consult RFC 821 (Simple Mail Transfer
Protocol) and RFC 1869 (SMTP Service Extensions).
- class smtplib.SMTP(host=», port=0, local_hostname=None[, timeout], source_address=None)
-
A SMTP instance encapsulates an SMTP connection. It has methods
that support a full repertoire of SMTP and ESMTP operations. If the optional
host and port parameters are given, the SMTP connect() method is
called with those parameters during initialization. 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(). If the connect() call returns anything other
than a success code, an SMTPConnectError is raised. The optional
timeout parameter specifies a timeout in seconds for blocking operations
like the connection attempt (if not specified, the global default timeout
setting will be used). The optional source_address parameter allows to bind
to some specific source address in a machine with multiple network
interfaces, and/or to some specific source TCP port. It takes a 2-tuple
(host, port), for the socket to bind to as its source address before
connecting. If omitted (or if host or port are » and/or 0 respectively)
the OS default behavior will be used.For normal use, you should only require the initialization/connect,
sendmail(), and quit() methods.
An example is included below.The SMTP class supports the with statement. When used
like this, the SMTP QUIT command is issued automatically when the
with statement exits. E.g.:>>> from smtplib import SMTP >>> with SMTP("domain.org") as smtp: ... smtp.noop() ... (250, b'Ok') >>>
Changed in version 3.3: Support for the with statement was added.
Changed in version 3.3: source_address argument was added.
- class smtplib.SMTP_SSL(host=», port=0, local_hostname=None, keyfile=None, certfile=None[, timeout], context=None, source_address=None)
-
A SMTP_SSL instance behaves exactly the same as instances of
SMTP. SMTP_SSL should be used for situations where SSL is
required from the beginning of the connection and using starttls() is
not appropriate. If host is not specified, the local host is used. If
port is zero, the standard SMTP-over-SSL port (465) is used. The optional
arguments local_hostname and source_address have the same meaning as
they do in the SMTP class. keyfile and certfile are also
optional, and can contain a PEM formatted private key and certificate chain
file for the SSL connection. context also optional, can contain a
SSLContext, and is an alternative to keyfile and certfile; If it is
specified both keyfile and certfile must be None. The optional timeout
parameter specifies a timeout in seconds for blocking operations like the
connection attempt (if not specified, the global default timeout setting
will be used). The optional source_address parameter allows to bind to some
specific source address in a machine with multiple network interfaces,
and/or to some specific source tcp port. It takes a 2-tuple (host, port),
for the socket to bind to as its source address before connecting. If
omitted (or if host or port are » and/or 0 respectively) the OS default
behavior will be used.Changed in version 3.3: context was added.
Changed in version 3.3: source_address argument was added.
- class smtplib.LMTP(host=», port=LMTP_PORT, local_hostname=None, source_address=None)
-
The LMTP protocol, which is very similar to ESMTP, is heavily based on the
standard SMTP client. It’s common to use Unix sockets for LMTP, so our
connect() method must support that as well as a regular host:port
server. The optional arguments local_hostname and source_address have the
same meaning as they do in the SMTP class. To specify a Unix
socket, you must use an absolute path for host, starting with a ‘/’.Authentication is supported, using the regular SMTP mechanism. When using a
Unix socket, LMTP generally don’t support or require any authentication, but
your mileage might vary.
A nice selection of exceptions is defined as well:
- exception smtplib.SMTPException
-
The base exception class for all the other exceptions provided by this
module.
- exception smtplib.SMTPServerDisconnected
-
This exception is raised when the server unexpectedly disconnects, or when an
attempt is made to use the SMTP instance before connecting it to a
server.
- exception smtplib.SMTPResponseException
-
Base class for all exceptions that include an SMTP error code. These exceptions
are generated in some instances when the SMTP server returns an error code. The
error code is stored in the smtp_code attribute of the error, and the
smtp_error attribute is set to the error message.
- exception smtplib.SMTPSenderRefused
-
Sender address refused. In addition to the attributes set by on all
SMTPResponseException exceptions, this sets ‘sender’ to the string that
the SMTP server refused.
- exception smtplib.SMTPRecipientsRefused
-
All recipient addresses refused. The errors for each recipient are accessible
through the attribute recipients, which is a dictionary of exactly the
same sort as SMTP.sendmail() returns.
- exception smtplib.SMTPDataError
-
The SMTP server refused to accept the message data.
- exception smtplib.SMTPConnectError
-
Error occurred during establishment of a connection with the server.
- exception smtplib.SMTPHeloError
-
The server refused our HELO message.
- exception smtplib.SMTPAuthenticationError
-
SMTP authentication went wrong. Most probably the server didn’t accept the
username/password combination provided.
See also
- RFC 821 — Simple Mail Transfer Protocol
- Protocol definition for SMTP. This document covers the model, operating
procedure, and protocol details for SMTP. - RFC 1869 — SMTP Service Extensions
- Definition of the ESMTP extensions for SMTP. This describes a framework for
extending SMTP with new commands, supporting dynamic discovery of the commands
provided by the server, and defines a few additional commands.
21.17.1. SMTP Objects
An SMTP instance has the following methods:
- SMTP.set_debuglevel(level)
-
Set the debug output level. A true value for level results in debug messages
for connection and for all messages sent to and received from the server.
- SMTP.docmd(cmd, args=»)
-
Send a command cmd to the server. The optional argument args is simply
concatenated to the command, separated by a space.This returns a 2-tuple composed of a numeric response code and the actual
response line (multiline responses are joined into one long line.)In normal operation it should not be necessary to call this method explicitly.
It is used to implement other methods and may be useful for testing private
extensions.If the connection to the server is lost while waiting for the reply,
SMTPServerDisconnected will be raised.
- SMTP.connect(host=’localhost’, port=0)
-
Connect to a host on a given port. The defaults are to connect to the local
host at the standard SMTP port (25). If the hostname ends with a colon (‘:’)
followed by a number, that suffix will be stripped off and the number
interpreted as the port number to use. This method is automatically invoked by
the constructor if a host is specified during instantiation. Returns a
2-tuple of the response code and message sent by the server in its
connection response.
- SMTP.helo(name=»)
-
Identify yourself to the SMTP server using HELO. The hostname argument
defaults to the fully qualified domain name of the local host.
The message returned by the server is stored as the helo_resp attribute
of the object.In normal operation it should not be necessary to call this method explicitly.
It will be implicitly called by the sendmail() when necessary.
- SMTP.ehlo(name=»)
-
Identify yourself to an ESMTP server using EHLO. The hostname argument
defaults to the fully qualified domain name of the local host. Examine the
response for ESMTP option and store them for use by has_extn().
Also sets several informational attributes: the message returned by
the server is stored as the ehlo_resp attribute, does_esmtp
is set to true or false depending on whether the server supports ESMTP, and
esmtp_features will be a dictionary containing the names of the
SMTP service extensions this server supports, and their
parameters (if any).Unless you wish to use has_extn() before sending mail, it should not be
necessary to call this method explicitly. It will be implicitly called by
sendmail() when necessary.
- SMTP.ehlo_or_helo_if_needed()
-
This method call ehlo() and or helo() if there has been no
previous EHLO or HELO command this session. It tries ESMTP EHLO
first.- SMTPHeloError
- The server didn’t reply properly to the HELO greeting.
- SMTP.has_extn(name)
-
Return True if name is in the set of SMTP service extensions returned
by the server, False otherwise. Case is ignored.
- SMTP.verify(address)
-
Check the validity of an address on this server using SMTP VRFY. Returns a
tuple consisting of code 250 and a full RFC 822 address (including human
name) if the user address is valid. Otherwise returns an SMTP error code of 400
or greater and an error string.Note
Many sites disable SMTP VRFY in order to foil spammers.
- SMTP.login(user, password)
-
Log in on an SMTP server that requires authentication. The arguments are the
username and the password to authenticate with. 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, or
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.
- SMTPException
- No suitable authentication method was found.
- SMTP.starttls(keyfile=None, certfile=None, context=None)
-
Put the SMTP connection in TLS (Transport Layer Security) mode. All SMTP
commands that follow will be encrypted. You should then call ehlo()
again.If keyfile and certfile are provided, these are passed to the socket
module’s ssl() function.Optional context parameter is a ssl.SSLContext object; This is an alternative to
using a keyfile and a certfile and if specified both keyfile and certfile should be None.If there has been no previous EHLO or HELO command this session,
this method tries ESMTP EHLO first.- SMTPHeloError
- The server didn’t reply properly to the HELO greeting.
- SMTPException
- The server does not support the STARTTLS extension.
- RuntimeError
- SSL/TLS support is not available to your Python interpreter.
Changed in version 3.3: context was added.
- SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
-
Send mail. The required arguments are an RFC 822 from-address string, a list
of RFC 822 to-address strings (a bare string will be treated as a list with 1
address), and a message string. The caller may pass a list of ESMTP options
(such as 8bitmime) to be used in MAIL FROM commands as mail_options.
ESMTP options (such as DSN commands) that should be used with all RCPT
commands can be passed as rcpt_options. (If you need to use different ESMTP
options to different recipients you have to use the low-level methods such as
mail(), rcpt() and data() to send the message.)Note
The from_addr and to_addrs parameters are used to construct the message
envelope used by the transport agents. sendmail does not modify the
message headers in any way.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 rn characters. A byte string is
not modified.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 the option is in the
feature set the server advertises). 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. Otherwise it will raise an exception. That is, if this method does
not raise an exception, then someone should get your mail. If this method does
not raise an exception, 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:
- SMTPRecipientsRefused
- All recipients were refused. Nobody got the mail. The recipients
attribute of the exception object is a dictionary with information about the
refused recipients (like the one returned when at least one recipient was
accepted). - SMTPHeloError
- The server didn’t reply properly to the HELO greeting.
- 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).
Unless otherwise noted, the connection will be open even after an exception is
raised.Changed in version 3.2: msg may be a byte string.
- SMTP.send_message(msg, from_addr=None, to_addrs=None, mail_options=[], rcpt_options=[])
-
This is a convenience method for calling sendmail() with the message
represented by an email.message.Message object. The arguments have
the same meaning as for sendmail(), except that msg is a Message
object.If from_addr is None or to_addrs is None, send_message fills
those arguments with addresses extracted from the headers of msg as
specified in RFC 2822: from_addr is set to the Sender
field if it is present, and otherwise to the From field.
to_adresses combines the values (if any) of the To,
Cc, and Bcc fields from msg. If exactly one
set of Resent-* headers appear in the message, the regular
headers are ignored and the Resent-* headers are used instead.
If the message contains more than one set of Resent-* headers,
a ValueError is raised, since there is no way to unambiguously detect
the most recent set of Resent- headers.send_message serializes msg using
BytesGenerator with rn as the linesep, and
calls sendmail() to transmit the resulting message. Regardless of the
values of from_addr and to_addrs, send_message does not transmit any
Bcc or Resent-Bcc headers that may appear
in msg.New in version 3.2.
- SMTP.quit()
-
Terminate the SMTP session and close the connection. Return the result of
the SMTP QUIT command.
Low-level methods corresponding to the standard SMTP/ESMTP commands HELP,
RSET, NOOP, MAIL, RCPT, and DATA are also supported.
Normally these do not need to be called directly, so they are not documented
here. For details, consult the module code.
21.17.2. SMTP Example
This example prompts the user for addresses needed in the message envelope (‘To’
and ‘From’ addresses), and the message to be delivered. Note that the headers
to be included with the message must be included in the message as entered; this
example doesn’t do any processing of the RFC 822 headers. In particular, the
‘To’ and ‘From’ addresses must be included in the message headers explicitly.
import smtplib def prompt(prompt): return input(prompt).strip() fromaddr = prompt("From: ") toaddrs = prompt("To: ").split() print("Enter message, end with ^D (Unix) or ^Z (Windows):") # Add the From: and To: headers at the start! msg = ("From: %srnTo: %srnrn" % (fromaddr, ", ".join(toaddrs))) while True: try: line = input() except EOFError: break if not line: break msg = msg + line print("Message length is", len(msg)) server = smtplib.SMTP('localhost') server.set_debuglevel(1) server.sendmail(fromaddr, toaddrs, msg) server.quit()
Note
In general, you will want to use the email package’s features to
construct an email message, which you can then send
via send_message(); see email: Examples.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Sending Emails With Python
You probably found this tutorial because you want to send emails using Python. Perhaps you want to receive email reminders from your code, send a confirmation email to users when they create an account, or send emails to members of your organization to remind them to pay their dues. Sending emails manually is a time-consuming and error-prone task, but it’s easy to automate with Python.
In this tutorial you’ll learn how to:
-
Set up a secure connection using
SMTP_SSL()
and.starttls()
-
Use Python’s built-in
smtplib
library to send basic emails -
Send emails with HTML content and attachments using the
email
package -
Send multiple personalized emails using a CSV file with contact data
-
Use the Yagmail package to send email through your Gmail account using only a few lines of code
You’ll find a few transactional email services at the end of this tutorial, which will come in useful when you want to send a large number of emails.
Getting Started
Python comes with the built-in smtplib
module for sending emails using the Simple Mail Transfer Protocol (SMTP). smtplib
uses the RFC 821 protocol for SMTP. The examples in this tutorial will use the Gmail SMTP server to send emails, but the same principles apply to other email services. Although the majority of email providers use the same connection ports as the ones in this tutorial, you can run a quick Google search to confirm yours.
To get started with this tutorial, set up a Gmail account for development, or set up an SMTP debugging server that discards emails you send and prints them to the command prompt instead. Both options are laid out for you below. A local SMTP debugging server can be useful for fixing any issues with email functionality and ensuring your email functions are bug-free before sending out any emails.
Option 1: Setting up a Gmail Account for Development
If you decide to use a Gmail account to send your emails, I highly recommend setting up a throwaway account for the development of your code. This is because you’ll have to adjust your Gmail account’s security settings to allow access from your Python code, and because there’s a chance you might accidentally expose your login details. Also, I found that the inbox of my testing account rapidly filled up with test emails, which is reason enough to set up a new Gmail account for development.
A nice feature of Gmail is that you can use the +
sign to add any modifiers to your email address, right before the @
sign. For example, mail sent to my+person1@gmail.com
and my+person2@gmail.com
will both arrive at my@gmail.com
. When testing email functionality, you can use this to emulate multiple addresses that all point to the same inbox.
To set up a Gmail address for testing your code, do the following:
- Create a new Google account.
- Turn Allow less secure apps to ON. Be aware that this makes it easier for others to gain access to your account.
If you don’t want to lower the security settings of your Gmail account, check out Google’s documentation on how to gain access credentials for your Python script, using the OAuth2 authorization framework.
Option 2: Setting up a Local SMTP Server
You can test email functionality by running a local SMTP debugging server, using the smtpd
module that comes pre-installed with Python. Rather than sending emails to the specified address, it discards them and prints their content to the console. Running a local debugging server means it’s not necessary to deal with encryption of messages or use credentials to log in to an email server.
You can start a local SMTP debugging server by typing the following in Command Prompt:
$ python -m smtpd -c DebuggingServer -n localhost:1025
On Linux, use the same command preceded by sudo
.
Any emails sent through this server will be discarded and shown in the terminal window as a bytes
object for each line:
---------- MESSAGE FOLLOWS ----------
b'X-Peer: ::1'
b''
b'From: my@address.com'
b'To: your@address.com'
b'Subject: a local test mail'
b''
b'Hello there, here is a test email'
------------ END MESSAGE ------------
For the rest of the tutorial, I’ll assume you’re using a Gmail account, but if you’re using a local debugging server, just make sure to use localhost
as your SMTP server and use port 1025 rather than port 465 or 587. Besides this, you won’t need to use login()
or encrypt the communication using SSL/TLS.
Sending a Plain-Text Email
Before we dive into sending emails with HTML content and attachments, you’ll learn to send plain-text emails using Python. These are emails that you could write up in a simple text editor. There’s no fancy stuff like text formatting or hyperlinks. You’ll learn that a bit later.
Starting a Secure SMTP Connection
When you send emails through Python, you should make sure that your SMTP connection is encrypted, so that your message and login credentials are not easily accessed by others. SSL (Secure Sockets Layer) and TLS (Transport Layer Security) are two protocols that can be used to encrypt an SMTP connection. It’s not necessary to use either of these when using a local debugging server.
There are two ways to start a secure connection with your email server:
- Start an SMTP connection that is secured from the beginning using
SMTP_SSL()
. - Start an unsecured SMTP connection that can then be encrypted using
.starttls()
.
In both instances, Gmail will encrypt emails using TLS, as this is the more secure successor of SSL. As per Python’s Security considerations, it is highly recommended that you use create_default_context()
from the ssl
module. This will load the system’s trusted CA certificates, enable host name checking and certificate validation, and try to choose reasonably secure protocol and cipher settings.
If you want to check the encryption for an email in your Gmail inbox, go to More → Show original to see the encryption type listed under the Received header.
smtplib
is Python’s built-in module for sending emails to any Internet machine with an SMTP or ESMTP listener daemon.
I’ll show you how to use SMTP_SSL()
first, as it instantiates a connection that is secure from the outset and is slightly more concise than the .starttls()
alternative. Keep in mind that Gmail requires that you connect to port 465 if using SMTP_SSL()
, and to port 587 when using .starttls()
.
Option 1: Using SMTP_SSL()
The code example below creates a secure connection with Gmail’s SMTP server, using the SMTP_SSL()
of smtplib
to initiate a TLS-encrypted connection. The default context of ssl
validates the host name and its certificates and optimizes the security of the connection. Make sure to fill in your own email address instead of my@gmail.com
:
import smtplib, ssl
port = 465 # For SSL
password = input("Type your password and press enter: ")
# Create a secure SSL context
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
server.login("my@gmail.com", password)
# TODO: Send email here
Using with smtplib.SMTP_SSL() as server:
makes sure that the connection is automatically closed at the end of the indented code block. If port
is zero, or not specified, .SMTP_SSL()
will use the standard port for SMTP over SSL (port 465).
It’s not safe practice to store your email password in your code, especially if you intend to share it with others. Instead, use input()
to let the user type in their password when running the script, as in the example above. If you don’t want your password to show on your screen when you type it, you can import the getpass
module and use .getpass()
instead for blind input of your password.
Option 2: Using .starttls()
Instead of using .SMTP_SSL()
to create a connection that is secure from the outset, we can create an unsecured SMTP connection and encrypt it using .starttls()
.
To do this, create an instance of smtplib.SMTP
, which encapsulates an SMTP connection and allows you access to its methods. I recommend defining your SMTP server and port at the beginning of your script to configure them easily.
The code snippet below uses the construction server = SMTP()
, rather than the format with SMTP() as server:
which we used in the previous example. To make sure that your code doesn’t crash when something goes wrong, put your main code in a try
block, and let an except
block print any error messages to stdout
:
import smtplib, ssl
smtp_server = "smtp.gmail.com"
port = 587 # For starttls
sender_email = "my@gmail.com"
password = input("Type your password and press enter: ")
# Create a secure SSL context
context = ssl.create_default_context()
# Try to log in to server and send email
try:
server = smtplib.SMTP(smtp_server,port)
server.ehlo() # Can be omitted
server.starttls(context=context) # Secure the connection
server.ehlo() # Can be omitted
server.login(sender_email, password)
# TODO: Send email here
except Exception as e:
# Print any error messages to stdout
print(e)
finally:
server.quit()
To identify yourself to the server, .helo()
(SMTP) or .ehlo()
(ESMTP) should be called after creating an .SMTP()
object, and again after .starttls()
. This function is implicitly called by .starttls()
and .sendmail()
if needed, so unless you want to check the SMTP service extensions of the server, it is not necessary to use .helo()
or .ehlo()
explicitly.
Sending Your Plain-text Email
After you initiated a secure SMTP connection using either of the above methods, you can send your email using .sendmail()
, which pretty much does what it says on the tin:
server.sendmail(sender_email, receiver_email, message)
I recommend defining the email addresses and message content at the top of your script, after the imports, so you can change them easily:
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
message = """
Subject: Hi there
This message is sent from Python."""
# Send email here
The message
string starts with "Subject: Hi there"
followed by two newlines (n
). This ensures Hi there
shows up as the subject of the email, and the text following the newlines will be treated as the message body.
The code example below sends a plain-text email using SMTP_SSL()
:
import smtplib, ssl
port = 465 # For SSL
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com" # Enter your address
receiver_email = "your@gmail.com" # Enter receiver address
password = input("Type your password and press enter: ")
message = """
Subject: Hi there
This message is sent from Python."""
context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
For comparison, here is a code example that sends a plain-text email over an SMTP connection secured with .starttls()
. The server.ehlo()
lines may be omitted, as they are called implicitly by .starttls()
and .sendmail()
, if required:
import smtplib, ssl
port = 587 # For starttls
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = """
Subject: Hi there
This message is sent from Python."""
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
server.ehlo() # Can be omitted
server.starttls(context=context)
server.ehlo() # Can be omitted
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, message)
Sending Fancy Emails
Python’s built-in email
package allows you to structure more fancy emails, which can then be transferred with smtplib
as you have done already. Below, you’ll learn how use the email
package to send emails with HTML content and attachments.
Including HTML Content
If you want to format the text in your email (bold, italics, and so on), or if you want to add any images, hyperlinks, or responsive content, then HTML comes in very handy. Today’s most common type of email is the MIME (Multipurpose Internet Mail Extensions) Multipart email, combining HTML and plain-text. MIME messages are handled by Python’s email.mime
module. For a detailed description, check the documentation.
As not all email clients display HTML content by default, and some people choose only to receive plain-text emails for security reasons, it is important to include a plain-text alternative for HTML messages. As the email client will render the last multipart attachment first, make sure to add the HTML message after the plain-text version.
In the example below, our MIMEText()
objects will contain the HTML and plain-text versions of our message, and the MIMEMultipart("alternative")
instance combines these into a single message with two alternative rendering options:
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email
# Create the plain-text and HTML version of your message
text = """
Hi,
How are you?
Real Python has many great tutorials:
www.realpython.com"""
html = """
<html>
<body>
<p>Hi,<br>
How are you?<br>
<a href="http://www.realpython.com">Real Python</a>
has many great tutorials.
</p>
</body>
</html>
"""
# Turn these into plain/html MIMEText objects
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
# Add HTML/plain-text parts to MIMEMultipart message
# The email client will try to render the last part first
message.attach(part1)
message.attach(part2)
# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(
sender_email, receiver_email, message.as_string()
)
In this example, you first define the plain-text and HTML message as string literals, and then store them as plain
/html
MIMEText
objects. These can then be added in this order to the MIMEMultipart("alternative")
message and sent through your secure connection with the email server. Remember to add the HTML message after the plain-text alternative, as email clients will try to render the last subpart first.
Adding Attachments Using the email
Package
In order to send binary files to an email server that is designed to work with textual data, they need to be encoded before transport. This is most commonly done using base64
, which encodes binary data into printable ASCII characters.
The code example below shows how to send an email with a PDF file as an attachment:
import email, smtplib, ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email # Recommended for mass emails
# Add body to email
message.attach(MIMEText(body, "plain"))
filename = "document.pdf" # In same directory as script
# Open PDF file in binary mode
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(sender_email, password)
server.sendmail(sender_email, receiver_email, text)
The MIMEultipart()
message accepts parameters in the form of RFC5233-style key/value pairs, which are stored in a dictionary and passed to the .add_header
method of the Message
base class.
Check out the documentation for Python’s email.mime
module to learn more about using MIME classes.
Sending Multiple Personalized Emails
Imagine you want to send emails to members of your organization, to remind them to pay their contribution fees. Or maybe you want to send students in your class personalized emails with the grades for their recent assignment. These tasks are a breeze in Python.
Make a CSV File With Relevant Personal Info
An easy starting point for sending multiple personalized emails is to create a CSV (comma-separated values) file that contains all the required personal information. (Make sure not to share other people’s private information without their consent.) A CSV file can be thought of as a simple table, where the first line often contains the column headers.
Below are the contents of the file contacts_file.csv
, which I saved in the same folder as my Python code. It contains the names, addresses, and grades for a set of fictional people. I used my+modifier@gmail.com
constructions to make sure all emails end up in my own inbox, which in this example is my@gmail.com:
name,email,grade
Ron Obvious,my+ovious@gmail.com,B+
Killer Rabbit of Caerbannog,my+rabbit@gmail.com,A
Brian Cohen,my+brian@gmail.com,C
When creating a CSV file, make sure to separate your values by a comma, without any surrounding whitespaces.
Loop Over Rows to Send Multiple Emails
The code example below shows you how to open a CSV file and loop over its lines of content (skipping the header row). To make sure that the code works correctly before you send emails to all your contacts, I’ve printed Sending email to ...
for each contact, which we can later replace with functionality that actually sends out emails:
import csv
with open("contacts_file.csv") as file:
reader = csv.reader(file)
next(reader) # Skip header row
for name, email, grade in reader:
print(f"Sending email to {name}")
# Send email here
In the example above, using with open(filename) as file:
makes sure that your file closes at the end of the code block. csv.reader()
makes it easy to read a CSV file line by line and extract its values. The next(reader)
line skips the header row, so that the following line for name, email, grade in reader:
splits subsequent rows at each comma, and stores the resulting values in the strings name
, email
and grade
for the current contact.
If the values in your CSV file contain whitespaces on either or both sides, you can remove them using the .strip()
method.
Personalized Content
You can put personalized content in a message by using str.format()
to fill in curly-bracket placeholders.
For example, "hi {name}, you {result} your assignment".format(name="John", result="passed")
will give you "hi John, you passed your assignment"
.
As of Python 3.6, string formatting can be done more elegantly using f-strings, but these require the placeholders to be defined before the f-string itself. In order to define the email message at the beginning of the script, and fill in placeholders for each contact when looping over the CSV file, the older .format()
method is used.
With this in mind, you can set up a general message body, with placeholders that can be tailored to individuals.
Code Example
The following code example lets you send personalized emails to multiple contacts. It loops over a CSV file with name,email,grade
for each contact, as in the example above.
The general message is defined in the beginning of the script, and for each contact in the CSV file its {name}
and {grade}
placeholders are filled in, and a personalized email is sent out through a secure connection with the Gmail server, as you saw before:
import csv, smtplib, ssl
message = """Subject: Your grade
Hi {name}, your grade is {grade}"""
from_address = "my@gmail.com"
password = input("Type your password and press enter: ")
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(from_address, password)
with open("contacts_file.csv") as file:
reader = csv.reader(file)
next(reader) # Skip header row
for name, email, grade in reader:
server.sendmail(
from_address,
email,
message.format(name=name,grade=grade),
)
Yagmail
There are multiple libraries designed to make sending emails easier, such as Envelopes, Flanker and Yagmail. Yagmail is designed to work specifically with Gmail, and it greatly simplifies the process of sending emails through a friendly API, as you can see in the code example below:
import yagmail
receiver = "your@gmail.com"
body = "Hello there from Yagmail"
filename = "document.pdf"
yag = yagmail.SMTP("my@gmail.com")
yag.send(
to=receiver,
subject="Yagmail test with attachment",
contents=body,
attachments=filename,
)
This code example sends an email with a PDF attachment in a fraction of the lines needed for our example using email
and smtplib
.
When setting up Yagmail, you can add your Gmail validations to the keyring of your OS, as described in the documentation. If you don’t do this, Yagmail will prompt you to enter your password when required and store it in the keyring automatically.
Transactional Email Services
If you plan to send a large volume of emails, want to see email statistics, and want to ensure reliable delivery, it may be worth looking into transactional email services.
Although all of the following services have paid plans for sending large volumes of emails, they also come with a free plan so you can try them out. Some of these free plans are valid indefinitely and may be sufficient for your email needs.
Below is an overview of the free plans for some of the major transactional email services. Clicking on the provider name will take you to the pricing section of their website.
You can run a Google search to see which provider best fits your needs, or just try out a few of the free plans to see which API you like working with most.
Sendgrid Code Example
Here’s a code example for sending emails with Sendgrid to give you a flavor of how to use a transactional email service with Python:
import os
import sendgrid
from sendgrid.helpers.mail import Content, Email, Mail
sg = sendgrid.SendGridAPIClient(
apikey=os.environ.get("SENDGRID_API_KEY")
)
from_email = Email("my@gmail.com")
to_email = Email("your@gmail.com")
subject = "A test email from Sendgrid"
content = Content(
"text/plain", "Here's a test email sent through Python"
)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
# The statements below can be included for debugging purposes
print(response.status_code)
print(response.body)
print(response.headers)
To run this code, you must first:
- Sign up for a (free) Sendgrid account
- Request an API key for user validation
- Add your API key by typing
setx SENDGRID_API_KEY "YOUR_API_KEY"
in Command Prompt (to store this API key permanently) orset SENDGRID_API_KEY YOUR_API_KEY
to store it only for the current client session
More information on how to set up Sendgrid for Mac and Windows can be found in the repository’s README on Github.
Conclusion
You can now start a secure SMTP connection and send multiple personalized emails to the people in your contacts list!
You’ve learned how to send an HTML email with a plain-text alternative and attach files to your emails. The Yagmail package simplifies all these tasks when you’re using a Gmail account. If you plan to send large volumes of email, it is worth looking into transactional email services.
Enjoy sending emails with Python, and remember: no spam please!
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Sending Emails With Python