Exemple #1
0
def settings_plan(request):
    plan_id, format = get_many(request.params, ['plan_id'],
                               optional=['format'])
    user = api.account.get_user(request)

    if not user:
        if format == 'redirect':
            request.session['plan_id'] = plan_id
            request.session.save()

        raise LoginRequired(next=request.route_path('settings'))

    if plan_id == 'enterprise':
        api.email.notify_admin(
            request,
            'Enterprise plan inquery: [%s] %s' % (user.id, user.display_name),
            user.email_to)
        request.flash(
            'The Enterprise plan is only available in limited access at the moment. We will reach out to you when a slot becomes available. You are welcome to choose another plan for now.'
        )
        return

    old_plan_id = user.plan_id
    api.account.set_plan(user, plan_id=plan_id)
    request.flash('Plan updated.')

    api.email.notify_admin(
        request, 'Plan changed: [%s] %s' % (user.id, user.display_name),
        '%s -> %s' % (old_plan_id, user.plan_id))
Exemple #2
0
def settings_payments(request):
    user = api.account.get_user(request, required=True)
    stripe_token, plan_id, ga_cid = get_many(
        request.params, optional=['stripe_token', 'plan_id', 'ga_cid'])

    payment_type = 'stripe'
    if user.payment and user.payment.id == 'namecheap':
        payment_type = 'namecheap'
    elif not stripe_token and not request.registry.settings.get('testing'):
        raise APIControllerError('Missing Stripe card token.')

    metadata = {}
    if ga_cid:
        metadata['ga_cid'] = ga_cid

    subject = 'Payment added: [%s] %s'
    if user.payment:
        subject = 'Payment updated: [%s] %s'

    api.account.set_payments(user,
                             plan_id=plan_id,
                             card_token=stripe_token,
                             metadata=metadata,
                             payment_type=payment_type)
    api.email.notify_admin(request, subject % (user.id, user.display_name),
                           'plan_id=%s' % user.plan_id)

    request.flash('Payment information is set.')
Exemple #3
0
def report_create(request):
    remote_id, report_type, account_id, pace = get_many(request.params, optional=['remote_id', 'type', 'account_id', 'pace'])

    user = api.account.get_user(request, required=True, joinedload='accounts')
    user_id = user.id
    account = user.get_account(id=account_id)
    if not account:
        raise APIControllerError("Account [%s] does not exist for user: %s" % (account_id, user_id))

    # Check limits
    num_sites = user.get_feature('num_sites')
    if num_sites:
        remote_ids = set(r.remote_id for r in account.reports)
        remote_ids.add(remote_id)
        if len(remote_ids) > num_sites:
            raise APIControllerError("Maximum number of sites limit reached, please consider upgrading your plan.")

    if not remote_id:
        remote_id = account.remote_id

    api_query = api.account.query_service(request, account=account)
    profile = api_query.get_profile(remote_id=remote_id)
    if not profile:
        raise APIControllerError("Profile does not belong to this account: %s" % remote_id)

    if profile.get('type') == u'APP':
        # TODO: Unhardcode this
        if report_type == 'mobile-week':
            pass
        elif report_type == 'week':
            report_type = 'mobile-week'
        elif report_type == 'activity-month':
            report_type = 'mobile-month'
        else:
            request.flash("This report type is not supported for mobile apps yet. Making a weekly report instead. Please contact support if you need a new report type.")
            api.email.notify_admin(request, "Attempted mobile report: %s" % report_type, str(user))
            report_type = 'mobile-week'

    config = {}
    if pace:
        config['pace'] = pace

    report_type = report_type or api_query.oauth.default_report
    try:
        report = api.report.create(account_id=account.id, remote_data=profile, subscribe_user_id=user_id, type=report_type, remote_id=remote_id, config=config)
    except APIError as e:
        raise APIControllerError(e.message)

    preferred_hour = user.config.get('preferred_hour')
    if preferred_hour is not None:
        api.report.reschedule(report.id, hour=int(preferred_hour))

    # Queue new report
    tasks.report.send.delay(report.id)
    request.flash("First %s report for %s has been queued. Please check your Spam folder if you don't see it in your Inbox in a few minutes." % (report.type, report.display_name))
    return {'report': report}
