def send_mail(subject, body, from_email, recipient_list, message_id=None): msg = SafeMIMEText(body) msg['Subject'] = subject msg['From'] = from_email msg['To'] = ', '.join(recipient_list) msg['Date'] = formatdate() msg['Message-Id'] = '<%s>' % (message_id or create_message_id()) server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT) try: server.sendmail(from_email, recipient_list, msg.as_string()) finally: server.quit()
def build_gpg_attachment(): gpg_attachment = SafeMIMEText(encrypted_msg, self.content_subtype, encoding) del gpg_attachment['Content-Type'] gpg_attachment.add_header('Content-Type', 'application/octet-stream', name=self.gpg_attachment_filename) gpg_attachment.add_header('Content-Disposition', 'inline', filename=self.gpg_attachment_filename) gpg_attachment.add_header('Content-Description', 'OpenPGP encrypted message') return gpg_attachment
def _create_attachment(self, filename, content, mimetype=None): """ Converts the filename, content, mimetype triple into a MIME attachment object. Use self.encoding when handling text attachments. """ if mimetype is None: mimetype, _ = mimetypes.guess_type(filename) if mimetype is None: mimetype = constants.MIME_TYPE_EXCEL basetype, subtype = mimetype.split('/', 1) if basetype == 'text': encoding = self.encoding or settings.DEFAULT_CHARSET attachment = SafeMIMEText( smart_str(content, settings.DEFAULT_CHARSET), subtype, encoding) else: # Encode non-text attachments with base64. attachment = MIMEBase(basetype, subtype) attachment.set_payload(content) encoders.encode_base64(attachment) if filename: try: filename = filename.encode('ascii') except UnicodeEncodeError: filename = Header(filename, 'utf-8').encode() attachment.add_header('Content-Disposition', 'attachment', filename=filename) return attachment
def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST_USER, auth_password=settings.EMAIL_HOST_PASSWORD, extra_headers=None): """ Given a datatuple of (subject, message, from_email, recipient_list), sends each message to each recipient list. Returns the number of e-mails sent. If from_email is None, the DEFAULT_FROM_EMAIL setting is used. If auth_user and auth_password are set, they're used to log in. """ try: server = smtplib.SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT) if auth_user and auth_password: server.login(auth_user, auth_password) except: if fail_silently: return raise num_sent = 0 for subject, message, from_email, recipient_list in datatuple: if not recipient_list: continue from_email = from_email or settings.DEFAULT_FROM_EMAIL msg = SafeMIMEText(message, 'plain', settings.DEFAULT_CHARSET) msg['Subject'] = subject msg['From'] = from_email msg['To'] = ', '.join(recipient_list) msg['Date'] = rfc822.formatdate() msg['Message-ID'] = '<*****@*****.**>' % \ (datetime.datetime.now().isoformat('T').replace(':', '.'), os.getpid(), random.randrange(0, sys.maxint)) if extra_headers: for h in extra_headers: msg[h] = extra_headers[h] try: server.sendmail(from_email, recipient_list, msg.as_string()) num_sent += 1 except: if not fail_silently: raise try: server.quit() except: if fail_silently: return raise return num_sent
def test_parse_raw_mime_8bit_utf8(self): # In come cases, the message below ends up with 'Content-Transfer-Encoding: 8bit', # so needs to be parsed as bytes, not text (see https://bugs.python.org/issue18271). # Message.as_string() returns str (text), not bytes. # (This might be a Django bug; plain old MIMEText avoids the problem by using # 'Content-Transfer-Encoding: base64', which parses fine as text or bytes.) # Either way, AnymailInboundMessage should try to sidestep the whole issue. raw = SafeMIMEText("Unicode ✓", "plain", "utf-8").as_string() msg = AnymailInboundMessage.parse_raw_mime(raw) self.assertEqual(msg.text, "Unicode ✓") # *not* "Unicode \\u2713"
def _create_attachment(self, filename, content, mimetype=None): """ Converts the filename, content, mimetype triple into a MIME attachment object. Use self.encoding when handling text attachments. """ if mimetype is None: mimetype, _ = mimetypes.guess_type(filename) if mimetype is None: mimetype = DEFAULT_ATTACHMENT_MIME_TYPE basetype, subtype = mimetype.split('/', 1) if basetype == 'text': encoding = self.encoding or settings.DEFAULT_CHARSET attachment = SafeMIMEText(smart_str(content, settings.DEFAULT_CHARSET), subtype, encoding) else: # Encode non-text attachments with base64. attachment = MIMEBase(basetype, subtype) attachment.set_payload(content) encoders.encode_base64(attachment) if filename: try: filename = filename.encode('ascii') except UnicodeEncodeError: filename = Header(filename, 'utf-8').encode() attachment.add_header('Content-Disposition', 'attachment', filename=filename) return attachment
def build_version_attachment(): version_attachment = SafeMIMEText('Version: 1\n', self.content_subtype, encoding) del version_attachment['Content-Type'] version_attachment.add_header('Content-Type', 'application/pgp-encrypted') version_attachment.add_header('Content-Description', 'PGP/MIME Versions Identification') return version_attachment
def create_email(template_name: str, subject: str, context: dict, **kwargs) -> EmailMessage: """ Attempts to create an email that only has a single language, which is pulled from the context using get_language. If it cannot send the email in the requested language, it will call create_multilingual_email to send an email in all the languages that are available. :param template_name: The template that should be used for this email :param subject: The subject of this email :param context: The context for the template :param kwargs: Any other data that should be passed to the EmailMessage constructor :return: an EmailMessage """ _ensure_setup() lang = get_language() kwargs['headers'] = kwargs['headers'] if 'headers' in kwargs else {} kwargs['headers'] = { "X-Mailer": get_mailer_name(), "Content-Language": lang, **kwargs['headers'] } langs = templates[template_name] if lang in langs: tpls = langs[lang] msg = MultiTemplatableEmailMessage(subject=_(subject), body=False, **kwargs) for template in tpls: msg.attach_alternative( SafeMIMEText(_text=get_template(template['file']).render({ 'locale': template['locale'], **context, }), _subtype=template['subtype'], _charset=msg.encoding or settings.DEFAULT_CHARSET), 'unknown/unknown') return msg return create_multilingual_mail(template_name, subject, context, **kwargs)
def message(self): from ..utils import replace_cid_and_change_headers to = anyjson.loads(self.to) cc = anyjson.loads(self.cc) bcc = anyjson.loads(self.bcc) html, text, inline_headers = replace_cid_and_change_headers( self.body, self.original_message_id) email_message = SafeMIMEMultipart('related') email_message['Subject'] = self.subject email_message['From'] = self.send_from.to_header() if to: email_message['To'] = ','.join(list(to)) if cc: email_message['cc'] = ','.join(list(cc)) if bcc: email_message['bcc'] = ','.join(list(bcc)) email_message_alternative = SafeMIMEMultipart('alternative') email_message.attach(email_message_alternative) email_message_text = SafeMIMEText(text, 'plain', 'utf-8') email_message_alternative.attach(email_message_text) email_message_html = SafeMIMEText(html, 'html', 'utf-8') email_message_alternative.attach(email_message_html) try: add_attachments_to_email(self, email_message, inline_headers) except IOError: return False return email_message
def get_base_message(self, message): payload = message.get_payload() if isinstance(message, SafeMIMEMultipart): # If this is a multipart message, we encrypt all its parts. # We create a new SafeMIMEMultipart instance, the original message contains all # headers (From, To, ...) which we shouldn't sign/encrypt. subtype = message.get_content_subtype() base = SafeMIMEMultipart(_subtype=subtype, _subparts=payload) else: # If it is a non-multipart message (-> plain-text email), we just encrypt the payload base = SafeMIMEText(payload) # TODO: Is it possible to influence the main content type of the message? If yes, we # need to copy it here. del base['MIME-Version'] return base
def message(self): encoding = self.encoding or settings.DEFAULT_CHARSET msg = SafeMIMEText(smart_str(self.body, encoding), self.content_subtype, encoding) msg = self._create_message(msg) msg['Subject'] = self.subject msg['From'] = self.extra_headers.get('From', self.from_email) msg['To'] = self.extra_headers.get("To", ', '.join(self.to)) if self.cc: msg['Cc'] = ', '.join(self.cc) # Email header names are case-insensitive (RFC 2045), so we have to # accommodate that when doing comparisons. header_names = [key.lower() for key in self.extra_headers] if 'date' not in header_names: msg['Date'] = formatdate() if 'message-id' not in header_names: msg['Message-ID'] = make_msgid() for name, value in self.extra_headers.items(): if name.lower() in ('from', 'to'): # From and To are already handled continue msg[name] = value return msg
def send_credit_notifications(username, course_key): """Sends email notification to user on different phases during credit course e.g., credit eligibility, credit payment etc. """ try: user = User.objects.get(username=username) except User.DoesNotExist: log.error(u'No user with %s exist', username) return course = modulestore().get_course(course_key, depth=0) course_display_name = course.display_name tracking_context = tracker.get_tracker().resolve_context() tracking_id = str(tracking_context.get('user_id')) client_id = str(tracking_context.get('client_id')) events = '&t=event&ec=email&ea=open' tracking_pixel = 'https://www.google-analytics.com/collect?v=1&tid' + tracking_id + '&cid' + client_id + events dashboard_link = _email_url_parser('dashboard') credit_course_link = _email_url_parser('courses', '?type=credit') # get attached branded logo logo_image = cache.get('credit.email.attached-logo') if logo_image is None: branded_logo = { 'title': 'Logo', 'path': settings.NOTIFICATION_EMAIL_EDX_LOGO, 'cid': str(uuid.uuid4()) } logo_image_id = branded_logo['cid'] logo_image = attach_image(branded_logo, 'Header Logo') if logo_image: cache.set('credit.email.attached-logo', logo_image, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: # strip enclosing angle brackets from 'logo_image' cache 'Content-ID' logo_image_id = logo_image.get('Content-ID', '')[1:-1] providers_names = get_credit_provider_attribute_values( course_key, 'display_name') providers_string = make_providers_strings(providers_names) context = { 'full_name': user.get_full_name(), 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'course_name': course_display_name, 'branded_logo': logo_image_id, 'dashboard_link': dashboard_link, 'credit_course_link': credit_course_link, 'tracking_pixel': tracking_pixel, 'providers': providers_string, } # create the root email message notification_msg = MIMEMultipart('related') # add 'alternative' part to root email message to encapsulate the plain and # HTML versions, so message agents can decide which they want to display. msg_alternative = MIMEMultipart('alternative') notification_msg.attach(msg_alternative) # render the credit notification templates subject = _(u'Course Credit Eligibility') if providers_string: subject = _(u'You are eligible for credit from {providers_string}' ).format(providers_string=providers_string) # add alternative plain text message email_body_plain = render_to_string( 'credit_notifications/credit_eligibility_email.txt', context) msg_alternative.attach( SafeMIMEText(email_body_plain, _subtype='plain', _charset='utf-8')) # add alternative html message email_body_content = cache.get('credit.email.css-email-body') if email_body_content is None: html_file_path = file_path_finder( 'templates/credit_notifications/credit_eligibility_email.html') if html_file_path: with open(html_file_path, 'r') as cur_file: cur_text = cur_file.read() # use html parser to unescape html characters which are changed # by the 'pynliner' while adding inline css to html content html_parser = six.moves.html_parser.HTMLParser() email_body_content = html_parser.unescape( with_inline_css(cur_text)) # cache the email body content before rendering it since the # email context will change for each user e.g., 'full_name' cache.set('credit.email.css-email-body', email_body_content, settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT) else: email_body_content = '' email_body = Template(email_body_content).render(context) msg_alternative.attach( SafeMIMEText(email_body, _subtype='html', _charset='utf-8')) # attach logo image if logo_image: notification_msg.attach(logo_image) # add email addresses of sender and receiver from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) to_address = user.email # send the root email message msg = EmailMessage(subject, '', from_address, [to_address]) msg.attach(notification_msg) msg.send()
def send_mass_mail(datatuple, extra={}, fail_silently=False, auth_user=settings.EMAIL_HOST_USER, auth_password=settings.EMAIL_HOST_PASSWORD, tls=getattr(settings, 'EMAIL_TLS', False), encoding=settings.DEFAULT_CHARSET): """Sends a message to each receipient in list. Given a datatuple of (subject, message, from_email, recipient_list), sends each message to each recipient list. Returns the number of e-mails sent. If from_email is None, the DEFAULT_FROM_EMAIL setting is used. If auth_user and auth_password are set, they're used to log in. Note that the message parameter can be either text or one of the SafeMIMExxx methods listed above. """ try: SMTP = smtplib.SMTP if settings.EMAIL_DEBUG: SMTP = STMPMock server = SMTP(settings.EMAIL_HOST, settings.EMAIL_PORT) server.ehlo() server.esmtp_features["auth"] = "LOGIN PLAIN" if tls: server.starttls() server.ehlo() if auth_user and auth_password: server.login(auth_user, auth_password) except: if fail_silently: return raise num_sent = 0 for subject, message, from_email, recipient_list, cc_list in datatuple: if not recipient_list: continue from_email = from_email or settings.DEFAULT_FROM_EMAIL ################################################# msg = None if isinstance(message, SafeMIMEText) or isinstance(message, SafeMIMEMultipart): ## Change below is important! ## msg does not act as a proper dictionary... msg['key'] = value does not ## reset the value for msg['key'], but adds to it! msg = copy.deepcopy(message) else: msg = SafeMIMEText(message.encode(encoding), 'plain', encoding) ################################################# # TODO: we should encode header fields that aren't pure ASCII, see: # http://maxischenko.in.ua/blog/entries/103/python-emails-i18n/ msg['Subject'] = Header(subject, encoding) msg['From'] = from_email msg['To'] = ', '.join(recipient_list) msg['Date'] = rfc822.formatdate() if cc_list: msg['Cc'] = ', '.join(cc_list) recipient_list.extend(cc_list) if extra: for key in extra.keys(): msg[key] = extra[key] try: server.sendmail(from_email, recipient_list, msg.as_string()) num_sent += 1 except: if not fail_silently: raise try: server.quit() except: if fail_silently: return raise return num_sent
def message(self): from ..utils import get_attachment_filename_from_url, replace_cid_and_change_headers to = anyjson.loads(self.to) cc = anyjson.loads(self.cc) bcc = anyjson.loads(self.bcc) if self.send_from.from_name: # Add account name to From header if one is available from_email = '"%s" <%s>' % (Header( u'%s' % self.send_from.from_name, 'utf-8'), self.send_from.email_address) else: # Otherwise only add the email address from_email = self.send_from.email_address html, text, inline_headers = replace_cid_and_change_headers( self.body, self.original_message_id) email_message = SafeMIMEMultipart('related') email_message['Subject'] = self.subject email_message['From'] = from_email if to: email_message['To'] = ','.join(list(to)) if cc: email_message['cc'] = ','.join(list(cc)) if bcc: email_message['bcc'] = ','.join(list(bcc)) email_message_alternative = SafeMIMEMultipart('alternative') email_message.attach(email_message_alternative) email_message_text = SafeMIMEText(text, 'plain', 'utf-8') email_message_alternative.attach(email_message_text) email_message_html = SafeMIMEText(html, 'html', 'utf-8') email_message_alternative.attach(email_message_html) for attachment in self.attachments.all(): if attachment.inline: continue try: storage_file = default_storage._open( attachment.attachment.name) except IOError: logger.exception('Couldn\'t get attachment, not sending %s' % self.id) return False filename = get_attachment_filename_from_url( attachment.attachment.name) storage_file.open() content = storage_file.read() storage_file.close() content_type, encoding = mimetypes.guess_type(filename) if content_type is None or encoding is not None: content_type = 'application/octet-stream' main_type, sub_type = content_type.split('/', 1) if main_type == 'text': msg = MIMEText(content, _subtype=sub_type) elif main_type == 'image': msg = MIMEImage(content, _subtype=sub_type) elif main_type == 'audio': msg = MIMEAudio(content, _subtype=sub_type) else: msg = MIMEBase(main_type, sub_type) msg.set_payload(content) Encoders.encode_base64(msg) msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename)) email_message.attach(msg) # Add the inline attachments to email message header for inline_header in inline_headers: main_type, sub_type = inline_header['content-type'].split('/', 1) if main_type == 'image': msg = MIMEImage(inline_header['content'], _subtype=sub_type, name=os.path.basename( inline_header['content-filename'])) msg.add_header('Content-Disposition', inline_header['content-disposition'], filename=os.path.basename( inline_header['content-filename'])) msg.add_header('Content-ID', inline_header['content-id']) email_message.attach(msg) return email_message
def build_plain_message(): msg = SafeMIMEText(self.body, self.content_subtype, encoding) msg = self._create_message(msg) return msg
def create_multilingual_mail(template_name: str, subject: str, context: dict, **kwargs) -> EmailMessage: """ Creates an instance of EmailMessage. If multiple languages exist for the given template name, it will create an RFC8255_ compatible email, and if only one language exists, it will simply send an email in that language, without bothering with any multilingual crap. As of implementing this method, very few to no email clients support RFC8255_ and so it is recommended to either use create_mail with a preference language or use this method and only create one set of language templates. :param template_name: The template that should be used for this email :param subject: The subject of this email :param context: The context for the template :param kwargs: Any other data that should be passed to the EmailMessage constructor :return: an EmailMessage .. _RFC8255: https://tools.ietf.org/html/rfc8255 """ _ensure_setup() kwargs['headers'] = kwargs['headers'] if 'headers' in kwargs else {} kwargs['headers'] = {"X-Mailer": get_mailer_name(), **kwargs['headers']} langs = templates[template_name] if len(langs.items()) == 1: lang, tpls = list(langs.items())[0] with language(lang): kwargs['headers'] = {"Content-Language": lang, **kwargs['headers']} msg = MultiTemplatableEmailMessage(subject=subject, body=False, **kwargs) for template in tpls: msg.attach_alternative( SafeMIMEText(_text=get_template(template['file']).render({ 'locale': template['locale'], **context, }), _subtype=template['subtype'], _charset=msg.encoding or settings.DEFAULT_CHARSET), 'unknown/unknown') return msg msg = MultilingualEmailMessage(subject=subject, **kwargs) for lang, tpls in langs.items(): with language(lang): lang_alt = MIMEMultipart(_subtype='alternative') lang_alt.add_header("Subject", _(subject)) for template in tpls: lang_alt.attach( SafeMIMEText(get_template(template['file']).render({ 'locale': template['locale'], **context, }), _subtype=template['subtype'])) msg.add_language(lang, lang_alt) info = MIMEMultipart(_subtype='alternative') info.attach( SafeMIMEText(get_template( "steambird/../templates/mail/multilingual_header.html").render({}), _subtype="html", _charset="utf-8")) info.attach( SafeMIMEText(get_template( "steambird/../templates/mail/multilingual_header.plain").render( {}), _subtype="plain", _charset="utf-8")) info.add_header("Content-Disposition", "inline") msg.attach(info) return msg