def create_mail(subject, message, from_email, recipient, message_html=None, attachments=None, rfc2822_headers=None): headers = {'Message-ID': make_msgid()} if rfc2822_headers: headers.update(rfc2822_headers) if message is None: # make text version out of html if text version is missing message = html2text(message_html) if message_html: msg = EmailMultiAlternatives(subject, message, from_email, [recipient], headers=headers) msg.attach_alternative(message_html, "text/html") else: msg = EmailMessage(subject, message, from_email, [recipient], headers=headers) if attachments: for filename, content, mimetype in attachments: msg.attach(filename, content, mimetype) return msg
def form_valid(self, form, **kwargs): email = form.cleaned_data['email'] name = form.cleaned_data['name'] from_email = '"{}" <{}>'.format(name, email) reply_to = from_email to_email = [settings.HELPDESK_ADDRESS] headers = {'Reply-To': reply_to, 'Message-Id': make_msgid()} # do spam checks, assignment email, cc_asker if form.cleaned_data['author']: return self.handle_spam(form, from_email, headers) if settings.ENABLE_HELPDESKQ: self.handle_assignment(form, headers=headers, message_id=headers.get('Message-Id', None)) if settings.HELPDESK_CC_ASKER: self.handle_confirmation(form, from_email) return super(HelpdeskEmailerView, self).form_valid( form, from_email=from_email, to_email=to_email, headers=headers, **kwargs)
def emailimage_media(context, image): if not context.get('render_mail', False): return default_storage.url(image) if 'attachments' not in context: context['attachments'] = {} key = "media:" + image if key not in context['attachments']: path = default_storage.path(image) if path is not None: with open(path, 'rb') as fh: image_data = fh.read() else: return "" # not found! image_cid = make_msgid('img') mime = MIMEImage(image_data) mime.add_header('Content-ID', image_cid) context['attachments'][key] = (mime, image_cid[1:-1]) return "cid:{}".format(context['attachments'][key][1])
def deliver(subject, message, from_email, recipient_list, message_html=None, attachments= None, callback=None, **kwargs): """ send email to recipient list (filter them through settings.EMAIL_WHITELIST if exists), puts messages to send into celery queue returns a list of (msgid, rawmessage) for each messages to be sent if callback is set to a celery task: it will be called on every single recipient delivery with callback(msgid, status) """ # make a list if only one recipient (and therefore string) is there if isinstance(recipient_list, basestring): recipient_list = [recipient_list] # filter out recipients which are not in the whitelist mylist = set(recipient_list) bad = None if hasattr(settings, "EMAIL_WHITELIST") and settings.EMAIL_WHITELIST: bad = set([x for x in recipient_list if x not in settings.EMAIL_WHITELIST]) if bad: logger = logging.getLogger() logger.warning('BAD EMAILS: %s are bad out of %s' % (str(bad), str(mylist))) recipient_list = list(mylist - bad) sentids = [] for recipient in recipient_list: msgid = make_msgid() msg = create_mail(subject, message, from_email, recipient, message_html, attachments, msgid) queued_mail_send.apply_async(args=[msgid, msg, from_email, recipient, callback], countdown=3) #queued_mail_send(msgid, msg, from_email, recipient, callback) sentids += [[msgid, msg.message()]] return sentids
def test_embedded_images(self): image_data = self.sample_image_content() # Read from a png file image_cid = make_msgid( "img") # Content ID per RFC 2045 section 7 (with <...>) image_cid_no_brackets = image_cid[ 1:-1] # Without <...>, for use as the <img> tag src text_content = 'This has an inline image.' html_content = '<p>This has an <img src="cid:%s" alt="inline" /> image.</p>' % image_cid_no_brackets email = mail.EmailMultiAlternatives('Subject', text_content, '*****@*****.**', ['*****@*****.**']) email.attach_alternative(html_content, "text/html") image = MIMEImage(image_data) image.add_header('Content-ID', image_cid) email.attach(image) email.send() data = self.get_api_call_data() self.assertEqual(data['message']['text'], text_content) self.assertEqual(data['message']['html'], html_content) self.assertEqual(len(data['message']['images']), 1) self.assertEqual(data['message']['images'][0]["type"], "image/png") self.assertEqual(data['message']['images'][0]["name"], image_cid) self.assertEqual(decode_att(data['message']['images'][0]["content"]), image_data) # Make sure neither the html nor the inline image is treated as an attachment: self.assertFalse('attachments' in data['message'])
def emailimage_static(context, image): if not context.get('render_mail', False): t = template.Template(r"{% load staticfiles %}{% static '" + image + "' %}") return t.render(context) if 'attachments' not in context: context['attachments'] = {} key = "static:" + image if key not in context['attachments']: path = finders.find(image) if path is not None: with open(path, 'rb') as fh: image_data = fh.read() else: return "" # not found! image_cid = make_msgid('img') mime = MIMEImage(image_data) mime.add_header('Content-ID', image_cid) context['attachments'][key] = (mime, image_cid[1:-1]) return "cid:{}".format(context['attachments'][key][1])
def attach_inline_image(message, content, filename=None, subtype=None, idstring="img", domain=None): """Add inline image to an EmailMessage, and return its content id""" content_id = make_msgid(idstring, domain) # Content ID per RFC 2045 section 7 (with <...>) image = MIMEImage(content, subtype) image.add_header('Content-Disposition', 'inline', filename=filename) image.add_header('Content-ID', content_id) message.attach(image) return unquote(content_id) # Without <...>, for use as the <img> tag src
def make_message_id(self): """Returns a Message-ID that could be used for this payload Tries to use the from_email's domain as the Message-ID's domain """ try: _, domain = self.data["from"].split("@") except (AttributeError, KeyError, TypeError, ValueError): domain = None return make_msgid(domain=domain)
def make_message_id(self): """Returns a Message-ID that could be used for this payload Tries to use the from_email's domain as the Message-ID's domain """ try: _, domain = self.data["from"]["email"].split("@") except (AttributeError, KeyError, TypeError, ValueError): domain = None return make_msgid(domain=domain)
def attach_inline_image(message, content, filename=None, subtype=None, idstring="img", domain=None): """Add inline image to an EmailMessage, and return its content id""" if domain is None: # Avoid defaulting to hostname that might end in '.com', because some ESPs # use Content-ID as filename, and Gmail blocks filenames ending in '.com'. domain = 'inline' # valid domain for a msgid; will never be a real TLD content_id = make_msgid(idstring, domain) # Content ID per RFC 2045 section 7 (with <...>) image = MIMEImage(content, subtype) image.add_header('Content-Disposition', 'inline', filename=filename) image.add_header('Content-ID', content_id) message.attach(image) return unquote(content_id) # Without <...>, for use as the <img> tag src
def _embed_images(template, images, test=False): for image in images: src_attr = 'src="cid:{}"'.format(image.placeholder_name) if test: replace_src = 'src="{}"'.format(image.image.url) else: if not len(image.content_id): image.content_id = make_msgid(image.placeholder_name) image.save() replace_src = 'src="cid:{}"'.format(image.content_id[1:-1]) template = template.replace(src_attr, replace_src) return template
def receive(self, subject, message, from_email, recipient_list, message_html=None, attachments=None, connecting_host="localhost"): ''' Fakes an incoming message trough ecsmail server ''' if isinstance(recipient_list, basestring): recipient_list = [recipient_list] sentids = [] for recipient in recipient_list: msgid = make_msgid() msg = create_mail(subject, message, from_email, recipient, message_html, attachments, msgid) self.logger.debug("Receiving Mail from %s to %s, message= %s" % (from_email, recipient, str(msg.message()))) routing.Router.deliver(MailRequest(connecting_host, from_email, recipient, str(msg.message()))) sentids += [[msgid, msg.message()]] return sentids
def send_out_email(self): lv = self.latest_version() msg_id = getattr(self, 'message_id', None) if not msg_id: msg_id = make_msgid() self.message_id = msg_id self.save() print "New msgid '%s' added to %s." % (msg_id, self) parent = lv.parent parent_msg_id = None if parent: parent_msg_id = getattr(parent, 'message_id', None) author_full_name = lv.author.get_full_name() author_email = lv.author.email author = "%s <%s>" % (author_full_name, author_email) heap = self.get_heap() recipients = [user['email'] for user in heap.user_fields.values()] print "Emails will be sent out to:" for to in recipients: print to msg_subject = self.get_conversation().subject # tags = body = self.latest_version().text if hasattr(settings, 'HEAP_EMAIL_DOMAIN'): reply_address = heap.short_name + '@' + settings.HEAP_EMAIL_DOMAIN else: reply_address = settings.EMAIL_HOST_USER extra_headers = {'Reply-To': reply_address, 'From': author} if msg_id: extra_headers['Message-ID'] = msg_id if parent_msg_id: extra_headers['In-Reply-To'] = parent_msg_id # TODO use a meaningful sender address! email = EmailMessage(msg_subject, body, settings.EMAIL_HOST_USER, recipients, None, headers=extra_headers) email.send()
def test_answer_timeout_recipient(self): old_timestamp = self.last_message.timestamp recipient = self.last_message.return_address self.last_message.timestamp = timezone.now() - timedelta( days=EcsMailReceiver.ANSWER_TIMEOUT+ 1) self.last_message.save() msgid = make_msgid() msg = MIMEText('This is a test reply.') msg['From'] = 'Bob <*****@*****.**>' msg['To'] = 'Alice <{}>'.format(recipient) msg['Message-ID'] = msgid self.assertEqual(EcsMailReceiver.ANSWER_TIMEOUT, 365) code, description = self.process_message([recipient], msg) self.assertEqual(code, 553) self.assertEqual(description, 'Invalid recipient <{}>'.format(recipient))
def form_valid(self, form, **kwargs): email = form.cleaned_data['email'] name = form.cleaned_data['name'] from_email = '"{}" <{}>'.format(name, email) reply_to = from_email headers = {'Reply-To': reply_to, 'Message-Id': make_msgid()} if self.check_spam and form.cleaned_data['author']: return self.handle_spam(form, from_email, headers) return super(CompanyEmailerView, self).form_valid( form, to_email=[settings.INDREL_ADDRESS], **kwargs)
def test_plain(self): recipient = self.last_message.return_address msgid = make_msgid() msg = MIMEText('This is a test reply.') msg['From'] = 'Bob <*****@*****.**>' msg['To'] = 'Alice <{}>'.format(recipient) msg['Message-ID'] = msgid code, description = self.process_message([recipient], msg) self.assertEqual(code, 250) self.assertEqual(description, 'Ok') self.assertEqual(self.thread.messages.count(), 2) reply = self.thread.messages.get(rawmsg_msgid=msgid) self.assertEqual(reply.text, 'This is a test reply.') self.assertEqual(reply.sender, self.bob) self.assertEqual(reply.receiver, self.alice)
def message(self): """ Returns a email.message.Message object. The object is based on original_message if original_message is not None. """ if self.original_message is None: result_message = super().message() else: msg = self._create_message(self.original_message) # Remove managed header from message for key in self.manage_headers: if key in msg: del msg[key] msg['To'] = ', '.join(self.to) or self.extra_headers.get('to', '') msg['Cc'] = ', '.join(self.cc) or self.extra_headers.get('cc', '') msg['Bcc'] = ', '.join(self.bcc) or self.extra_headers.get('bcc', '') msg['From'] = self.from_email or self.extra_headers.get('from', '') msg['Reply-To'] = ', '.join(self.reply_to) or self.extra_headers.get('reply-to', '') msg['Subject'] = self.subject or self.extra_headers.get('subject', '*** No Subject ***') # Check for missing headers 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(domain=DNS_NAME) # Set extra headers for name, value in self.extra_headers.items(): if name.lower() in self.manage_headers: continue if name.lower() in msg: del msg[name] msg[name] = value if self.body is not None: log.warning('The message method constructed a email.message.Message based on ' 'original_message. The content of body is lost!') result_message = msg return result_message
def inline_images(message, html): """Given HTML with inline data images, convert these to attachments, and add HTML as an alternative """ images = re.findall(r'<img.*?src="data:image/png;base64,.*?">', html) for i, image_tag in enumerate(images): filename = "img{}.png".format(i) data = re.findall(r'<img.*?src="data:image/png;base64,(.*?)">', image_tag)[0] content_id = make_msgid( "img") # Content ID per RFC 2045 section 7 (with <...>) image = MIMEImage(data, "png", _encoder=lambda x: x) image.add_header("Content-Disposition", "inline", filename=filename) image.add_header("Content-ID", content_id) image.add_header("Content-Transfer-Encoding", "base64") message.attach(image) html = html.replace(image_tag, '<img src="cid:{}">'.format(unquote(content_id))) message.attach_alternative(html, "text/html") return message
def create_mail(subject, message, from_email, recipient, message_html=None, attachments= None, msgid=None, **kwargs): ''' ''' if msgid is None: msgid = make_msgid() headers = {'Message-ID': msgid} if message is None: # make text version out of html if text version is missing message = whitewash(message_html) if message and message_html: msg = EmailMultiAlternatives(subject, message, from_email, [recipient], headers= headers) msg.attach_alternative(message_html, "text/html") else: msg = EmailMessage(subject, message, from_email, [recipient], headers= headers) if attachments: for attachment in attachments: filename = content = mimetype = encoding = None if isinstance(attachment, tuple) or isinstance(attachment, list): filename, content = attachment[0:2] if len(attachment) == 3: mimetype = attachment[2] else: mimetype, encoding = mimetypes.guess_type(filename) elif isinstance(attachment, basestring): filename = attachment if not os.path.exists(filename): raise IOError("attachment file not found %s" % filename) mimetype, encoding = mimetypes.guess_type(filename) content = open(filename, "rb").read() else: raise TypeError('dont know how to handle attachment from type %s' % (str(type(attachment)))) if not mimetype: raise TypeError("No content type given, and couldn't guess from the filename: %s" % filename) msg.attach(filename, content, mimetype) return msg
def message(self): if len(self.to) == 1: carrier = get_carrier_by_email_address(self.to[0]) or 'N' else: # TODO carrier = 'N' msg = make_message(carrier, smart_unicode(self.subject), smart_unicode(self.body), errors=self.errors) msg['From'] = self.from_email msg['To'] = ', '.join(self.to) msg['Date'] = formatdate() msg['Message-ID'] = make_msgid() if self.bcc: msg['Bcc'] = ', '.join(self.bcc) for name, value in self.extra_headers.items(): msg[name] = value return msg
def send_mail(self): """Send event request email generated using form data.""" template = get_template('email/event.txt') context = self.cleaned_data email = template.render(context) subject, body = email.split("\n", 1) from_email = settings.EVENT_EMAIL to_email = self.cleaned_data['email'] message_id = make_msgid() email = EmailMessage(subject, body, from_email, [to_email, from_email], headers={'Message-ID': message_id} ) email.send() event_url = self.tracker_event(context) tracker_email = EmailMessage(subject, event_url, from_email, [from_email], headers={'In-Reply-To': message_id}) tracker_email.send() return self.cleaned_data
def test_mixed(self): recipient = self.last_message.return_address msgid = make_msgid() msg = MIMEMultipart('alternative') msg.attach(MIMEText('<html><body><p>HTML</p></body></html>', 'html')) msg.attach(MIMEText('PLAIN', 'plain')) msg['From'] = 'Bob <*****@*****.**>' msg['To'] = 'Alice <{}>'.format(recipient) msg['Message-ID'] = msgid code, description = self.process_message([recipient], msg) self.assertEqual(code, 250) self.assertEqual(description, 'Ok') self.assertEqual(self.thread.messages.count(), 2) reply = self.thread.messages.get(rawmsg_msgid=msgid) # When both HTML and plain text is available, the latter takes # precedence. self.assertEqual(reply.text, 'PLAIN') self.assertEqual(reply.sender, self.bob) self.assertEqual(reply.receiver, self.alice)
def test_embedded_images(self): image_data = self.sample_image_content() # Read from a png file image_cid = make_msgid("img") # Content ID per RFC 2045 section 7 (with <...>) image_cid_no_brackets = image_cid[1:-1] # Without <...>, for use as the <img> tag src text_content = 'This has an inline image.' html_content = '<p>This has an <img src="cid:%s" alt="inline" /> image.</p>' % image_cid_no_brackets email = mail.EmailMultiAlternatives('Subject', text_content, '*****@*****.**', ['*****@*****.**']) email.attach_alternative(html_content, "text/html") image = MIMEImage(image_data) image.add_header('Content-ID', image_cid) email.attach(image) email.send() data = self.get_api_call_data() self.assertEqual(data['message']['text'], text_content) self.assertEqual(data['message']['html'], html_content) self.assertEqual(len(data['message']['images']), 1) self.assertEqual(data['message']['images'][0]["type"], "image/png") self.assertEqual(data['message']['images'][0]["name"], image_cid) self.assertEqual(decode_att(data['message']['images'][0]["content"]), image_data) # Make sure neither the html nor the inline image is treated as an attachment: self.assertFalse('attachments' in data['message'])
def generate_cid(self): self._content_id = make_msgid('img', self.domain)
def send_mass_mail_recorded(datatuple, mailbox, related_object=None, bcc=None, fail_silently=False, auth_user=None, auth_password=None, connection=None): """ Shadows Django's send mass mail, but records each outgoing message to the specified mailbox. Also added optional bcc argument. """ connection = connection or get_connection( username=auth_user, password=auth_password, fail_silently=fail_silently ) messages = [EmailMessage(subject, message, sender, recipient, bcc, headers={'Message-ID': make_msgid()}) for subject, message, sender, recipient in datatuple] record_messages(mailbox, messages, related_object) return connection.send_messages(messages)
def send_mass_html_mail_recorded(datatuple, mailbox, related_object=None, bcc=None, fail_silently=False, user=None, password=None, connection=None): """ Like above, but lets you send HTML emails. """ connection = connection or get_connection( username=user, password=password, fail_silently=fail_silently ) messages = [] for subject, text, html, from_email, recipient in datatuple: message = EmailMultiAlternatives(subject, text, from_email, recipient, bcc, headers={'Message-ID': make_msgid()}) message.attach_alternative(html, 'text/html') messages.append(message) record_messages(mailbox, messages) return connection.send_messages(messages)
def message(self): """ Returns the final message to be sent, including all headers etc. Content and attachments are encrypted using GPG in PGP/MIME format (RFC 3156). """ def build_plain_message(): msg = SafeMIMEText(self.body, self.content_subtype, encoding) msg = self._create_message(msg) return msg 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 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 encoding = self.encoding or settings.DEFAULT_CHARSET # build message including attachments as it would also be built without GPG msg = build_plain_message() # encrypt whole message including attachments encrypted_msg = self._encrypt(str(msg)) # build new message object wrapping the encrypted message msg = SafeMIMEMultipart(_subtype=self.encrypted_subtype, encoding=encoding, protocol='application/pgp-encrypted') version_attachment = build_version_attachment() gpg_attachment = build_gpg_attachment() msg.attach(version_attachment) msg.attach(gpg_attachment) self.extra_headers['Content-Transfer-Encoding'] = '7bit' # add headers # everything below this line has not been modified when overriding message() ############################################################################ msg['Subject'] = self.subject msg['From'] = self.extra_headers.get('From', self.from_email) msg['To'] = self.extra_headers.get('To', ', '.join(map(force_text, self.to))) if self.cc: msg['Cc'] = ', '.join(map(force_text, self.cc)) if self.reply_to: msg['Reply-To'] = self.extra_headers.get('Reply-To', ', '.join(map(force_text, self.reply_to))) # 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: # Use cached DNS_NAME for performance msg['Message-ID'] = make_msgid(domain=DNS_NAME) 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 message(self): """ Returns the final message to be sent, including all headers etc. Content and attachments are encrypted using GPG in PGP/MIME format (RFC 3156). """ def build_plain_message(): msg = SafeMIMEText(self.body, self.content_subtype, encoding) msg = self._create_message(msg) return msg 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 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 encoding = self.encoding or settings.DEFAULT_CHARSET # build message including attachments as it would also be built without GPG msg = build_plain_message() # encrypt whole message including attachments encrypted_msg = self._encrypt(str(msg)) # build new message object wrapping the encrypted message msg = SafeMIMEMultipart(_subtype=self.encrypted_subtype, encoding=encoding, protocol='application/pgp-encrypted') version_attachment = build_version_attachment() gpg_attachment = build_gpg_attachment() msg.attach(version_attachment) msg.attach(gpg_attachment) self.extra_headers['Content-Transfer-Encoding'] = '7bit' # add headers # everything below this line has not been modified when overriding message() ############################################################################ msg['Subject'] = self.subject msg['From'] = self.extra_headers.get('From', self.from_email) msg['To'] = self.extra_headers.get('To', ', '.join(map(force_text, self.to))) if self.cc: msg['Cc'] = ', '.join(map(force_text, self.cc)) if self.reply_to: msg['Reply-To'] = self.extra_headers.get( 'Reply-To', ', '.join(map(force_text, self.reply_to))) # 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: # Use cached DNS_NAME for performance msg['Message-ID'] = make_msgid(domain=DNS_NAME) 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 create_messageid(self, idstring=None): idstring = idstring or utils.get_random_string(8) return mail.make_msgid(idstring=idstring, domain=self.domain)