Exemple #4
0
def settings_feedback(request):
    user = api.account.get_user(request, required=True)
    body, subject, extend_trial = get_many(
        request.params, optional=['body', 'subject', 'extend_trial'])

    text = "From user_id=%s:\n%s\n%s\n\n===\n\n%s" % (
        user.id, user.email_to, " [extend_trial=%d]" %
        user.num_remaining if extend_trial else "", body)
    api.email.notify_admin(request, subject, text=text)
    request.flash('Feedback submitted. We\'ll be in touch shortly!')
Exemple #5
0
    def extract_transaction(self, webhook_data, load_customer=True):
        if webhook_data['type'] != "invoice.payment_succeeded":
            return  # Skip

        invoice = webhook_data['data']['object']
        id, total, currency, metadata, lines, customer_id = get_many(
            invoice, ['id', 'total', 'currency', 'metadata', 'lines'],
            ['customer'])

        # TODO: Verify event

        user_id = client_id = None
        if load_customer and customer_id:
            r = self.get('https://api.stripe.com/v1/customers/%s' %
                         customer_id)
            user_id = to_ga_key(r.get('metadata'), keys=GA_UID_KEYS)
            client_id = to_ga_key(r.get('metadata'), keys=GA_CID_KEYS)

        items = []
        for line in (lines or {}).get('data', []):
            if not line:
                continue

            items.append({
                'hit_type':
                'item',
                'user_id':
                user_id,
                'client_id':
                client_id,
                'ti':
                id,
                'ip':
                line['amount'] / 100.0,
                'iq':
                line['quantity'],
                'in': (line.get('plan') or {}).get('name')
                or line.get('description') or line['id'],
                'cu':
                line['currency'].upper(),
            })

        transaction = {
            'hit_type': 'transaction',
            'user_id': user_id,
            'client_id': client_id,
            'items': items,
            'ti': id,
            'tr': total / 100.0,
            'cu': currency.upper(),
        }

        return transaction
Exemple #6
0
def funnel_clear(request):
    user_id = api.account.get_user_id(request, required=True)
    account_id, = get_many(request.params, required=['account_id'])

    account = model.Account.get_by(id=account_id, user_id=user_id)
    if not account:
        raise APIControllerError("Invalid account: %s", account_id)

    account.config.pop('ga_funnels', None)
    account.config.changed()
    model.Session.commit()

    request.flash("Cleared event connections from %s account: %s" % (account.service, account.display_name))
Exemple #7
0
def dry_run(request):
    api.account.get_admin(request)

    num_extra, filter_account, days_offset = get_many(
        request.params,
        optional=['num_extra', 'filter_account', 'days_offset'])
    num_extra = int(num_extra or 10)
    days_offset = int(days_offset or 14)

    tasks.report.dry_run.delay(num_extra=num_extra,
                               filter_account=filter_account,
                               days_offset=days_offset)
    request.flash(
        'Dry run queued: num_extra=%s filter_account=%s days_offset=%s' %
        (num_extra, filter_account, days_offset))
Exemple #8
0
    def delete(self):
        # TODO: Move to API?
        user_id = api.account.get_user_id(self.request)
        token, is_confirmed, is_feedback, why, retention = get_many(
            self.request.params,
            optional=['token', 'confirmed', 'feedback', 'why', 'retention'])

        if not user_id and not token:
            raise LoginRequired(next=self.current_path)

        user = None
        if token:
            user = api.account.get(token=token)

        if not user and user_id:
            user = model.User.get(user_id)

        self.c.user = user
        self.c.token = token

        reasons = 'Why:\r\n%s\r\n\r\nRetention:\r\n%s' % (why, retention)

        if not user:
            return self._render('delete.mako')

        if is_confirmed:
            api.email.notify_admin(self.request,
                                   'Account deleted: [%s] "%s" <%s>' %
                                   (user.id, user.display_name, user.email),
                                   text=reasons)
            api.account.delete(user_id=user.id)
            api.account.logout_user(self.request)
            self.request.session.flash('Good bye.')
        elif is_feedback:
            api.email.notify_admin(self.request,
                                   'RETENTION: [%s] "%s" <%s>' %
                                   (user.id, user.display_name, user.email),
                                   text=reasons)
            self.request.session.flash(
                'Feedback submitted. We\'ll be in touch shortly!')
        else:
            self.c.token = token
            return self._render('delete.mako')

        return self._redirect(location=self.request.route_path('index'))
