diff --git a/matcher/error_mail.py b/matcher/error_mail.py
new file mode 100644
index 0000000..45ad845
--- /dev/null
+++ b/matcher/error_mail.py
@@ -0,0 +1,46 @@
+import logging
+from logging.handlers import SMTPHandler
+from logging import Formatter
+from flask import request
+
+PROJECT = 'osm-wikidata'
+
+class MatcherSMTPHandler(SMTPHandler):
+    def getSubject(self, record):  # noqa: N802
+        return (f'{PROJECT} error: {record.exc_info[0].__name__}'
+                if (record.exc_info and record.exc_info[0])
+                else f'{PROJECT} error: {record.pathname}:{record.lineno:d}')
+
+
+class RequestFormatter(Formatter):
+    def format(self, record):
+        record.request = request
+        return super().format(record)
+
+
+def setup_error_mail(app):
+    if not app.config.get('ERROR_MAIL'):
+        return
+    formatter = RequestFormatter('''
+    Message type:       {levelname}
+    Location:           {pathname:s}:{lineno:d}
+    Module:             {module:s}
+    Function:           {funcName:s}
+    Time:               {asctime:s}
+    GET args:           {request.args!r}
+    URL:                {request.url}
+
+    Message:
+
+    {message:s}
+    ''', style='{')
+
+    mail_handler = MatcherSMTPHandler(app.config['SMTP_HOST'],
+                                      app.config['MAIL_FROM'],
+                                      app.config['ADMINS'],
+                                      app.name + ' error')
+    mail_handler.setFormatter(formatter)
+
+    mail_handler.setLevel(logging.ERROR)
+    app.logger.propagate = True
+    app.logger.addHandler(mail_handler)
diff --git a/web_view.py b/web_view.py
index 08544b5..a47335b 100755
--- a/web_view.py
+++ b/web_view.py
@@ -5,12 +5,14 @@ from flask import (Flask, render_template, request, jsonify, redirect, url_for,
 from sqlalchemy import func
 from sqlalchemy.sql.expression import update
 from matcher import (nominatim, model, database, commons, wikidata, wikidata_api,
-                     osm_oauth, edit, mail, api)
+                     osm_oauth, edit, mail, api, error_mail)
+from werkzeug.debug.tbtools import get_current_traceback
 from matcher.data import property_map
 from time import time, sleep
 from requests_oauthlib import OAuth1Session
 from lxml import etree
-from sqlalchemy.orm.attributes import flag_modified
+import werkzeug.exceptions
+import inspect
 import flask_login
 import requests
 import json
@@ -24,6 +26,7 @@ re_point = re.compile(r'^POINT\((.+) (.+)\)$')
 app = Flask(__name__)
 app.debug = True
 app.config.from_object('config.default')
+error_mail.setup_error_mail(app)
 
 login_manager = flask_login.LoginManager(app)
 login_manager.login_view = 'login_route'
@@ -47,6 +50,32 @@ def shutdown_session(exception=None):
 def global_user():
     g.user = flask_login.current_user._get_current_object()
 
+def dict_repr_values(d):
+    return {key: repr(value) for key, value in d.items()}
+
+
+@app.errorhandler(werkzeug.exceptions.InternalServerError)
+def exception_handler(e):
+    tb = get_current_traceback()
+    last_frame = next(frame for frame in reversed(tb.frames) if not frame.is_library)
+    last_frame_args = inspect.getargs(last_frame.code)
+    if request.path.startswith("/api/"):
+        return cors_jsonify({
+            "success": False,
+            "error": tb.exception,
+            "traceback": tb.plaintext,
+            "locals": dict_repr_values(last_frame.locals),
+            "last_function": {
+                "name": tb.frames[-1].function_name,
+                "args": repr(last_frame_args),
+            },
+        }), 500
+
+    return render_template('show_error.html',
+                           tb=tb,
+                           last_frame=last_frame,
+                           last_frame_args=last_frame_args), 500
+
 def cors_jsonify(*args, **kwargs):
     response = jsonify(*args, **kwargs)
     response.headers["Access-Control-Allow-Origin"] = "*"