def test_with_users(self): project = self.project user_a = User.objects.create(email='*****@*****.**') user_b = User.objects.create(email='*****@*****.**') user_c = User.objects.create(email='*****@*****.**') UserOption.objects.create( user=user_b, key='alert_email', value='*****@*****.**', ) UserOption.objects.create( user=user_c, project=project, key='mail:email', value='*****@*****.**', ) msg = MessageBuilder( subject='Test', body='hello world', html_body='<!DOCTYPE html>\n<b>hello world</b>', ) msg.add_users([user_a.id, user_b.id, user_c.id], project=project) msg.send() assert len(mail.outbox) == 3 assert sorted([out.to[0] for out in mail.outbox]) == [ '*****@*****.**', '*****@*****.**', '*****@*****.**', ]
def test_fake_dont_send(self): project = self.project user_a = User.objects.create(email=create_fake_email('foo', 'fake')) user_b = User.objects.create(email=create_fake_email('bar', 'fake')) user_c = User.objects.create(email=create_fake_email('baz', 'fake')) UserOption.objects.create( user=user_b, key='alert_email', value=create_fake_email('fizzle', 'fake'), ) UserOption.objects.create( user=user_c, project=project, key='mail:email', value=create_fake_email('bazzer', 'fake'), ) msg = MessageBuilder( subject='Test', body='hello world', html_body='<!DOCTYPE html>\n<b>hello world</b>', ) msg.add_users([user_a.id, user_b.id, user_c.id], project=project) msg.send() assert len(mail.outbox) == 0
def handle_project(plugin, project, stream): stream.write('# Project: %s\n' % project) from sentry.utils.email import MessageBuilder msg = MessageBuilder('test') msg.add_users(plugin.get_sendable_users(project), project) for email in msg._send_to: stream.write(email + '\n')
def _build_message(self, project, subject, template=None, html_template=None, body=None, reference=None, reply_reference=None, headers=None, context=None, send_to=None, type=None): if send_to is None: send_to = self.get_send_to(project) if not send_to: logger.debug('Skipping message rendering, no users to send to.') return subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() subject_prefix = force_text(subject_prefix) subject = force_text(subject) msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, type=type, context=context, reference=reference, reply_reference=reply_reference, ) msg.add_users(send_to, project=project) return msg
def send_notification_as_email( notification: BaseNotification, recipients: Iterable[Team | User], shared_context: Mapping[str, Any], extra_context_by_actor_id: Mapping[int, Mapping[str, Any]] | None, ) -> None: for recipient in recipients: with sentry_sdk.start_span(op="notification.send_email", description="one_recipient"): if isinstance(recipient, Team): # TODO(mgaeta): MessageBuilder only works with Users so filter out Teams for now. continue log_message(notification, recipient) with sentry_sdk.start_span(op="notification.send_email", description="build_message"): msg = MessageBuilder( **get_builder_args(notification, recipient, shared_context, extra_context_by_actor_id)) with sentry_sdk.start_span(op="notification.send_email", description="send_message"): # TODO: find better way of handling this add_users_kwargs = {} if isinstance(notification, ProjectNotification): add_users_kwargs["project"] = notification.project msg.add_users([recipient.id], **add_users_kwargs) msg.send_async() notification.record_notification_sent(recipient, ExternalProviders.EMAIL)
def send_notification_as_email( notification: BaseNotification, recipients: Iterable[Union["Team", "User"]], shared_context: Mapping[str, Any], extra_context_by_user_id: Optional[Mapping[int, Mapping[str, Any]]], ) -> None: headers = get_headers(notification) for recipient in recipients: if isinstance(recipient, Team): # TODO(mgaeta): MessageBuilder only works with Users so filter out Teams for now. continue extra_context = (extra_context_by_user_id or {}).get(recipient.id, {}) log_message(notification, recipient) context = get_context(notification, recipient, shared_context, extra_context) subject = get_subject_with_prefix(notification, context=context) msg = MessageBuilder( subject=subject, context=context, template=notification.get_template(), html_template=notification.get_html_template(), headers=headers, reference=notification.get_reference(), reply_reference=notification.get_reply_reference(), type=notification.get_type(), ) msg.add_users([recipient.id], project=notification.project) msg.send_async()
def build_message(timestamp, duration, organization, user, report): start, stop = interval = _to_interval(timestamp, duration) duration_spec = durations[duration] message = MessageBuilder( subject=u'{} Report for {}: {} - {}'.format( duration_spec.adjective.title(), organization.name, date_format(start), date_format(stop), ), template='sentry/emails/reports/body.txt', html_template='sentry/emails/reports/body.html', type='report.organization', context={ 'duration': duration_spec, 'interval': { 'start': date_format(start), 'stop': date_format(stop), }, 'organization': organization, 'personal': fetch_personal_statistics( interval, organization, user, ), 'report': to_context(report), 'user': user, }, ) message.add_users((user.id,)) return message
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, group=None, headers=None, context=None, fail_silently=False): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix try: subject_prefix = subject_prefix.encode("utf-8") subject = subject.encode("utf-8") except UnicodeDecodeError: subject_prefix = unicode(subject_prefix, "utf-8") subject = unicode(subject, "utf-8") msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, reference=group, ) msg.add_users(send_to, project=project) return msg.send(fail_silently=fail_silently)
def send_notification_as_email( notification: BaseNotification, users: Set[User], shared_context: Mapping[str, Any], extra_context_by_user_id: Optional[Mapping[int, Mapping[str, Any]]], ) -> None: headers = get_headers(notification) subject = get_subject_with_prefix(notification) type = get_email_type(notification) for user in users: extra_context = (extra_context_by_user_id or {}).get(user.id, {}) log_message(notification, user) msg = MessageBuilder( subject=subject, context=get_context(notification, user, shared_context, extra_context), template=notification.get_template(), html_template=notification.get_html_template(), headers=headers, reference=notification.get_reference(), reply_reference=notification.get_reply_reference(), type=type, ) msg.add_users([user.id], project=notification.project) msg.send_async()
def send(self): if not self.should_email(): return participants = self.get_participants() if not participants: return activity = self.activity project = self.project group = self.group context = self.get_base_context() context.update(self.get_context()) template = self.get_template() html_template = self.get_html_template() email_type = self.get_email_type() headers = self.get_headers() for user, reason in participants.items(): if group: context.update( { 'reason': GroupSubscriptionReason.descriptions.get( reason, "are subscribed to this issue", ), 'unsubscribe_link': generate_signed_link( user.id, 'sentry-account-email-unsubscribe-issue', kwargs={'issue_id': group.id}, ), } ) user_context = self.get_user_context(user) if user_context: user_context.update(context) else: user_context = context msg = MessageBuilder( subject=self.get_subject_with_prefix(), template=template, html_template=html_template, headers=headers, type=email_type, context=user_context, reference=activity, reply_reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def send_notification_as_email( notification: ActivityNotification, user: User, context: Mapping[str, Any] ) -> None: msg = MessageBuilder( subject=notification.get_subject_with_prefix(), template=notification.get_template(), html_template=notification.get_html_template(), headers=notification.get_headers(), type=notification.get_email_type(), context=context, reference=notification.activity, reply_reference=notification.group, ) msg.add_users([user.id], project=notification.project) msg.send_async()
def send_notification(self): from sentry.utils.email import MessageBuilder, group_id_to_email if not self.group_id: return if self.type not in (Activity.NOTE, Activity.ASSIGNED): return send_to = self.get_recipients() if not send_to: return author = self.user.first_name or self.user.username subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) context = { 'data': self.data, 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.id), } template_name = self.get_type_display() msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/activity/{}.txt'.format(template_name), html_template='sentry/emails/activity/{}.html'.format(template_name), headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def send(self): if not self.should_email(): return users = self.get_participants() if not users: return activity = self.activity project = self.project group = self.group context = self.get_base_context() context.update(self.get_context()) subject_prefix = self._get_subject_prefix() subject = (u'{}{}'.format( subject_prefix, self.get_subject(), )).encode('utf-8') template = self.get_template() html_template = self.get_html_template() email_type = self.get_email_type() headers = self.get_headers() for user in users: if group: context['unsubscribe_link'] = generate_signed_link( user.id, 'sentry-account-email-unsubscribe-issue', kwargs={'issue_id': group.id}, ) msg = MessageBuilder( subject=subject, template=template, html_template=html_template, headers=headers, type=email_type, context=context, reference=activity, reply_reference=group, ) msg.add_users([user.id], project=project) msg.send_async()
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, headers=None, context=None, fail_silently=False): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, ) msg.add_users(send_to, project=project) return msg.send(fail_silently=fail_silently)
def _send_mail(self, subject, template=None, html_template=None, body=None, project=None, group=None, headers=None, context=None): send_to = self.get_send_to(project) if not send_to: return subject_prefix = self.get_option('subject_prefix', project) or self.subject_prefix subject_prefix = force_text(subject_prefix) subject = force_text(subject) msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, context=context, reference=group, ) msg.add_users(send_to, project=project) return msg.send()
def send_notification_as_email( notification: ActivityNotification, users: Mapping[User, int], shared_context: Mapping[str, Any], ) -> None: headers = get_headers(notification) subject = get_subject_with_prefix(notification) type = get_email_type(notification) for user, reason in users.items(): msg = MessageBuilder( subject=subject, template=notification.get_template(), html_template=notification.get_html_template(), headers=headers, type=type, context=get_context(notification, user, reason, shared_context), reference=notification.activity, reply_reference=notification.group, ) msg.add_users([user.id], project=notification.project) msg.send_async()
def _build_message(self, project, subject, template=None, html_template=None, body=None, reference=None, reply_reference=None, headers=None, context=None, send_to=None, type=None): if send_to is None: send_to = self.get_send_to(project) if not send_to: logger.debug('Skipping message rendering, no users to send to.') return tags = {} if 'event' in context: tags = dict(context['event'].get_tags()) subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() try: subject_prefix = subject_prefix.format(tags=tags) except Exception: pass subject_prefix = force_text(subject_prefix) subject = force_text(subject) msg = MessageBuilder( subject='%s%s' % (subject_prefix, subject), template=template, html_template=html_template, body=body, headers=headers, type=type, context=context, reference=reference, reply_reference=reply_reference, ) msg.add_users(send_to, project=project) return msg
def send_notification(self): from sentry.models import User, UserOption, ProjectOption from sentry.utils.email import MessageBuilder, group_id_to_email if self.type != Activity.NOTE or not self.group: return # TODO(dcramer): some of this logic is duplicated in NotificationPlugin # fetch access group members user_id_list = set( User.objects.filter( accessgroup__projects=self.project, is_active=True ).exclude( id=self.user_id, ).values_list('id', flat=True) ) if self.project.team: # fetch team members user_id_list |= set( u_id for u_id in self.project.team.member_set.filter( user__is_active=True, ).exclude( user__id=self.user_id, ).values_list('user', flat=True) ) if not user_id_list: return disabled = set(UserOption.objects.filter( user__in=user_id_list, key='subscribe_notes', value=u'0', ).values_list('user', flat=True)) send_to = filter(lambda u_id: u_id not in disabled, user_id_list) if not send_to: return author = self.user.first_name or self.user.username subject_prefix = ProjectOption.objects.get_value( self.project, 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' subject = '%s%s' % (subject_prefix, self.event.get_email_subject()) context = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.pk), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) try: msg.send() except Exception as e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option("subject_prefix", settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + " " if self.group: subject = "%s%s" % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = "%sRelease %s" % (subject_prefix, self.data["version"]) else: raise NotImplementedError headers = {} context = { "data": self.data, "author": author, "project": self.project, "project_link": absolute_uri( reverse("sentry-stream", kwargs={"organization_slug": org.slug, "project_id": project.slug}) ), } if self.group: headers.update({"X-Sentry-Reply-To": group_id_to_email(self.group.id)}) context.update({"group": self.group, "link": self.group.get_absolute_url()}) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update( { "release": Release.objects.get(version=self.data["version"], project=project), "release_link": absolute_uri( reverse( "sentry-release-details", kwargs={ "organization_slug": org.slug, "project_id": project.slug, "version": self.data["version"], }, ) ), } ) template_name = self.get_type_display() msg = MessageBuilder( subject=subject, context=context, template="sentry/emails/activity/{}.txt".format(template_name), html_template="sentry/emails/activity/{}.html".format(template_name), headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def send_notification(self): from sentry.models import User, UserOption from sentry.utils.email import MessageBuilder, group_id_to_email if self.type != Activity.NOTE or not self.group: return # TODO(dcramer): some of this logic is duplicated in NotificationPlugin # fetch access group members user_id_list = set( User.objects.filter(accessgroup__projects=self.project, is_active=True).exclude( id=self.user_id, ).values_list('id', flat=True)) if self.project.team: # fetch team members user_id_list |= set( u_id for u_id in self.project.team.member_set.filter( user__is_active=True, ).exclude(user__id=self.user_id, ). values_list('user', flat=True)) if not user_id_list: return disabled = set( UserOption.objects.filter( user__in=user_id_list, key='subscribe_comments', value='0', ).values_list('user', flat=True)) send_to = [u_id for u_id in user_id_list if u_id not in disabled] if not send_to: return author = self.user.first_name or self.user.username subject = '[%s] %s: %s' % (self.project.name, author, self.data['text'].splitlines()[0][:64]) context = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.pk), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', headers=headers, ) msg.add_users(send_to, project=self.project) try: msg.send() except Exception, e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization group = self.group if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' if self.group: subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = '%sRelease %s' % (subject_prefix, self.data['version']) else: raise NotImplementedError headers = {} context = { 'data': self.data, 'author': author, 'project': project, 'project_link': absolute_uri(reverse('sentry-stream', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, })), } if group: group_link = absolute_uri('/{}/{}/group/{}/'.format(org.slug, project.slug, group.id)) activity_link = '{}activity/'.format(group_link) headers.update({ 'X-Sentry-Reply-To': group_id_to_email(group.id), }) context.update({ 'group': group, 'link': group_link, 'activity_link': activity_link, }) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update({ 'release': Release.objects.get( version=self.data['version'], project=project, ), 'release_link': absolute_uri(reverse('sentry-release-details', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, 'version': self.data['version'], })), }) template_name = self.get_type_display() msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/activity/{}.txt'.format(template_name), html_template='sentry/emails/activity/{}.html'.format(template_name), headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def send_notification(self): from sentry.models import User, UserOption from sentry.utils.email import MessageBuilder, group_id_to_email if self.type != Activity.NOTE or not self.group: return # TODO(dcramer): some of this logic is duplicated in NotificationPlugin # fetch access group members user_id_list = set( User.objects.filter( accessgroup__projects=self.project, is_active=True ).exclude( id=self.user_id, ).values_list('id', flat=True) ) if self.project.team: # fetch team members user_id_list |= set( u_id for u_id in self.project.team.member_set.filter( user__is_active=True, ).exclude( user__id=self.user_id, ).values_list('user', flat=True) ) if not user_id_list: return disabled = set(UserOption.objects.filter( user__in=user_id_list, key='subscribe_comments', value='0', ).values_list('user', flat=True)) send_to = [ u_id for u_id in user_id_list if u_id not in disabled ] if not send_to: return author = self.user.first_name or self.user.username subject = '[%s] %s: %s' % ( self.project.name, author, self.data['text'].splitlines()[0][:64]) context = { 'text': self.data['text'], 'author': author, 'group': self.group, 'link': self.group.get_absolute_url(), } headers = { 'X-Sentry-Reply-To': group_id_to_email(self.group.pk), } msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/new_note.txt', html_template='sentry/emails/new_note.html', headers=headers, ) msg.add_users(send_to, project=self.project) try: msg.send() except Exception, e: logger = logging.getLogger('sentry.mail.errors') logger.exception(e)
def send_notification(self): from sentry.models import Release from sentry.utils.email import MessageBuilder, group_id_to_email if self.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return send_to = self.get_recipients() if not send_to: return project = self.project org = self.project.organization if self.user: author = self.user.first_name or self.user.username else: author = None subject_prefix = self.project.get_option( 'subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' if self.group: subject = '%s%s' % (subject_prefix, self.group.get_email_subject()) elif self.type == Activity.RELEASE: subject = '%sRelease %s' % (subject_prefix, self.data['version']) else: raise NotImplementedError headers = {} context = { 'data': self.data, 'author': author, 'project': self.project, 'project_link': absolute_uri(reverse('sentry-stream', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, })), } if self.group: headers.update({ 'X-Sentry-Reply-To': group_id_to_email(self.group.id), }) context.update({ 'group': self.group, 'link': self.group.get_absolute_url(), }) # TODO(dcramer): abstract each activity email into its own helper class if self.type == Activity.RELEASE: context.update({ 'release': Release.objects.get( version=self.data['version'], project=project, ), 'release_link': absolute_uri(reverse('sentry-release-details', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, 'version': self.data['version'], })), }) template_name = self.get_type_display() msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/activity/{}.txt'.format(template_name), html_template='sentry/emails/activity/{}.html'.format(template_name), headers=headers, reference=self, reply_reference=self.group, ) msg.add_users(send_to, project=self.project) msg.send_async()
def notify_about_activity(self, activity): if activity.type not in (Activity.NOTE, Activity.ASSIGNED, Activity.RELEASE): return candidate_ids = set(self.get_send_to(activity.project)) # Never send a notification to the user that performed the action. candidate_ids.discard(activity.user_id) if activity.type == Activity.ASSIGNED: # Only notify the assignee, and only if they are in the candidate set. recipient_ids = candidate_ids & set((activity.data['assignee'],)) elif activity.type == Activity.NOTE: recipient_ids = candidate_ids - set( UserOption.objects.filter( user__in=candidate_ids, key='subscribe_notes', value=u'0', ).values_list('user', flat=True) ) else: recipient_ids = candidate_ids if not recipient_ids: return project = activity.project org = project.organization group = activity.group headers = {} context = { 'data': activity.data, 'author': activity.user, 'project': project, 'project_link': absolute_uri(reverse('sentry-stream', kwargs={ 'organization_slug': org.slug, 'project_id': project.slug, })), } if group: group_link = absolute_uri('/{}/{}/issues/{}/'.format( org.slug, project.slug, group.id )) activity_link = '{}activity/'.format(group_link) headers.update({ 'X-Sentry-Reply-To': group_id_to_email(group.id), }) context.update({ 'group': group, 'link': group_link, 'activity_link': activity_link, }) # TODO(dcramer): abstract each activity email into its own helper class if activity.type == Activity.RELEASE: context.update({ 'release': Release.objects.get( version=activity.data['version'], project=project, ), 'release_link': absolute_uri('/{}/{}/releases/{}/'.format( org.slug, project.slug, activity.data['version'], )), }) template_name = activity.get_type_display() # TODO: Everything below should instead use `_send_mail` for consistency. subject_prefix = project.get_option('subject_prefix', settings.EMAIL_SUBJECT_PREFIX) if subject_prefix: subject_prefix = subject_prefix.rstrip() + ' ' if group: subject = '%s%s' % (subject_prefix, group.get_email_subject()) elif activity.type == Activity.RELEASE: subject = '%sRelease %s' % (subject_prefix, activity.data['version']) else: raise NotImplementedError msg = MessageBuilder( subject=subject, context=context, template='sentry/emails/activity/{}.txt'.format(template_name), html_template='sentry/emails/activity/{}.html'.format(template_name), headers=headers, reference=activity, reply_reference=group, ) msg.add_users(recipient_ids, project=project) msg.send()
def handle_user_report(self, payload, project, **kwargs): from sentry.models import Group, GroupSubscription, GroupSubscriptionReason group = Group.objects.get(id=payload['report']['issue']['id']) participants = GroupSubscription.objects.get_participants(group=group) if not participants: return context = { 'project': project, 'project_link': absolute_uri(u'/{}/{}/'.format( project.organization.slug, project.slug, )), 'issue_link': absolute_uri(u'/{}/{}/issues/{}/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), # TODO(dcramer): we dont have permalinks to feedback yet 'link': absolute_uri(u'/{}/{}/issues/{}/feedback/'.format( project.organization.slug, project.slug, payload['report']['issue']['id'], )), 'group': group, 'report': payload['report'], } subject_prefix = self.get_option('subject_prefix', project) or self._subject_prefix() subject_prefix = force_text(subject_prefix) subject = force_text(u'{}{} - New Feedback from {}'.format( subject_prefix, group.qualified_short_id, payload['report']['name'], )) headers = { 'X-Sentry-Project': project.slug, } # TODO(dcramer): this is copypasta'd from activity notifications # and while it'd be nice to re-use all of that, they are currently # coupled to <Activity> instances which makes this tough for user, reason in participants.items(): context.update({ 'reason': GroupSubscriptionReason.descriptions.get( reason, "are subscribed to this issue", ), 'unsubscribe_link': generate_signed_link( user.id, 'sentry-account-email-unsubscribe-issue', kwargs={'issue_id': group.id}, ), }) msg = MessageBuilder( subject=subject, template='sentry/emails/activity/new-user-feedback.txt', html_template='sentry/emails/activity/new-user-feedback.html', headers=headers, type='notify.user-report', context=context, reference=group, ) msg.add_users([user.id], project=project) msg.send_async()