Exemple #9
0
def subscription_delete(request):
    # TODO: Remove report if it's the last subscriber?
    user_id = api.account.get_user_id(request, required=True)
    subscription_id, = get_many(request.params, required=['subscription_id'])

    sub = model.Session.query(model.Subscription).options(orm.joinedload_all('report.account'), orm.joinedload('user')).get(subscription_id)
    if not sub:
        raise APIControllerError("Invalid subscription id: %s" % subscription_id)

    report = sub.report
    if sub.user_id != user_id and report.account.user_id != user_id:
        raise APIControllerError("Subscription does not belong to you: %s" % subscription_id)

    email = sub.user.email
    sub.delete()
    model.Session.commit()

    request.flash("Removed subscriber [%s] to report for [%s]" % (email, report.display_name))
Exemple #10
0
def explore_api(request):
    u = api.account.get_admin(request)
    a = u.get_account(service='google')

    report_id, dimensions, metrics, extra, date_start, date_end = get_many(
        request.params, ['report_id'],
        optional=['dimensions', 'metrics', 'extra', 'date_start', 'date_end'])
    report = model.Report.get_by(account_id=a.id, id=report_id)

    if not report:
        raise APIControllerError("Invalid report id: %s" % report_id)

    cache_keys = ('admin/explore_api', )
    google_query = api.account.query_service(request,
                                             report.account,
                                             cache_keys=cache_keys)

    date_end = date_end or date.today()
    date_start = date_start or date_end - timedelta(days=7)

    params = {
        'ids': 'ga:%s' % report.remote_id,
        'start-date': date_start,
        'end-date': date_end,
    }

    if metrics:
        metrics = [Column(m) for m in metrics.split(',')]

    if dimensions:
        dimensions = [Column(m) for m in dimensions.split(',')]

    if extra:
        params.update(part.split('=', 1) for part in extra.split('&'))

    try:
        r = google_query.get_table(params,
                                   metrics=metrics,
                                   dimensions=dimensions)
    except KeyError as e:
        raise APIControllerError("Invalid metric or dimension: %s" % e.args[0])

    return {'table': r}
Exemple #11
0
    def index(self):
        user = api.account.get_user(self.request,
                                    required=True,
                                    joinedload='accounts')

        plan_id = self.request.session.pop('plan_id', None)
        if plan_id:
            api.account.set_plan(user, plan_id=plan_id)
            self.request.flash('Plan updated.')

        self.c.selected_plan = user.plan if user.plan.id != 'trial' else PLAN_DEFAULT
        self.c.user = user

        available_services = set(['google', 'stripe'])
        for a in user.accounts:
            available_services.discard(a.service)

        # TODO: Merge with plan level/features?
        self.c.extra_services = get_many(service_registry, available_services)

        return self._render('settings.mako')
Exemple #12
0
def settings_branding(request):
    user = api.account.get_user(request, required=True)
    header_logo, header_text, from_name, reply_to = get_many(
        request.params,
        optional=['header_logo', 'header_text', 'from_name', 'reply_to'])

    if not user.get_feature('custom_branding'):
        raise APIControllerError(
            'Your plan does not include custom branding. Please upgrade your plan.'
        )

    if reply_to and '@' not in reply_to:
        raise APIControllerError(
            '"Reply To" field must be a valid email address.')

    base_dir = request.features.get('upload_logo')
    if base_dir and hasattr(header_logo, 'file'):
        prefix = '%s-' % user.id

        try:
            user.config['email_header_image'] = save_logo(
                fp=header_logo.file,
                base_dir=base_dir,
                replace_path=user.config.get('email_header_image'),
                prefix=prefix,
                pretend=request.registry.settings.get('testing'),
            )
        except (ValueError, OSError) as e:
            log.error('settings.branding: Failed to save logo: %r' % e)
            raise APIControllerError(
                "Failed to read image '%s'. Please re-save the image as a PNG and try again."
                % header_logo.filename)

    user.config['email_intro_text'] = header_text
    user.config['reply_to'] = reply_to
    user.config['from_name'] = from_name

    model.Session.commit()

    request.flash('Branding updated.')
Exemple #13
0
    def disconnect(self):
        user = api.account.get_user(self.request,
                                    required=True,
                                    joinedload='accounts')
        account_id, = get_many(self.request.params, ['account_id'])

        account = user.get_account(id=account_id)
        if not account:
            raise APIControllerError('Invalid account id: %s' % account_id)

        if len(user.accounts) == 1:
            raise APIControllerError(
                'Cannot disconnect last remaining service while retaining the account.'
            )

        # TODO: Stop doing model stuff
        display_name = account.display_name
        model.Session.delete(account)
        model.Session.commit()

        self.request.flash('Account disconnected: %s' % display_name)

        return self._redirect(location='/settings')
