Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from django.conf import settings
import base64
import os
import os.path

Expand Down Expand Up @@ -57,7 +56,8 @@ def __contains__(self, search):
settings.INSTALLED_APPS = tuple(settings.INSTALLED_APPS) + (
'tests',
)
settings.SENTRY_KEY = base64.b64encode(os.urandom(40))
# Need a predictable key for tests that involve checking signatures
settings.SENTRY_KEY = 'abc123'
settings.SENTRY_PUBLIC = False
# This speeds up the tests considerably, pbkdf2 is by design, slow.
settings.PASSWORD_HASHERS = [
Expand All @@ -67,3 +67,4 @@ def __contains__(self, search):
# enable draft features
settings.SENTRY_ENABLE_EXPLORE_CODE = True
settings.SENTRY_ENABLE_EXPLORE_USERS = True
settings.SENTRY_ENABLE_EMAIL_REPLIES = True
41 changes: 41 additions & 0 deletions docs/config/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,47 @@ The following settings are available for the built-in UDP API server:
SENTRY_UDP_PORT = 9001


.. _config-smtp-server:

SMTP Server
~~~~~~~~~~~

The following settings are available for the built-in SMTP mail server:

.. data:: SENTRY_SMTP_HOST
:noindex:

The hostname which the smtp server should bind to.

Defaults to ``localhost``.

::

SENTRY_SMTP_HOST = '0.0.0.0' # bind to all addresses

.. data:: SENTRY_SMTP_PORT
:noindex:

The port which the smtp server should listen on.

Defaults to ``1025``.

::

SENTRY_SMTP_PORT = 1025

.. data:: SENTRY_SMTP_HOSTNAME
:noindex:

The hostname which matches the server's MX record.

Defaults to ``localhost``.

::

SENTRY_SMTP_HOSTNAME = 'reply.getsentry.com'


Data Sampling
-------------

Expand Down
37 changes: 37 additions & 0 deletions docs/quickstart/nginx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,40 @@ as well the ``sentry.wsgi`` module:
; allow longer headers for raven.js if applicable
; default: 4096
buffer-size = 32768


Proxying Incoming Email
~~~~~~~~~~~~~~~~~~~~~~~

Nginx is recommended for handling incoming emails in front of the Sentry smtp server.

Below is a sample configuration for Nginx:

::
http {
# Bind an http server to localhost only just for the smtp auth
server {
listen 127.0.0.1:80;

# Return back the address and port for the listening
# Sentry smtp server. Default is 127.0.0.1:1025.
location = /smtp {
add_header Auth-Server 127.0.0.1;
add_header Auth-Port 1025;
return 200;
}
}
}

mail {
auth_http localhost/smtp;

server {
listen 25;

protocol smtp;
proxy on;
smtp_auth none;
xclient off;
}
}
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
'django-social-auth>=0.7.28,<0.8.0',
'django-static-compiler>=0.3.0,<0.4.0',
'django-templatetag-sugar>=0.1.0,<0.2.0',
'email-reply-parser>=0.2.0,<0.3.0',
'gunicorn>=0.17.2,<0.18.0',
'httpagentparser>=1.2.1,<1.3.0',
'logan>=0.5.8.2,<0.6.0',
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,12 @@
SENTRY_UDP_HOST = 'localhost'
SENTRY_UDP_PORT = 9001

# SMTP Service
SENTRY_ENABLE_EMAIL_REPLIES = False
SENTRY_SMTP_HOSTNAME = 'localhost'
SENTRY_SMTP_HOST = 'localhost'
SENTRT_SMTP_PORT = 25

SENTRY_ALLOWED_INTERFACES = set([
'sentry.interfaces.Exception',
'sentry.interfaces.Message',
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/management/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Command(BaseCommand):
)

def handle(self, service_name='http', address=None, upgrade=True, **options):
from sentry.services import http, udp
from sentry.services import http, udp, smtp

if address:
if ':' in address:
Expand All @@ -47,6 +47,7 @@ def handle(self, service_name='http', address=None, upgrade=True, **options):
services = {
'http': http.SentryHTTPServer,
'udp': udp.SentryUDPServer,
'smtp': smtp.SentrySMTPServer,
}

if service_name == 'worker':
Expand Down
7 changes: 6 additions & 1 deletion src/sentry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ def save(self, *args, **kwargs):
self.event.update(num_comments=F('num_comments') + 1)

def send_notification(self):
from sentry.utils.email import MessageBuilder
from sentry.utils.email import MessageBuilder, group_id_to_email

if self.type != Activity.NOTE or not self.group:
return
Expand Down Expand Up @@ -1156,11 +1156,16 @@ def send_notification(self):
'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,
)

try:
Expand Down
73 changes: 30 additions & 43 deletions src/sentry/plugins/sentry_mail/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@

from django.conf import settings
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from sentry.models import User, UserOption
from sentry.plugins import register
from sentry.plugins.bases.notify import NotificationPlugin
from sentry.utils.cache import cache
from sentry.utils.email import MessageBuilder
from sentry.utils.email import MessageBuilder, group_id_to_email
from sentry.utils.http import absolute_uri

NOTSET = object()
Expand All @@ -33,8 +32,8 @@ class MailPlugin(NotificationPlugin):
project_conf_form = None
subject_prefix = settings.EMAIL_SUBJECT_PREFIX

def _send_mail(self, subject, body, html_body=None, project=None,
headers=None, fail_silently=False):
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
Expand All @@ -43,9 +42,11 @@ def _send_mail(self, subject, body, html_body=None, project=None,

msg = MessageBuilder(
subject='%s%s' % (subject_prefix, subject),
template=template,
html_template=html_template,
body=body,
html_body=html_body,
headers=headers,
context=context,
)
msg.send(send_to, fail_silently=fail_silently)

Expand All @@ -66,35 +67,29 @@ def on_alert(self, alert):
project.name.encode('utf-8'),
alert.message.encode('utf-8'),
)
body = self.get_alert_plaintext_body(alert)
html_body = self.get_alert_html_body(alert)
template = 'sentry/emails/alert.txt'
html_template = 'sentry/emails/alert.html'

context = {
'alert': alert,
'link': alert.get_absolute_url(),
'settings_link': self.get_notification_settings_url(),
}

headers = {
'X-Sentry-Project': project.name,
}

self._send_mail(
subject=subject,
body=body,
html_body=html_body,
template=template,
html_template=html_template,
project=project,
fail_silently=False,
headers=headers,
context=context,
)

def get_alert_plaintext_body(self, alert):
return render_to_string('sentry/emails/alert.txt', {
'alert': alert,
'link': alert.get_absolute_url(),
})

def get_alert_html_body(self, alert):
return render_to_string('sentry/emails/alert.html', {
'alert': alert,
'link': alert.get_absolute_url(),
'settings_link': self.get_notification_settings_url(),
})

def get_emails_for_users(self, user_ids, project=None):
email_list = set()
user_ids = set(user_ids)
Expand Down Expand Up @@ -172,43 +167,35 @@ def notify_users(self, group, event, fail_silently=False):

link = group.get_absolute_url()

body = self.get_plaintext_body(group, event, link, interface_list)
template = 'sentry/emails/error.txt'
html_template = 'sentry/emails/error.html'

html_body = self.get_html_body(group, event, link, interface_list)
context = {
'group': group,
'event': event,
'link': link,
'interfaces': interface_list,
'settings_link': self.get_notification_settings_url(),
}

headers = {
'X-Sentry-Logger': event.logger,
'X-Sentry-Logger-Level': event.get_level_display(),
'X-Sentry-Project': project.name,
'X-Sentry-Server': event.server_name,
'X-Sentry-Reply-To': group_id_to_email(group.pk),
}

self._send_mail(
subject=subject,
body=body,
html_body=html_body,
template=template,
html_template=html_template,
project=project,
fail_silently=fail_silently,
headers=headers,
context=context,
)

def get_plaintext_body(self, group, event, link, interface_list):
return render_to_string('sentry/emails/error.txt', {
'group': group,
'event': event,
'link': link,
'interfaces': interface_list,
})

def get_html_body(self, group, event, link, interface_list):
return render_to_string('sentry/emails/error.html', {
'group': group,
'event': event,
'link': link,
'interfaces': interface_list,
'settings_link': self.get_notification_settings_url(),
})


# Legacy compatibility
MailProcessor = MailPlugin
Expand Down
Loading