def send_downgrade_email(customer_email): send_email(to=customer_email, subject='Successfully downgraded from {} {}'.format( settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/downgraded.txt'), html=render_template('email/downgraded.html'), sender=settings.DEFAULT_SENDER)
def request_unconfirm_form(form_id): ''' This endpoints triggers a confirmation email that directs users to the GET version of unconfirm_form. ''' form = Form.query.get(form_id) unconfirm_url = url_for('unconfirm_form', form_id=form.id, digest=form.unconfirm_digest(), _external=True) send_email( to=form.email, subject='Unsubscribe from form at {}'.format(form.host), html=render_template_string( TEMPLATES.get('unsubscribe-confirmation.html'), url=unconfirm_url, email=form.email, host=form.host), text=render_template('email/unsubscribe-confirmation.txt', url=unconfirm_url, email=form.email, host=form.host), sender=settings.DEFAULT_SENDER, ) return render_template( 'info.html', title='Link sent to your address', text="We've sent an email to {} with a link to finish " "unsubscribing.".format(form.email)), 200
def request_unconfirm_form(form_id): ''' This endpoints triggers a confirmation email that directs users to the GET version of unconfirm_form. ''' form = Form.query.get(form_id) unconfirm_url = url_for( 'unconfirm_form', form_id=form.id, digest=form.unconfirm_digest(), _external=True ) send_email( to=form.email, subject='Unsubscribe from form at {}'.format(form.host), html=render_template_string(TEMPLATES.get('unsubscribe-confirmation.html'), url=unconfirm_url, email=form.email, host=form.host), text=render_template('email/unsubscribe-confirmation.txt', url=unconfirm_url, email=form.email, host=form.host), sender=settings.DEFAULT_SENDER, ) return render_template('info.html', title='Link sent to your address', text="We've sent an email to {} with a link to finish " "unsubscribing.".format(form.email)), 200
def stripe_webhook(): event = request.get_json() g.log.info('Webhook from Stripe', type=event['type']) try: if event['type'] == 'customer.subscription.deleted': # User subscription has expired customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) if len(customer.subscriptions.data) == 0: user = User.query.filter_by(stripe_id=customer_id).first() user.upgraded = False DB.session.add(user) DB.session.commit() g.log.info('Downgraded user from webhook.', account=user.email) send_email(to=customer.email, subject='Successfully Downgraded from {} {}'.format(settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/downgraded.txt'), html=render_template('email/downgraded.html'), sender=settings.DEFAULT_SENDER) elif event['type'] == 'invoice.payment_failed': # User payment failed customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) g.log.info('User payment failed', account=customer.email) send_email(to=customer.email, subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/payment-failed.txt'), html=render_template('email/payment-failed.html'), sender=settings.DEFAULT_SENDER) except Exception as e: g.log.error('Webhook failed for customer', json=event, error=e) return 'Failure, developer please check logs', 500 return 'ok'
def send_downgrade_reason_email(customer_email, reason): send_email( to=settings.CONTACT_EMAIL, reply_to=customer_email, subject="A customer has downgraded from {}".format(settings.UPGRADED_PLAN_NAME), text=render_template("email/downgraded_reason.txt", reason=reason), sender=settings.DEFAULT_SENDER, )
def send_downgrade_reason_email(customer_email, reason): send_email(to=settings.CONTACT_EMAIL, reply_to=customer_email, subject='A customer has downgraded from {}'.format( settings.UPGRADED_PLAN_NAME), text=render_template('email/downgraded_reason.txt', reason=reason), sender=settings.DEFAULT_SENDER)
def send_downgrade_email(customer_email): send_email( to=customer_email, subject='Successfully downgraded from {} {}'.format(settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/downgraded.txt'), html=render_template('email/downgraded.html'), sender=settings.DEFAULT_SENDER )
def stripe_webhook(): payload = request.data g.log.info('Received webhook from Stripe') sig_header = request.headers.get('STRIPE_SIGNATURE') event = None try: if settings.TESTING: event = request.get_json() else: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET) if event[ 'type'] == 'customer.subscription.deleted': # User subscription has expired customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) if len(customer.subscriptions.data) == 0: user = User.query.filter_by(stripe_id=customer_id).first() user.upgraded = False DB.session.add(user) DB.session.commit() g.log.info('Downgraded user from webhook.', account=user.email) send_email(to=customer.email, subject='Successfully Downgraded from {} {}'.format( settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/downgraded.txt'), html=render_template('email/downgraded.html'), sender=settings.DEFAULT_SENDER) elif event['type'] == 'invoice.payment_failed': # User payment failed customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) g.log.info('User payment failed', account=customer.email) send_email( to=customer.email, subject='[ACTION REQUIRED] Failed Payment for {} {}'.format( settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/payment-failed.txt'), html=render_template('email/payment-failed.html'), sender=settings.DEFAULT_SENDER) return 'ok' except ValueError as e: g.log.error('Webhook failed for customer', json=event, error=e) return 'Failure, developer please check logs', 500 except stripe.error.SignatureVerificationError as e: g.log.error('Webhook failed Stripe signature verification', json=event, error=e) return '', 400 except Exception as e: g.log.error('Webhook failed for unknown reason', json=event, error=e) return '', 500
def dispatch(self, submission, sorted_keys): key = PLUGINFAILURE_KEY(id=self.id) nfailures = redis_store.get(key) or b"0" nfailures = int(nfailures.decode("ascii")) if nfailures >= settings.PLUGINS_MAX_FAILURES: g.log.debug( "Disabling plugin due to an excess of failures.", kind=self.kind, form=self.form_id, failures=nfailures, ) redis_store.delete(key) self.enabled = False DB.session.add(self) DB.session.commit() # notify them through email render_args = dict( plugin_kind=self.kind, form_name=self.form.hashid, nfailures=nfailures, form_plugins_link=url_for( "form-page-section", hashid=self.form.hashid, s="plugins", _external=True, ), ) send_email( to=self.form.email, subject="Plugin disabled due to delivery failure", text=render_template( "email/plugin-disabled-notification.txt", **render_args ), html=render_email("plugin-disabled-notification.html", **render_args), sender=settings.DEFAULT_SENDER, ) else: g.log.debug( "Dispatching submission to plugin.", kind=self.kind, form=self.form_id, failures=nfailures, ) { PluginKind.google_sheets: self.dispatch_google_sheets, PluginKind.webhook: self.dispatch_webhook, PluginKind.trello: self.dispatch_trello, PluginKind.slack: self.dispatch_slack, PluginKind.mailchimp: self.dispatch_mailchimp, }[self.kind](submission, sorted_keys)
def stripe_webhook(): payload = request.data.decode("utf-8") g.log.info("Received webhook from Stripe") sig_header = request.headers.get("STRIPE_SIGNATURE") event = None try: if settings.TESTING: event = request.get_json() else: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET ) if ( event["type"] == "customer.subscription.deleted" ): # User subscription has expired customer_id = event["data"]["object"]["customer"] customer = stripe.Customer.retrieve(customer_id) if len(customer.subscriptions.data) == 0: user = User.query.filter_by(stripe_id=customer_id).first() user.plan = Plan.free DB.session.add(user) DB.session.commit() g.log.info("Downgraded user from webhook.", account=user.email) send_downgrade_email.delay(user.email) elif event["type"] == "invoice.payment_failed": # User payment failed customer_id = event["data"]["object"]["customer"] customer = stripe.Customer.retrieve(customer_id) g.log.info("User payment failed", account=customer.email) send_email( to=customer.email, subject="[ACTION REQUIRED] Failed Payment for {} {}".format( settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME ), text=render_template("email/payment-failed.txt"), html=render_email("payment-failed.html"), sender=settings.DEFAULT_SENDER, ) return "ok" except ValueError as e: g.log.error("Webhook failed for customer", json=event, error=e) return "Failure, developer please check logs", 500 except stripe.error.SignatureVerificationError as e: g.log.error("Webhook failed Stripe signature verification", json=event, error=e) return "", 400 except Exception as e: g.log.error("Webhook failed for unknown reason", json=event, error=e) return "", 500
def send_confirmation(addr, user_id): addr = addr.lower().strip() if not IS_VALID_EMAIL(addr): raise ValueError( 'Cannot send confirmation. %s is not a valid email.' % addr) message = 'email={email}&user_id={user_id}'.format(email=addr, user_id=user_id) digest = hmac.new(settings.NONCE_SECRET, message, hashlib.sha256).hexdigest() link = url_for('confirm-account-email', digest=digest, email=addr, _external=True) res = send_email(to=addr, subject='Confirm email for your account at %s' % settings.SERVICE_NAME, text=render_template('email/confirm-account.txt', email=addr, link=link), html=render_template('email/confirm-account.html', email=addr, link=link), sender=settings.ACCOUNT_SENDER) if not res[0]: return False else: return True
def send_confirmation(addr, user_id): g.log = g.log.new(address=addr, user_id=user_id) g.log.info('Sending email confirmation for new address on account.') addr = addr.lower().strip() if not IS_VALID_EMAIL(addr): g.log.info('Failed. Invalid address.') raise ValueError(u'Cannot send confirmation. ' '{} is not a valid email.'.format(addr)) message = u'email={email}&user_id={user_id}'.format( email=addr, user_id=user_id) digest = hmac.new( settings.NONCE_SECRET, message.encode('utf-8'), hashlib.sha256 ).hexdigest() link = url_for('confirm-account-email', digest=digest, email=addr, _external=True) res = send_email( to=addr, subject='Confirm email for your account at %s' % settings.SERVICE_NAME, text=render_template('email/confirm-account.txt', email=addr, link=link), html=render_template_string(TEMPLATES.get('confirm-account.html'), email=addr, link=link), sender=settings.ACCOUNT_SENDER ) if not res[0]: g.log.info('Failed to send email.', reason=res[1], code=res[2]) return False else: return True
def request_unconfirm_form(form_id): """ This endpoints triggers a confirmation email that directs users to the GET version of unconfirm_form. """ # repel bots if not request.user_agent.browser: return "" form = Form.query.get(form_id) unconfirm_url = url_for( "unconfirm_form", form_id=form.id, digest=form.unconfirm_digest(), _external=True, ) send_email( to=form.email, subject="Unsubscribe from form at {}".format(form.host), html=render_email( "unsubscribe-confirmation.html", url=unconfirm_url, email=form.email, host=form.host, ), text=render_template( "email/unsubscribe-confirmation.txt", url=unconfirm_url, email=form.email, host=form.host, ), sender=settings.DEFAULT_SENDER, ) return ( render_template( "info.html", title="Link sent to your address", text="We've sent an email to {} with a link to finish " "unsubscribing.".format(form.email), ), 200, )
def stripe_webhook(): payload = request.data.decode('utf-8') g.log.info('Received webhook from Stripe') sig_header = request.headers.get('STRIPE_SIGNATURE') event = None try: if settings.TESTING: event = request.get_json() else: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET) if event['type'] == 'customer.subscription.deleted': # User subscription has expired customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) if len(customer.subscriptions.data) == 0: user = User.query.filter_by(stripe_id=customer_id).first() user.upgraded = False DB.session.add(user) DB.session.commit() g.log.info('Downgraded user from webhook.', account=user.email) send_downgrade_email.delay(user.email) elif event['type'] == 'invoice.payment_failed': # User payment failed customer_id = event['data']['object']['customer'] customer = stripe.Customer.retrieve(customer_id) g.log.info('User payment failed', account=customer.email) send_email(to=customer.email, subject='[ACTION REQUIRED] Failed Payment for {} {}'.format(settings.SERVICE_NAME, settings.UPGRADED_PLAN_NAME), text=render_template('email/payment-failed.txt'), html=render_template('email/payment-failed.html'), sender=settings.DEFAULT_SENDER) return 'ok' except ValueError as e: g.log.error('Webhook failed for customer', json=event, error=e) return 'Failure, developer please check logs', 500 except stripe.error.SignatureVerificationError as e: g.log.error('Webhook failed Stripe signature verification', json=event, error=e) return '', 400 except Exception as e: g.log.error('Webhook failed for unknown reason', json=event, error=e) return '', 500
def send_confirmation(self, with_data=None): ''' Helper that actually creates confirmation nonce and sends the email to associated email. Renders different templates depending on the result ''' log.debug('Sending confirmation') if self.confirm_sent: return {'code': Form.STATUS_CONFIRMATION_DUPLICATED} # the nonce for email confirmation will be the hash when it exists # (whenever the form was created from a simple submission) or # a concatenation of HASH(email, id) + ':' + hashid # (whenever the form was created from the dashboard) id = str(self.id) nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid) link = url_for('confirm_email', nonce=nonce, _external=True) def render_content(ext): data, keys = None, None if with_data: if type(with_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(with_data) else: data, keys = with_data, with_data.keys() return render_template('email/confirm.%s' % ext, email=self.email, host=self.host, nonce_link=link, data=data, keys=keys) log.debug('Sending email') result = send_email(to=self.email, subject='Confirm email for %s' % settings.SERVICE_NAME, text=render_content('txt'), html=render_content('html'), sender=settings.DEFAULT_SENDER) log.debug('Sent') if not result[0]: return {'code': Form.STATUS_CONFIRMATION_FAILED} self.confirm_sent = True DB.session.add(self) DB.session.commit() return {'code': Form.STATUS_CONFIRMATION_SENT}
def send_confirmation(self, with_data=None): ''' Helper that actually creates confirmation nonce and sends the email to associated email. Renders different templates depending on the result ''' log.debug('Sending confirmation') if self.confirm_sent: return { 'code': Form.STATUS_CONFIRMATION_DUPLICATED } # the nonce for email confirmation will be the hash when it exists # (whenever the form was created from a simple submission) or # a concatenation of HASH(email, id) + ':' + hashid # (whenever the form was created from the dashboard) id = str(self.id) nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid) link = url_for('confirm_email', nonce=nonce, _external=True) def render_content(ext): data, keys = None, None if with_data: if type(with_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(with_data) else: data, keys = with_data, with_data.keys() return render_template('email/confirm.%s' % ext, email=self.email, host=self.host, nonce_link=link, data=data, keys=keys) log.debug('Sending email') result = send_email(to=self.email, subject='Confirm email for %s' % settings.SERVICE_NAME, text=render_content('txt'), html=render_content('html'), sender=settings.DEFAULT_SENDER) log.debug('Sent') if not result[0]: return { 'code': Form.STATUS_CONFIRMATION_FAILED } self.confirm_sent = True DB.session.add(self) DB.session.commit() return { 'code': Form.STATUS_CONFIRMATION_SENT }
def send_password_reset(self): digest = self.reset_password_digest() link = url_for('reset-password', digest=digest, email=self.email, _external=True) res = send_email( to=self.email, subject='Reset your %s password!' % settings.SERVICE_NAME, text=render_template('email/reset-password.txt', addr=self.email, link=link), html=render_template('email/reset-password.html', add=self.email, link=link), sender=settings.ACCOUNT_SENDER ) if not res[0]: return False else: return True
def send_password_reset(self): g.log.info('Sending password reset.', account=self.email) digest = self.reset_password_digest() link = url_for('reset-password', digest=digest, email=self.email, _external=True) res = send_email( to=self.email, subject='Reset your %s password!' % settings.SERVICE_NAME, text=render_template('email/reset-password.txt', addr=self.email, link=link), html=render_template_string(TEMPLATES.get('reset-password.html'), add=self.email, link=link), sender=settings.ACCOUNT_SENDER ) if not res[0]: g.log.info('Failed to send email.', reason=res[1], code=res[2]) return False else: return True
def send_confirmation(addr, user_id): addr = addr.lower().strip() if not IS_VALID_EMAIL(addr): raise ValueError('Cannot send confirmation. %s is not a valid email.' % addr) message = 'email={email}&user_id={user_id}'.format(email=addr, user_id=user_id) digest = hmac.new(settings.NONCE_SECRET, message, hashlib.sha256).hexdigest() link = url_for('confirm-account-email', digest=digest, email=addr, _external=True) res = send_email( to=addr, subject='Confirm email for your account at %s' % settings.SERVICE_NAME, text=render_template('email/confirm-account.txt', email=addr, link=link), html=render_template('email/confirm-account.html', email=addr, link=link), sender=settings.ACCOUNT_SENDER ) if not res[0]: return False else: return True
def send_password_reset(self): digest = self.reset_password_digest() link = url_for('reset-password', digest=digest, email=self.email, _external=True) res = send_email(to=self.email, subject='Reset your %s password!' % settings.SERVICE_NAME, text=render_template('email/reset-password.txt', addr=self.email, link=link), html=render_template('email/reset-password.html', add=self.email, link=link), sender=settings.ACCOUNT_SENDER) if not res[0]: return False else: return True
def send_password_reset(self): g.log.info("Sending password reset.", account=self.email) digest = self.reset_password_digest() link = url_for( "reset-password", digest=digest, email=self.email, _external=True ) res = send_email( to=self.email, subject="Reset your %s password!" % settings.SERVICE_NAME, text=render_template( "email/reset-password.txt", addr=self.email, link=link ), html=render_email("reset-password.html", add=self.email, link=link), sender=settings.ACCOUNT_SENDER, ) if not res[0]: g.log.info("Failed to send email.", reason=res[1], code=res[2]) return False else: return True
def send_confirmation(addr, user_id): g.log = g.log.new(address=addr, user_id=user_id) g.log.info("Sending email confirmation for new address on account.") addr = addr.lower().strip() if not is_valid_email(addr): g.log.info("Failed. Invalid address.") raise ValueError( "Cannot send confirmation. " "{} is not a valid email.".format(addr) ) link = url_for( "verify-account-email", nonce=store_pending_email(addr, user_id), _external=True, ) res = send_email( to=addr, subject="Action Required: Verify email linked to %s" % settings.SERVICE_NAME, text=render_template( "email/verify-email-in-account.txt", owner_email=current_user.email, email=addr, link=link, ), html=render_email( "verify-email-in-account.html", owner_email=current_user.email, email=addr, link=link, ), sender=settings.ACCOUNT_SENDER, ) if not res[0]: g.log.info("Failed to send email.", reason=res[1], code=res[2]) return False else: return True
def send_confirmation(addr, user_id): g.log = g.log.new(address=addr, user_id=user_id) g.log.info("Sending email confirmation for new address on account.") addr = addr.lower().strip() if not is_valid_email(addr): g.log.info("Failed. Invalid address.") raise ValueError("Cannot send confirmation. " "{} is not a valid email.".format(addr)) link = url_for( "verify-account-email", nonce=store_pending_email(addr, user_id), _external=True, ) res = send_email( to=addr, subject="Action Required: Verify email linked to %s" % settings.SERVICE_NAME, text=render_template( "email/verify-email-in-account.txt", owner_email=current_user.email, email=addr, link=link, ), html=render_email( "verify-email-in-account.html", owner_email=current_user.email, email=addr, link=link, ), sender=settings.ACCOUNT_SENDER, ) if not res[0]: g.log.info("Failed to send email.", reason=res[1], code=res[2]) return False else: return True
def send_password_reset(self): g.log.info("Sending password reset.", account=self.email) digest = self.reset_password_digest() link = url_for("reset-password", digest=digest, email=self.email, _external=True) res = send_email( to=self.email, subject="Reset your %s password!" % settings.SERVICE_NAME, text=render_template("email/reset-password.txt", addr=self.email, link=link), html=render_email("reset-password.html", add=self.email, link=link), sender=settings.ACCOUNT_SENDER, ) if not res[0]: g.log.info("Failed to send email.", reason=res[1], code=res[2]) return False else: return True
def process(self, keys): """ Processes a submission, including sending submission to target email. Assumes sender's email has been verified. NOTE: shouldn't call DB.commit() when processing a submission. All actions are committed after processing by process_and_commit_submission() """ # url to request_unconfirm_form page unconfirm_url = url_for( "request_unconfirm_form", form_id=self.form.id, _external=True ) spam_url = url_for("mark-spam", id=self.spam_hash, _external=True) if ( not self.form.has_feature("archive") and random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY ): self.form.delete_over_archive_limit() if self.check_over_submission_limit(unconfirm_url) is True: self.append_error("Over submission limit") return self.dispatch_plugins(keys) # build the list of recipients: for normal forms it will be just # form.email; for forms with routing rules we ignore that and use # the results from the routing rules instead. if ( self.form.has_feature("submission_routing") and self.form.routing_rules.count() ): recipients = { (rule.email, rule.id) for rule in self.form.routing_rules if rule.matches(self) } g.log.info("Got recipients from route matching", n=len(recipients)) else: # if emails are disabled, don't send email notification # (but routing rules are applied anyway) if self.form.has_feature("dashboard") and self.form.disable_email: g.log.info("Form has email disabled, will not send.") return else: recipients = {(self.form.email, None)} g.log.info("Will send to simple recipient", recipient=self.form.email) # prepare email properties ----------------------------------------------- # set variables used for sending a submission reply_to = get_replyto(self.data) subject = self.data.get("_subject") or self.get_host_path() cc = self.data.get("_cc", None) format = self.data.get("_format", None) from_name = None # turn cc emails into array if cc: cc = [e.strip() for e in cc.split(",")] now = datetime.datetime.utcnow().strftime("%I:%M %p UTC - %d %B %Y") text = render_template( "email/form.txt", data=self.data, host=self.get_host_path(), keys=keys, now=now, unconfirm_url=unconfirm_url, ) template_data = EmailTemplate.make_mustache_context( data=self.data, host=self.get_host_path(), keys=keys, now=now, unconfirm_url=unconfirm_url, ) # if there's a custom email template we should use it # otherwise check if the user wants a new or old version of the email if ( self.form.has_feature("whitelabel") and self.form.template and self.form.template.body ): html = self.form.template.render_body(template_data, unconfirm_url) elif format == "plain": html = render_template( "email/plain_form.html", data=self.data, host=self.get_host_path(), keys=keys, now=now, unconfirm_url=unconfirm_url, ) else: html = render_email( "form.html", data=self.data, host=self.get_host_path(), keys=keys, now=now, unconfirm_url=unconfirm_url, submission_count=self.form.counter, upgraded=self.form.has_feature("dashboard"), spam_url=spam_url, ) # override other email properties if there's a template if self.form.has_feature("whitelabel") and self.form.template: subject = ( self.form.template.render_subject(template_data) if self.form.template.subject else subject ) from_name = self.form.template.from_name or from_name # send the emails ---------------------------------------------------------- for to, rule_id in recipients: g.log.info("Submitting.", target=to) result = send_email( to=to, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, from_name=from_name, reply_to=reply_to, cc=cc, headers={ "List-Unsubscribe-Post": "List-Unsubscribe=One-Click", "List-Unsubscribe": "<" + url_for( "unconfirm_form", form_id=self.form.id, digest=self.form.unconfirm_digest(), _external=True, ) + ">", }, ) if not result[0]: g.log.warning("Failed to send email.", reason=result[1], code=result[2]) # self.errors.append("Couldn't send email. " + result[1]) self.append_error( "Could not send email", debug_msg=f"code {result[2]}: {result[1]}", rule=rule_id, )
def send_confirmation(self, store_data=None): ''' Helper that actually creates confirmation nonce and sends the email to associated email. Renders different templates depending on the result ''' g.log = g.log.new(form=self.id, to=self.email, host=self.host) g.log.debug('Confirmation.') if self.confirm_sent: g.log.debug('Previously sent.') return {'code': Form.STATUS_CONFIRMATION_DUPLICATED} # the nonce for email confirmation will be the hash when it exists # (whenever the form was created from a simple submission) or # a concatenation of HASH(email, id) + ':' + hashid # (whenever the form was created from the dashboard) id = str(self.id) nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid) link = url_for('confirm_email', nonce=nonce, _external=True) def render_content(ext): data, keys = None, None if store_data: if type(store_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, _ = http_form_to_dict(store_data) store_first_submission(nonce, data) else: store_first_submission(nonce, store_data) return render_template('email/confirm.%s' % ext, email=self.email, host=self.host, nonce_link=link, keys=keys) DB.session.add(self) DB.session.flush() result = send_email( to=self.email, subject='Confirm email for {} on {}' \ .format(settings.SERVICE_NAME, self.host), text=render_content('txt'), html=render_content('html'), sender=settings.DEFAULT_SENDER, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for( 'unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True ) + '>' } ) g.log.debug('Confirmation email queued.') if not result[0]: return {'code': Form.STATUS_CONFIRMATION_FAILED} self.confirm_sent = True DB.session.add(self) DB.session.commit() return {'code': Form.STATUS_CONFIRMATION_SENT}
def send(self, data, keys, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' subject = data.get('_subject') or \ 'New submission from %s' % referrer_to_path(referrer) reply_to = (data.get('_replyto', data.get('email', data.get('Email'))) or '').strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return {'code': Form.STATUS_EMAIL_EMPTY} # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return {'code': Form.STATUS_EMAIL_SENT, 'next': next} # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 # if submission storage is disabled and form is upgraded, don't store submission if self.disable_storage and self.upgraded: pass else: DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = { key: data[key] for key in data if key not in KEYS_NOT_STORED } DB.session.add(sub) # commit changes DB.session.commit() # sometimes we'll delete all archived submissions over the limit if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY: records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT total_records = DB.session.query(func.count(Submission.id)) \ .filter_by(form_id=self.id) \ .scalar() if total_records > records_to_keep: newest = self.submissions.with_entities( Submission.id).limit(records_to_keep) DB.engine.execute( delete(table('submissions')). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT and not self.upgraded: overlimit = True if monthly_counter == int(settings.MONTHLY_SUBMISSIONS_LIMIT * 0.9) and not self.upgraded: # send email notification send_email(to=self.email, subject="[WARNING] Approaching submission limit", text=render_template('email/90-percent-warning.txt'), html=render_template('email/90-percent-warning.html'), sender=settings.DEFAULT_SENDER) now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return {'code': Form.STATUS_OVERLIMIT} text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) # if emails are disabled and form is upgraded, don't send email notification if self.disable_email and self.upgraded: return {'code': Form.STATUS_NO_EMAIL, 'next': next} else: result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for('unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True) + '>' }) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } return { 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
def send(self, http_form, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' data, keys = http_form_to_dict(http_form) subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', None))) cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_EMAIL_FAILED } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: return{ 'code': Form.STATUS_EMAIL_FAILED } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send_confirmation(self, store_data=None, sorted_keys=[]): """ Helper that actually creates confirmation nonce and sends the email to associated email. Renders different templates depending on the result """ g.log = g.log.new(form=self.id, to=self.email, host=self.host) g.log.debug("Confirmation.") if not self.host: g.log.error("Trying to send confirmation without host.") if self.confirm_sent: g.log.debug("Previously sent.") return {"code": Form.STATUS_CONFIRMATION_DUPLICATED} # the nonce for email confirmation will be the hash # (we only send confirmation emails for legacy forms created # automatically now) nonce = self.hash link = url_for("confirm_email", nonce=nonce, _external=True) def render_content(ext): if store_data: store_first_submission(nonce, store_data, sorted_keys) params = dict( email=self.email, host=referrer_to_path(self.host), nonce_link=link ) if ext == "html": return render_email("confirm.html", **params) elif ext == "txt": return render_template("email/confirm.txt", **params) DB.session.add(self) try: DB.session.flush() except IntegrityError: return {"code": Form.STATUS_CONFIRMATION_DUPLICATED} result = send_email( to=self.email, subject=self.SUBJECT_ACTIVATION % (settings.SERVICE_NAME, self.host), text=render_content("txt"), html=render_content("html"), sender=settings.DEFAULT_SENDER, headers={ "List-Unsubscribe-Post": "List-Unsubscribe=One-Click", "List-Unsubscribe": "<" + url_for( "unconfirm_form", form_id=self.id, digest=self.unconfirm_digest(), _external=True, ) + ">", }, ) g.log.debug("Confirmation email queued.") if not result[0]: return {"code": Form.STATUS_CONFIRMATION_FAILED} self.confirm_sent = True DB.session.add(self) DB.session.commit() return {"code": Form.STATUS_CONFIRMATION_SENT}
def send(self, submitted_data, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(submitted_data) else: data, keys = submitted_data, submitted_data.keys() subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', ''))).strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'error-message': '"%s" is not a valid email address.' % reply_to } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_OVERLIMIT } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email( to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc ) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR} return{ 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(self, submitted_data, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict): data, keys = http_form_to_dict(submitted_data) else: data, keys = submitted_data, submitted_data.keys() subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', ''))).strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): return { 'code': Form.STATUS_REPLYTO_ERROR, 'error-message': '"%s" is not a valid email address.' % reply_to } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT: overlimit = True if self.controllers: for c in self.controllers: if c.upgraded: overlimit = False break now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now) else: html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25: # only send this overlimit notification for the first 25 overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. return { 'code': Form.STATUS_OVERLIMIT } text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR} return{ 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def send(self, http_form, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' data, keys = http_form_to_dict(http_form) subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer)) reply_to = data.get('_replyto', data.get('email', data.get('Email', None))) cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) # prevent submitting empty form if not any(data.values()): return { 'code': Form.STATUS_EMAIL_EMPTY } # return a fake success for spam if spam: return { 'code': Form.STATUS_EMAIL_SENT, 'next': next } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = data DB.session.add(sub) # commit changes DB.session.commit() # delete all archived submissions over the limit records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete('submissions'). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # check if the forms are over the counter and the user is not upgraded overlimit = False if self.get_monthly_counter(basedate=request_date) > settings.MONTHLY_SUBMISSIONS_LIMIT: if not self.owner or not self.owner.upgraded: overlimit = True now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now) html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now) else: text = render_template('email/overlimit-notification.txt', host=self.host) html = render_template('email/overlimit-notification.html', host=self.host) result = send_email(to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc) if not result[0]: return{ 'code': Form.STATUS_EMAIL_FAILED } return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }
def check_over_submission_limit(self, unconfirm_url): # check if the forms are over the counter and the user has unlimited submissions monthly_counter = self.form.update_counters() monthly_limit = ( settings.MONTHLY_SUBMISSIONS_LIMIT if self.form.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE else settings.GRANDFATHER_MONTHLY_LIMIT ) overlimit = monthly_counter > monthly_limit and not self.form.has_feature( "unlimited" ) if overlimit: g.log.info("Form over limit.", monthly_counter=monthly_counter) # send overlimit or approaching limit emails if monthly_counter == int(monthly_limit * 0.9) and not self.form.has_feature( "unlimited" ): # send email notification send_email( to=self.form.email, subject=self.SUBJECT_APPROACHING_LIMIT, text=render_template( "email/90-percent-warning.txt", unconfirm_url=unconfirm_url, limit=monthly_limit, ), html=render_email( "90-percent-warning.html", unconfirm_url=unconfirm_url, limit=monthly_limit, ), sender=settings.DEFAULT_SENDER, ) # send an overlimit notification for the first x overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. if ( overlimit and monthly_counter <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY ): send_email( to=self.form.email, subject=self.SUBJECT_OVER_LIMIT, text=render_template( "email/overlimit-notification.txt", host=self.get_host_path(), unconfirm_url=unconfirm_url, limit=monthly_limit, ), html=render_email( "overlimit-notification.html", host=self.get_host_path(), unconfirm_url=unconfirm_url, limit=monthly_limit, ), sender=settings.DEFAULT_SENDER, ) return overlimit
def send(self, data, keys, referrer): ''' Sends form to user's email. Assumes sender's email has been verified. ''' subject = data.get('_subject') or \ 'New submission from %s' % referrer_to_path(referrer) reply_to = (data.get( '_replyto', data.get('email', data.get('Email')) ) or '').strip() cc = data.get('_cc', None) next = next_url(referrer, data.get('_next')) spam = data.get('_gotcha', None) format = data.get('_format', None) # turn cc emails into array if cc: cc = [email.strip() for email in cc.split(',')] # prevent submitting empty form if not any(data.values()): return {'code': Form.STATUS_EMAIL_EMPTY} # return a fake success for spam if spam: g.log.info('Submission rejected.', gotcha=spam) return {'code': Form.STATUS_EMAIL_SENT, 'next': next} # validate reply_to, if it is not a valid email address, reject if reply_to and not IS_VALID_EMAIL(reply_to): g.log.info('Submission rejected. Reply-To is invalid.', reply_to=reply_to) return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } # increase the monthly counter request_date = datetime.datetime.now() self.increase_monthly_counter(basedate=request_date) # increment the forms counter self.counter = Form.counter + 1 # if submission storage is disabled and form is upgraded, don't store submission if self.disable_storage and self.upgraded: pass else: DB.session.add(self) # archive the form contents sub = Submission(self.id) sub.data = {key: data[key] for key in data if key not in KEYS_NOT_STORED} DB.session.add(sub) # commit changes DB.session.commit() # sometimes we'll delete all archived submissions over the limit if random.random() < settings.EXPENSIVELY_WIPE_SUBMISSIONS_FREQUENCY: records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT total_records = DB.session.query(func.count(Submission.id)) \ .filter_by(form_id=self.id) \ .scalar() if total_records > records_to_keep: newest = self.submissions.with_entities(Submission.id).limit(records_to_keep) DB.engine.execute( delete(table('submissions')). \ where(Submission.form_id == self.id). \ where(~Submission.id.in_(newest)) ) # url to request_unconfirm_form page unconfirm = url_for('request_unconfirm_form', form_id=self.id, _external=True) # check if the forms are over the counter and the user is not upgraded overlimit = False monthly_counter = self.get_monthly_counter() monthly_limit = settings.MONTHLY_SUBMISSIONS_LIMIT \ if self.id > settings.FORM_LIMIT_DECREASE_ACTIVATION_SEQUENCE \ else settings.GRANDFATHER_MONTHLY_LIMIT if monthly_counter > monthly_limit and not self.upgraded: overlimit = True if monthly_counter == int(monthly_limit * 0.9) and not self.upgraded: # send email notification send_email( to=self.email, subject="Formspree Notice: Approaching submission limit.", text=render_template('email/90-percent-warning.txt', unconfirm_url=unconfirm, limit=monthly_limit ), html=render_template_string( TEMPLATES.get('90-percent-warning.html'), unconfirm_url=unconfirm, limit=monthly_limit ), sender=settings.DEFAULT_SENDER ) now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y') if not overlimit: g.log.info('Submitted.') text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) # check if the user wants a new or old version of the email if format == 'plain': html = render_template('email/plain_form.html', data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) else: html = render_template_string(TEMPLATES.get('form.html'), data=data, host=self.host, keys=keys, now=now, unconfirm_url=unconfirm) else: g.log.info('Submission rejected. Form over quota.', monthly_counter=monthly_counter) # send an overlimit notification for the first x overlimit emails # after that, return an error so the user can know the website owner is not # going to read his message. if monthly_counter <= monthly_limit + settings.OVERLIMIT_NOTIFICATION_QUANTITY: subject = 'Formspree Notice: Your submission limit has been reached.' text = render_template('email/overlimit-notification.txt', host=self.host, unconfirm_url=unconfirm, limit=monthly_limit) html = render_template_string(TEMPLATES.get('overlimit-notification.html'), host=self.host, unconfirm_url=unconfirm, limit=monthly_limit) else: return {'code': Form.STATUS_OVERLIMIT} # if emails are disabled and form is upgraded, don't send email notification if self.disable_email and self.upgraded: return {'code': Form.STATUS_NO_EMAIL, 'next': next} else: result = send_email( to=self.email, subject=subject, text=text, html=html, sender=settings.DEFAULT_SENDER, reply_to=reply_to, cc=cc, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for( 'unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True ) + '>' } ) if not result[0]: g.log.warning('Failed to send email.', reason=result[1], code=result[2]) if result[1].startswith('Invalid replyto email address'): return { 'code': Form.STATUS_REPLYTO_ERROR, 'address': reply_to, 'referrer': referrer } return { 'code': Form.STATUS_EMAIL_FAILED, 'mailer-code': result[2], 'error-message': result[1] } return {'code': Form.STATUS_EMAIL_SENT, 'next': next}
def send_confirmation(self, store_data=None): ''' Helper that actually creates confirmation nonce and sends the email to associated email. Renders different templates depending on the result ''' g.log = g.log.new(form=self.id, to=self.email, host=self.host) g.log.debug('Confirmation.') if self.confirm_sent: g.log.debug('Previously sent.') return {'code': Form.STATUS_CONFIRMATION_DUPLICATED} # the nonce for email confirmation will be the hash when it exists # (whenever the form was created from a simple submission) or # a concatenation of HASH(email, id) + ':' + hashid # (whenever the form was created from the dashboard) id = str(self.id) nonce = self.hash or '%s:%s' % (HASH(self.email, id), self.hashid) link = url_for('confirm_email', nonce=nonce, _external=True) def render_content(ext): data, keys = None, None if store_data: if type(store_data) in ( ImmutableMultiDict, ImmutableOrderedMultiDict): data, _ = http_form_to_dict(store_data) store_first_submission(nonce, data) else: store_first_submission(nonce, store_data) params = dict( email=self.email, host=self.host, nonce_link=link, keys=keys ) if ext == 'html': return render_template_string(TEMPLATES.get('confirm.html'), **params) elif ext == 'txt': return render_template('email/confirm.txt', **params) DB.session.add(self) DB.session.flush() result = send_email( to=self.email, subject='Confirm email for {} on {}' \ .format(settings.SERVICE_NAME, self.host), text=render_content('txt'), html=render_content('html'), sender=settings.DEFAULT_SENDER, headers={ 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 'List-Unsubscribe': '<' + url_for( 'unconfirm_form', form_id=self.id, digest=self.unconfirm_digest(), _external=True ) + '>' } ) g.log.debug('Confirmation email queued.') if not result[0]: return {'code': Form.STATUS_CONFIRMATION_FAILED} self.confirm_sent = True DB.session.add(self) DB.session.commit() return {'code': Form.STATUS_CONFIRMATION_SENT}