Exemple #14
0
def account_login(request):
    is_force, token, save_redirect, service = get_many(
        request.params, optional=['force', 'token', 'next', 'service'])
    service = service or request.matchdict.get('service', 'google')

    if service == 'stripe':
        # We don't do native auth with Stripe.
        if not api.account.get_user_id(request):
            # Not logged in? Force Google first.
            save_redirect = request.route_url('account_login',
                                              service=service,
                                              _query={'next': save_redirect})
            service = 'google'
        else:
            is_force = True

    u = api.account.login_user(request,
                               service=service,
                               save_redirect=save_redirect,
                               token=token,
                               is_force=is_force)

    return {'user': u}
Exemple #15
0
def account_connect(request):
    # TODO: Move /account/*/connect logic in here too?
    payload, service_id = get_many(request.params, ["payload", "service"])
    service = service_registry[service_id]

    if service.protocol != 'openidconnect':
        raise APIControllerError('OpenID Connect not supported for service.')

    p = parse_qs(payload)
    id_token, = p['id_token']
    decode_options = {}
    if request.registry.settings.get('testing'):
        decode_options = {
            'verify_exp': False,
        }

    try:
        decoded = jwt.decode(id_token,
                             service.config['sso_client_secret'],
                             audience=service.config['sso_client_id'],
                             options=decode_options)
    except jwt.DecodeError as e:
        raise APIControllerError('Failed to verify id token: %s' % e)
    except (jwt.InvalidTokenError, jwt.InvalidIssuedAtError) as e:
        raise APIControllerError('Invalid token: %s' % e)

    remote_id = decoded['sub']
    account = model.Session.query(model.Account).filter_by(
        remote_id=remote_id, service=service_id).first()
    if not account:
        # TODO: Create on demand?
        raise APIControllerError('Account does not exist.')

    api.account.login_user_id(request, account.user_id)

    redirect_to = '/reports'
    return {'redirect': redirect_to, 'decoded': decoded}
Exemple #16
0
def funnel_create(request):
    user_id = api.account.get_user_id(request, required=True)
    from_account_id, to_report_id, ga_tracking_id = get_many(request.params, required=['from_account_id'], optional=['to_report_id', 'ga_tracking_id'])

    from_account = model.Account.get_by(id=from_account_id, user_id=user_id)
    if not from_account:
        raise APIControllerError("Invalid account: %s" % from_account_id)

    to_display_name = ga_tracking_id
    if not ga_tracking_id:
        to_report = model.Session.query(model.Report).join(model.Account).filter(model.Report.id==to_report_id, model.Account.user_id==user_id).first()
        if to_report:
            ga_tracking_id = to_report.remote_data['webPropertyId']
            to_display_name = to_report.display_name

    if not ga_tracking_id:
        raise APIControllerError("Failed to load Google Analytics tracking id from report: %s" % to_report_id)

    ga_funnels = from_account.config.get('ga_funnels') or []
    ga_funnels.append(ga_tracking_id)
    from_account.config['ga_funnels'] = ga_funnels
    model.Session.commit()

    request.flash("Attached events from Stripe (%s) to Google Analytics property (%s) for new transactions." % (from_account.display_name, to_display_name))
Exemple #17
0
def subscription_create(request):
    report_id, email, display_name = get_many(request.params, required=['report_id', 'email'], optional=['display_name'])

    if '@' not in email:
        raise APIControllerError('Invalid email address: %s' % email)

    user = api.account.get_user(request, required=True)

    report = model.Report.get(report_id)
    if not report:
        raise APIControllerError("Invalid report id: %s" % report_id)

    if report.account.user_id != user.id:
        raise APIControllerError("Account does not belong to user: %s" % user.id)

    num_recipients = user.get_feature('num_recipients')
    if num_recipients and len(report.subscriptions) >= num_recipients:
        raise APIControllerError("Maximum number of recipients limit reached, please consider upgrading your plan.")

    new_user = api.report.add_subscriber(report_id, email, display_name, invited_by_user_id=user.id)

    request.flash("Added subscriber [%s] to report for [%s]" % (new_user.email, report.display_name))

    return {'user': new_user}
Exemple #18
0
def send(request,
         report,
         since_time=None,
         pretend=False,
         session=model.Session):
    t = time.time()

    since_time = since_time or now()

    if not pretend and report.time_next and report.time_next > since_time:
        log.warning('send too early, skipping for report: %s' % report.id)
        return

    owner = report.account.user
    if not pretend and report.time_expire and report.time_expire < since_time:
        if owner.num_remaining is None:
            # Started paying, un-expire.
            report.time_expire = None
        else:
            log.info('Deleting expired report for %s: %s' % (owner, report))
            session.delete(report)
            session.commit()
            return

    send_users = report.users
    if not send_users:
        log.warning('No recipients, skipping report: %s' % report.id)
        return

    try:
        report_context = fetch(request, report, since_time)
    except InvalidGrantError:
        subject = u"Problem with your Briefmetrics"
        html = api_email.render(request, 'email/error_auth.mako',
                                Context({
                                    'report': report,
                                }))

        message = api_email.create_message(
            request,
            to_email=owner.email,
            subject=subject,
            html=html,
        )

        if not pretend:
            api_email.send_message(request, message)
            report.delete()
            session.commit()

        log.warning('Invalid token, removed report: %s' % report)
        return
    except APIError as e:
        if not 'User does not have sufficient permissions for this profile' in e.message:
            raise

        subject = u"Problem with your Briefmetrics"
        html = api_email.render(request, 'email/error_permission.mako',
                                Context({
                                    'report': report,
                                }))

        message = api_email.create_message(
            request,
            to_email=owner.email,
            subject=subject,
            html=html,
        )

        if not pretend:
            api_email.send_message(request, message)
            report.delete()
            model.Session.commit()

        log.warning('Lost permission to profile, removed report: %s' % report)
        return

    messages, force_debug = check_trial(request,
                                        report,
                                        has_data=report_context.data,
                                        pretend=pretend)

    report_context.messages += messages
    subject = report_context.get_subject()
    template = report_context.template

    time_revive = None
    if not report_context.data:
        send_users = [report.account.user]
        template = 'email/error_empty.mako'
        time_revive = report_context.next_preferred(report_context.date_end +
                                                    datetime.timedelta(
                                                        days=30))

    log.info('Sending report to [%d] subscribers: %s' %
             (len(send_users), report))

    debug_sample = float(request.registry.settings.get('mail.debug_sample', 1))
    debug_bcc = not report.time_next or random.random() < debug_sample
    if force_debug:
        debug_bcc = True

    email_kw = {}
    from_name, from_email, reply_to, api_mandrill_key, api_mailgun_key = get_many(
        owner.config,
        optional=[
            'from_name', 'from_email', 'reply_to', 'api_mandrill_key',
            'api_mailgun_key'
        ])
    if from_name:
        email_kw['from_name'] = from_name
    if from_email:
        email_kw['from_email'] = from_email
    if reply_to and reply_to != from_email:
        email_kw['reply_to'] = reply_to

    send_kw = {}
    if api_mandrill_key:
        send_kw['settings'] = {
            'api.mandrill.key': api_mandrill_key,
        }
        email_kw['mailer'] = 'mandrill'
    if api_mailgun_key:
        send_kw['settings'] = {
            'api.mailgun.key': api_mailgun_key,
        }
        email_kw['mailer'] = 'mailgun'

    for user in report.users:
        html = api_email.render(
            request, template,
            Context({
                'user': user,
                'report': report_context,
            }))

        message = api_email.create_message(request,
                                           to_email=user.email,
                                           subject=subject,
                                           html=html,
                                           debug_bcc=debug_bcc,
                                           **email_kw)

        if pretend:
            continue

        api_email.send_message(request, message, **send_kw)

    model.ReportLog.create_from_report(
        report,
        body=html,
        subject=subject,
        seconds_elapsed=time.time() - t,
        time_sent=None if pretend else now(),
    )

    if pretend:
        session.commit()
        return

    if report_context.data:
        if owner.num_remaining == 1:
            subject = u"Your Briefmetrics trial is over"
            html = api_email.render(request, 'email/error_trial_end.mako')
            message = api_email.create_message(
                request,
                to_email=owner.email,
                subject=subject,
                html=html,
            )
            log.info('Sending trial expiration: %s' % owner)
            if not pretend:
                api_email.send_message(request, message)

        if owner.num_remaining:
            owner.num_remaining -= 1

    report.time_last = now()
    report.time_next = time_revive or report_context.next_preferred(
        report_context.date_end +
        datetime.timedelta(days=7))  # XXX: Generalize

    if owner.num_remaining == 0:
        report.time_expire = report.time_next

    session.commit()
Exemple #19
0
def _namecheap_subscription_create(request, data):
    nc_api = service_registry['namecheap'].instance

    event_id = data['event']['id']
    return_uri = data['event'].get('returnURI')
    subscription_id = data['event']['subscription_id']

    try:
        email, first_name, last_name, remote_id = get_many(data['event']['user'], ['email', 'first_name', 'last_name', 'username'])
    except KeyError as e:
        k = e.args[0]
        log.warning("namecheap webhook: Failing due to missing field '%s': %s" % (k, event_id))
        return {
            'type': 'subscription_create_resp',
            'id': event_id,
            'response': {
                'state': 'failed',
                'provider_id': '',
                'message': 'missing field: %s' % k,
            }
        }

    display_name = ' '.join([first_name, last_name])
    plan_id = data['event']['order'].get('pricing_plan_sku')

    user = api.account.get_or_create(
        email=email,
        service='namecheap',
        display_name=display_name,
        remote_id=remote_id,
        remote_data=data['event']['user'],
        plan_id=plan_id,
    )

    if user.num_remaining != 10:
        log.warning('Resetting num_remaining from %s: %s' % (user.num_remaining, user))
    user.num_remaining = 10

    ack_message = 'Briefmetrics activation instructions sent to %s' % email
    ack_state = 'Active'

    if user.payment:
        ack_message = 'Failed to provision new Briefmetrics account for {email}. Account already exists with payment information.'.format(email=email)
        ack_state = 'Failed'
        log.info('namecheap webhook: Provision skipped %s' % user)
    else:
        user.set_payment('namecheap', subscription_id)
        if user.payment.auto_charge:
            user.time_next_payment = now() + user.payment.auto_charge
        model.Session.commit()
        log.info('namecheap webhook: Provisioned %s' % user)

    ack = {
        'type': 'subscription_create_resp',
        'id': event_id,
        'response': {
            'state': ack_state,
            'provider_id': user.id,
            'message': ack_message,
        }
    }

    if return_uri:
        # Confirm event, activate subscription
        r = nc_api.session.request('PUT', return_uri, json=ack) # Bypass our wrapper
        assert_response(r)

    if ack_state != 'Active':
        return ack

    subject = u"Welcome to Briefmetrics"
    html = api.email.render(request, 'email/welcome_namecheap.mako')
    message = api.email.create_message(request,
        to_email=email,
        subject=subject,
        html=html,
    )
    api.email.send_message(request, message)
    return ack
Exemple #20
0
    def view(self):
        user = api.account.get_user(self.request, required=True, joinedload='accounts')
        report_id = self.request.matchdict['id']

        q = model.Session.query(model.Report).filter_by(id=report_id)
        if not user.is_admin:
            q = q.join(model.Report.account).filter_by(user_id=user.id)

        report = q.first()
        if not report:
            raise httpexceptions.HTTPNotFound()

        config_options = ['pace', 'intro', 'ads', 'bcc']
        config = dict((k, v) for k, v in self.request.params.items() if k in config_options and v)

        # Last Sunday
        since_time = now()
        report_context = api.report.fetch(self.request, report, since_time, config=config)

        template = report_context.template
        if not report_context.data:
            template = 'email/error_empty.mako'

        html = api.email.render(self.request, template, Context({
            'report': report_context,
            'user': user,
        }))

        is_send = self.request.params.get('send')
        if is_send:
            owner = report_context.owner
            email_kw = {}
            debug_bcc = config.get('bcc')
            from_name, from_email, reply_to, api_mandrill_key = get_many(owner.config, optional=['from_name', 'from_email', 'reply_to', 'api_mandrill_key'])
            if from_name:
                email_kw['from_name'] = from_name
            if from_email:
                email_kw['from_email'] = from_email
            if reply_to and reply_to != from_email:
                email_kw['reply_to'] = reply_to
            if debug_bcc:
                email_kw['debug_bcc'] = debug_bcc

            send_kw = {}
            if api_mandrill_key:
                send_kw['settings'] = {
                    'api.mandrill.key': api_mandrill_key,
                }

            to_email = user.email
            message = api.email.create_message(self.request,
                to_email=to_email,
                subject=report_context.get_subject(),
                html=html,
                **email_kw
            )
            api.email.send_message(self.request, message, **send_kw)
            self.request.session.flash('Sent report for [%s] to: %s' % (report.display_name, to_email))
            return self._redirect(self.request.route_path('reports'))


        return Response(html)