mirror of
https://github.com/simple-login/app.git
synced 2024-09-30 05:31:30 +02:00
commit
e8e0923de7
@ -5,7 +5,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block single_content %}
|
{% block single_content %}
|
||||||
<div class="text-center">
|
<div class="text-center bg-white p-5" style="max-width: 50rem">
|
||||||
<h1>
|
<h1>
|
||||||
An email to validate your email is on its way.
|
An email to validate your email is on its way.
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -16,4 +16,5 @@ from .views import (
|
|||||||
directory,
|
directory,
|
||||||
mailbox,
|
mailbox,
|
||||||
deleted_alias,
|
deleted_alias,
|
||||||
|
mailbox_detail,
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="col-sm-6 p-1">
|
<div class="col-sm-6 p-1">
|
||||||
<select class="form-control" name="suffix">
|
<select class="form-control custom-select" name="suffix">
|
||||||
{% for suffix in suffixes %}
|
{% for suffix in suffixes %}
|
||||||
<option value="{{ suffix[1] }}">
|
<option value="{{ suffix[1] }}">
|
||||||
{% if suffix[0] %}
|
{% if suffix[0] %}
|
||||||
@ -54,10 +54,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if mailboxes|length > 1 %}
|
{% if mailboxes|length > 1 or current_user.full_mailbox %}
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col p-1">
|
<div class="col p-1">
|
||||||
<select class="form-control" name="mailbox">
|
<select class="form-control custom-select" name="mailbox">
|
||||||
{% for mailbox in mailboxes %}
|
{% for mailbox in mailboxes %}
|
||||||
<option value="{{ mailbox }}">
|
<option value="{{ mailbox }}">
|
||||||
{{ mailbox }}
|
{{ mailbox }}
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
<input type="hidden" name="form-name" value="delete">
|
<input type="hidden" name="form-name" value="delete">
|
||||||
<input type="hidden" class="dir-name" value="{{ dir.name }}">
|
<input type="hidden" class="dir-name" value="{{ dir.name }}">
|
||||||
<input type="hidden" name="dir-id" value="{{ dir.id }}">
|
<input type="hidden" name="dir-id" value="{{ dir.id }}">
|
||||||
<span class="card-link btn btn-link float-right delete-dir">
|
<span class="card-link btn btn-link float-right text-danger delete-dir">
|
||||||
Delete
|
Delete
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
@ -85,8 +85,7 @@
|
|||||||
|
|
||||||
{{ new_dir_form.name(class="form-control", placeholder="my-directory",
|
{{ new_dir_form.name(class="form-control", placeholder="my-directory",
|
||||||
pattern="[0-9a-z-_]{3,}",
|
pattern="[0-9a-z-_]{3,}",
|
||||||
title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.",
|
title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.") }}
|
||||||
autofocus="1") }}
|
|
||||||
{{ render_field_errors(new_dir_form.name) }}
|
{{ render_field_errors(new_dir_form.name) }}
|
||||||
<button class="btn btn-lg btn-success mt-2">Create</button>
|
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -134,12 +134,6 @@
|
|||||||
|
|
||||||
<hr class="my-2">
|
<hr class="my-2">
|
||||||
|
|
||||||
{% if alias_info.mailbox != None %}
|
|
||||||
<div class="small-text">
|
|
||||||
Owned by <b>{{ alias_info.mailbox.email }}</b> mailbox
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p class="small-text">
|
<p class="small-text">
|
||||||
Created {{ gen_email.created_at | dt }}
|
Created {{ gen_email.created_at | dt }}
|
||||||
{% if alias_info.highlight %}
|
{% if alias_info.highlight %}
|
||||||
@ -157,10 +151,42 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if current_user.full_mailbox and mailboxes|length > 1 %}
|
||||||
|
<form method="post">
|
||||||
|
<div class="small-text mt-2">Current mailbox</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<select class="form-control form-control-sm custom-select" name="mailbox">
|
||||||
|
{% for mailbox in mailboxes %}
|
||||||
|
<option value="{{ mailbox }}" {% if mailbox == alias_info.mailbox.email %} selected {% endif %}>
|
||||||
|
{{ mailbox }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-2">
|
||||||
|
<input type="hidden" name="form-name" value="set-mailbox">
|
||||||
|
<input type="hidden" name="gen-email-id" value="{{ gen_email.id }}">
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-outline-info w-100">
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% elif alias_info.mailbox != None and alias_info.mailbox.email != current_user.email %}
|
||||||
|
<div class="small-text">
|
||||||
|
Owned by <b>{{ alias_info.mailbox.email }}</b> mailbox
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
|
|
||||||
<div class="col-10">
|
<div class="col-lg-10">
|
||||||
<textarea
|
<textarea
|
||||||
name="note"
|
name="note"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@ -168,11 +194,11 @@
|
|||||||
placeholder="Alias Note.">{{ gen_email.note or "" }}</textarea>
|
placeholder="Alias Note.">{{ gen_email.note or "" }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-2">
|
<div class="col-lg-2">
|
||||||
<input type="hidden" name="form-name" value="set-note">
|
<input type="hidden" name="form-name" value="set-note">
|
||||||
<input type="hidden" name="gen-email-id" value="{{ gen_email.id }}">
|
<input type="hidden" name="gen-email-id" value="{{ gen_email.id }}">
|
||||||
|
|
||||||
<button class="btn btn-sm btn-outline-success">
|
<button class="btn btn-sm btn-outline-success w-100">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,13 +20,15 @@
|
|||||||
A <em>mailbox</em> is just another personal email address. When creating a new alias, you could choose the
|
A <em>mailbox</em> is just another personal email address. When creating a new alias, you could choose the
|
||||||
mailbox that <em>owns</em> this alias, i.e: <br>
|
mailbox that <em>owns</em> this alias, i.e: <br>
|
||||||
- all emails sent to this alias will be forwarded to this mailbox <br>
|
- all emails sent to this alias will be forwarded to this mailbox <br>
|
||||||
- from this mailbox, you can reply/send emails from the alias. <br>
|
- from this mailbox, you can reply/send emails from the alias. <br><br>
|
||||||
|
|
||||||
By default, all aliases are owned by your email <b>{{ current_user.email }}</b>. <br><br>
|
{% if current_user.full_mailbox %}
|
||||||
|
When you signed up, a mailbox is automatically created with your email <b>{{ current_user.email }}</b>
|
||||||
|
<br><br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
The mailbox doesn't have to be your email: it can be your friend's email
|
The mailbox doesn't have to be your email: it can be your friend's email
|
||||||
if you want to create aliases for your buddy. <br>
|
if you want to create aliases for your buddy.
|
||||||
They just need to validate this mailbox when they receive the activation email.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for mailbox in mailboxes %}
|
{% for mailbox in mailboxes %}
|
||||||
@ -41,22 +43,46 @@
|
|||||||
🚫
|
🚫
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if mailbox.id == current_user.default_mailbox_id %}
|
||||||
|
<div class="badge badge-primary float-right" data-toggle="tooltip"
|
||||||
|
title="When a new random alias is created, it belongs to the default mailbox">Default Mailbox
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<h6 class="card-subtitle mb-2 text-muted">
|
<h6 class="card-subtitle mb-2 text-muted">
|
||||||
Created {{ mailbox.created_at | dt }} <br>
|
Created {{ mailbox.created_at | dt }} <br>
|
||||||
<span class="font-weight-bold">{{ mailbox.nb_alias() }}</span> aliases.
|
<span class="font-weight-bold">{{ mailbox.nb_alias() }}</span> aliases. <br>
|
||||||
|
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
|
<a href="{{ url_for('dashboard.mailbox_detail_route', mailbox_id=mailbox.id) }}">Edit ➡</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer p-0">
|
<div class="card-footer p-0">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
{% if mailbox.verified and current_user.full_mailbox %}
|
||||||
|
<div class="col">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="form-name" value="set-default">
|
||||||
|
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||||
|
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||||
|
<button class="card-link btn btn-link
|
||||||
|
{% if mailbox.id == current_user.default_mailbox_id %} disabled {% endif %}"
|
||||||
|
>
|
||||||
|
Set As Default Mailbox
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" name="form-name" value="delete">
|
<input type="hidden" name="form-name" value="delete">
|
||||||
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||||
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||||
<span class="card-link btn btn-link float-right delete-mailbox">
|
<span class="card-link btn btn-link text-danger float-right delete-mailbox
|
||||||
|
{% if mailbox.id == current_user.default_mailbox_id %} disabled {% endif %}">
|
||||||
Delete
|
Delete
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
@ -81,8 +107,7 @@
|
|||||||
A verification email will be sent to this email to make sure you have access to this email.
|
A verification email will be sent to this email to make sure you have access to this email.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ new_mailbox_form.email(class="form-control", placeholder="email@example.com",
|
{{ new_mailbox_form.email(class="form-control", placeholder="email@example.com") }}
|
||||||
autofocus="1") }}
|
|
||||||
{{ render_field_errors(new_mailbox_form.email) }}
|
{{ render_field_errors(new_mailbox_form.email) }}
|
||||||
<button class="btn btn-lg btn-success mt-2">Create</button>
|
<button class="btn btn-lg btn-success mt-2">Create</button>
|
||||||
</form>
|
</form>
|
||||||
|
58
app/dashboard/templates/dashboard/mailbox_detail.html
Normal file
58
app/dashboard/templates/dashboard/mailbox_detail.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'default.html' %}
|
||||||
|
|
||||||
|
{% set active_page = "mailbox" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Mailbox {{ mailbox.email }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block default_content %}
|
||||||
|
|
||||||
|
<div class="col-md-8 offset-md-2 pb-3">
|
||||||
|
<h1 class="h3">{{ mailbox.email }}
|
||||||
|
{% if mailbox.verified %}
|
||||||
|
<span class="cursor" data-toggle="tooltip" data-original-title="Mailbox Verified">✅</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="cursor" data-toggle="tooltip" data-original-title="Mailbox Not Verified">
|
||||||
|
🚫
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Change email -->
|
||||||
|
<div class="card">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="form-name" value="update-email">
|
||||||
|
{{ change_email_form.csrf_token }}
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
|
Change Mailbox Address
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Address</label>
|
||||||
|
|
||||||
|
<!-- Not allow user to change mailbox if there's a pending change -->
|
||||||
|
{{ change_email_form.email(class="form-control", value=mailbox.email, readonly=pending_email != None) }}
|
||||||
|
{{ render_field_errors(change_email_form.email) }}
|
||||||
|
|
||||||
|
{% if pending_email %}
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="text-danger">Pending change: {{ pending_email }}</span>
|
||||||
|
<a href="{{ url_for('dashboard.cancel_mailbox_change_route', mailbox_id=mailbox.id) }}"
|
||||||
|
class="btn btn-secondary btn-sm">
|
||||||
|
Cancel mailbox change
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Change</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- END Change email -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
@ -43,9 +43,7 @@ def custom_alias():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mailboxes = [current_user.email]
|
mailboxes = current_user.mailboxes()
|
||||||
for mailbox in Mailbox.query.filter_by(user_id=current_user.id, verified=True):
|
|
||||||
mailboxes.append(mailbox.email)
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
alias_prefix = request.form.get("prefix")
|
alias_prefix = request.form.get("prefix")
|
||||||
@ -85,7 +83,8 @@ def custom_alias():
|
|||||||
LOG.d("Set alias %s domain to %s", full_alias, custom_domain)
|
LOG.d("Set alias %s domain to %s", full_alias, custom_domain)
|
||||||
gen_email.custom_domain_id = custom_domain.id
|
gen_email.custom_domain_id = custom_domain.id
|
||||||
|
|
||||||
if mailbox_email != current_user.email:
|
# assign alias to a mailbox
|
||||||
|
if current_user.full_mailbox or mailbox_email != current_user.email:
|
||||||
mailbox = Mailbox.get_by(email=mailbox_email)
|
mailbox = Mailbox.get_by(email=mailbox_email)
|
||||||
gen_email.mailbox_id = mailbox.id
|
gen_email.mailbox_id = mailbox.id
|
||||||
LOG.d("Set alias %s mailbox to %s", full_alias, mailbox)
|
LOG.d("Set alias %s mailbox to %s", full_alias, mailbox)
|
||||||
|
@ -74,6 +74,16 @@ def index():
|
|||||||
gen_email = GenEmail.create_new_random(
|
gen_email = GenEmail.create_new_random(
|
||||||
user_id=current_user.id, scheme=scheme
|
user_id=current_user.id, scheme=scheme
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if current_user.full_mailbox:
|
||||||
|
if not current_user.default_mailbox_id:
|
||||||
|
LOG.error(
|
||||||
|
"Full mailbox User %s does not have default mailbox ",
|
||||||
|
current_user,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
gen_email.mailbox_id = current_user.default_mailbox_id
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
LOG.d("generate new email %s for user %s", gen_email, current_user)
|
LOG.d("generate new email %s for user %s", gen_email, current_user)
|
||||||
@ -142,6 +152,31 @@ def index():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif request.form.get("form-name") == "set-mailbox":
|
||||||
|
gen_email_id = request.form.get("gen-email-id")
|
||||||
|
gen_email: GenEmail = GenEmail.get(gen_email_id)
|
||||||
|
mailbox_email = request.form.get("mailbox")
|
||||||
|
|
||||||
|
mailbox = Mailbox.get_by(email=mailbox_email)
|
||||||
|
if not mailbox or mailbox.user_id != current_user.id:
|
||||||
|
flash("Something went wrong, please retry", "warning")
|
||||||
|
else:
|
||||||
|
gen_email.mailbox_id = mailbox.id
|
||||||
|
db.session.commit()
|
||||||
|
LOG.d("Set alias %s mailbox to %s", gen_email, mailbox)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
f"Update mailbox for {gen_email.email} to {mailbox_email}",
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
url_for(
|
||||||
|
"dashboard.index",
|
||||||
|
highlight_gen_email_id=gen_email.id,
|
||||||
|
query=query,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(url_for("dashboard.index", query=query))
|
return redirect(url_for("dashboard.index", query=query))
|
||||||
|
|
||||||
client_users = (
|
client_users = (
|
||||||
@ -153,6 +188,8 @@ def index():
|
|||||||
|
|
||||||
sorted(client_users, key=lambda cu: cu.client.name)
|
sorted(client_users, key=lambda cu: cu.client.name)
|
||||||
|
|
||||||
|
mailboxes = current_user.mailboxes()
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"dashboard/index.html",
|
"dashboard/index.html",
|
||||||
client_users=client_users,
|
client_users=client_users,
|
||||||
@ -160,6 +197,7 @@ def index():
|
|||||||
highlight_gen_email_id=highlight_gen_email_id,
|
highlight_gen_email_id=highlight_gen_email_id,
|
||||||
query=query,
|
query=query,
|
||||||
AliasGeneratorEnum=AliasGeneratorEnum,
|
AliasGeneratorEnum=AliasGeneratorEnum,
|
||||||
|
mailboxes=mailboxes,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class NewMailboxForm(FlaskForm):
|
|||||||
@dashboard_bp.route("/mailbox", methods=["GET", "POST"])
|
@dashboard_bp.route("/mailbox", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def mailbox_route():
|
def mailbox_route():
|
||||||
if not current_user.can_use_multiple_mailbox:
|
if not current_user.can_use_multiple_mailbox and not current_user.full_mailbox:
|
||||||
flash("You don't have access to this page, redirect to home page", "warning")
|
flash("You don't have access to this page, redirect to home page", "warning")
|
||||||
return redirect(url_for("dashboard.index"))
|
return redirect(url_for("dashboard.index"))
|
||||||
|
|
||||||
@ -44,11 +44,36 @@ def mailbox_route():
|
|||||||
flash("Unknown error. Refresh the page", "warning")
|
flash("Unknown error. Refresh the page", "warning")
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
|
if mailbox.id == current_user.default_mailbox_id:
|
||||||
|
flash("You cannot delete default mailbox", "error")
|
||||||
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
email = mailbox.email
|
email = mailbox.email
|
||||||
Mailbox.delete(mailbox_id)
|
Mailbox.delete(mailbox_id)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f"Mailbox {email} has been deleted", "success")
|
flash(f"Mailbox {email} has been deleted", "success")
|
||||||
|
|
||||||
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
if request.form.get("form-name") == "set-default":
|
||||||
|
mailbox_id = request.form.get("mailbox-id")
|
||||||
|
mailbox = Mailbox.get(mailbox_id)
|
||||||
|
|
||||||
|
if not mailbox or mailbox.user_id != current_user.id:
|
||||||
|
flash("Unknown error. Refresh the page", "warning")
|
||||||
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
|
if mailbox.id == current_user.default_mailbox_id:
|
||||||
|
flash("This mailbox is already default one", "error")
|
||||||
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
|
if not mailbox.verified:
|
||||||
|
flash("Cannot set unverified mailbox as default", "error")
|
||||||
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
|
current_user.default_mailbox_id = mailbox.id
|
||||||
|
db.session.commit()
|
||||||
|
flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success")
|
||||||
|
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
elif request.form.get("form-name") == "create":
|
elif request.form.get("form-name") == "create":
|
||||||
|
150
app/dashboard/views/mailbox_detail.py
Normal file
150
app/dashboard/views/mailbox_detail.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
from flask import render_template, request, redirect, url_for, flash
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from itsdangerous import Signer, BadSignature
|
||||||
|
from wtforms import validators
|
||||||
|
from wtforms.fields.html5 import EmailField
|
||||||
|
|
||||||
|
from app.config import FLASK_SECRET
|
||||||
|
from app.config import URL
|
||||||
|
from app.dashboard.base import dashboard_bp
|
||||||
|
from app.email_utils import can_be_used_as_personal_email, email_already_used
|
||||||
|
from app.email_utils import (
|
||||||
|
send_email,
|
||||||
|
render,
|
||||||
|
)
|
||||||
|
from app.extensions import db
|
||||||
|
from app.log import LOG
|
||||||
|
from app.models import (
|
||||||
|
GenEmail,
|
||||||
|
DeletedAlias,
|
||||||
|
)
|
||||||
|
from app.models import Mailbox
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeEmailForm(FlaskForm):
|
||||||
|
email = EmailField(
|
||||||
|
"email", validators=[validators.DataRequired(), validators.Email()]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_bp.route("/mailbox/<int:mailbox_id>/", methods=["GET", "POST"])
|
||||||
|
@login_required
|
||||||
|
def mailbox_detail_route(mailbox_id):
|
||||||
|
mailbox = Mailbox.get(mailbox_id)
|
||||||
|
if not mailbox or mailbox.user_id != current_user.id:
|
||||||
|
flash("You cannot see this page", "warning")
|
||||||
|
return redirect(url_for("dashboard.index"))
|
||||||
|
|
||||||
|
change_email_form = ChangeEmailForm()
|
||||||
|
|
||||||
|
if mailbox.new_email:
|
||||||
|
pending_email = mailbox.new_email
|
||||||
|
else:
|
||||||
|
pending_email = None
|
||||||
|
|
||||||
|
if change_email_form.validate_on_submit():
|
||||||
|
new_email = change_email_form.email.data
|
||||||
|
if new_email != mailbox.email and not pending_email:
|
||||||
|
# check if this email is not already used
|
||||||
|
if (
|
||||||
|
email_already_used(new_email)
|
||||||
|
or GenEmail.get_by(email=new_email)
|
||||||
|
or DeletedAlias.get_by(email=new_email)
|
||||||
|
):
|
||||||
|
flash(f"Email {new_email} already used", "error")
|
||||||
|
elif not can_be_used_as_personal_email(new_email):
|
||||||
|
flash(
|
||||||
|
"You cannot use this email address as your mailbox", "error",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mailbox.new_email = new_email
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
s = Signer(FLASK_SECRET)
|
||||||
|
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
||||||
|
verification_url = (
|
||||||
|
URL
|
||||||
|
+ "/dashboard/mailbox/confirm_change"
|
||||||
|
+ f"?mailbox_id={mailbox_id_signed}"
|
||||||
|
)
|
||||||
|
|
||||||
|
send_email(
|
||||||
|
new_email,
|
||||||
|
f"Confirm mailbox change on SimpleLogin",
|
||||||
|
render(
|
||||||
|
"transactional/verify-mailbox-change.txt",
|
||||||
|
user=current_user,
|
||||||
|
link=verification_url,
|
||||||
|
mailbox_email=mailbox.email,
|
||||||
|
mailbox_new_email=new_email,
|
||||||
|
),
|
||||||
|
render(
|
||||||
|
"transactional/verify-mailbox-change.html",
|
||||||
|
user=current_user,
|
||||||
|
link=verification_url,
|
||||||
|
mailbox_email=mailbox.email,
|
||||||
|
mailbox_new_email=new_email,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
flash(
|
||||||
|
f"You are going to receive an email to confirm {new_email}.",
|
||||||
|
"success",
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("dashboard/mailbox_detail.html", **locals(),)
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_bp.route(
|
||||||
|
"/mailbox/<int:mailbox_id>/cancel_email_change", methods=["GET", "POST"]
|
||||||
|
)
|
||||||
|
@login_required
|
||||||
|
def cancel_mailbox_change_route(mailbox_id):
|
||||||
|
mailbox = Mailbox.get(mailbox_id)
|
||||||
|
if not mailbox or mailbox.user_id != current_user.id:
|
||||||
|
flash("You cannot see this page", "warning")
|
||||||
|
return redirect(url_for("dashboard.index"))
|
||||||
|
|
||||||
|
if mailbox.new_email:
|
||||||
|
mailbox.new_email = None
|
||||||
|
db.session.commit()
|
||||||
|
flash("Your mailbox change is cancelled", "success")
|
||||||
|
return redirect(
|
||||||
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
flash("You have no pending mailbox change", "warning")
|
||||||
|
return redirect(
|
||||||
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_bp.route("/mailbox/confirm_change")
|
||||||
|
def mailbox_confirm_change_route():
|
||||||
|
s = Signer(FLASK_SECRET)
|
||||||
|
mailbox_id = request.args.get("mailbox_id")
|
||||||
|
|
||||||
|
try:
|
||||||
|
r_id = int(s.unsign(mailbox_id))
|
||||||
|
except BadSignature:
|
||||||
|
flash("Invalid link", "error")
|
||||||
|
else:
|
||||||
|
mailbox = Mailbox.get(r_id)
|
||||||
|
mailbox.email = mailbox.new_email
|
||||||
|
mailbox.new_email = None
|
||||||
|
|
||||||
|
# mark mailbox as verified if the change request is sent from an unverified mailbox
|
||||||
|
mailbox.verified = True
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
LOG.d("Mailbox change %s is verified", mailbox)
|
||||||
|
flash(
|
||||||
|
f"The {mailbox.email} is updated", "success",
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox.id)
|
||||||
|
)
|
@ -185,6 +185,8 @@ def send_email(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
LOG.d("send email to %s, subject %s", to_email, subject)
|
||||||
|
|
||||||
# host IP, setup via Docker network
|
# host IP, setup via Docker network
|
||||||
smtp = SMTP(POSTFIX_SERVER, 25)
|
smtp = SMTP(POSTFIX_SERVER, 25)
|
||||||
|
|
||||||
@ -214,11 +216,9 @@ def send_email(
|
|||||||
msg["To"] = to_email
|
msg["To"] = to_email
|
||||||
|
|
||||||
msg_id_header = make_msgid()
|
msg_id_header = make_msgid()
|
||||||
LOG.d("message-id %s", msg_id_header)
|
|
||||||
msg["Message-ID"] = msg_id_header
|
msg["Message-ID"] = msg_id_header
|
||||||
|
|
||||||
date_header = formatdate()
|
date_header = formatdate()
|
||||||
LOG.d("Date header: %s", date_header)
|
|
||||||
msg["Date"] = date_header
|
msg["Date"] = date_header
|
||||||
|
|
||||||
# add DKIM
|
# add DKIM
|
||||||
|
@ -137,6 +137,19 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||||||
db.Boolean, default=False, nullable=False, server_default="0"
|
db.Boolean, default=False, nullable=False, server_default="0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# only use mailbox instead of default to user email
|
||||||
|
# this requires a migration before to:
|
||||||
|
# 1. create default mailbox for the user email address
|
||||||
|
# 2. assign existing aliases to this default mailbox
|
||||||
|
full_mailbox = db.Column(
|
||||||
|
db.Boolean, default=False, nullable=False, server_default="0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# the mailbox used when create random alias
|
||||||
|
default_mailbox_id = db.Column(
|
||||||
|
db.ForeignKey("mailbox.id"), nullable=True, default=None
|
||||||
|
)
|
||||||
|
|
||||||
profile_picture = db.relationship(File)
|
profile_picture = db.relationship(File)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -154,6 +167,14 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||||||
GenEmail.create_new(user.id, prefix="my-first-alias")
|
GenEmail.create_new(user.id, prefix="my-first-alias")
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
# todo: uncomment when all existing users are full_mailbox
|
||||||
|
# to run just after migrating all existing user to full mailbox
|
||||||
|
# so new users are automatically full-mailbox
|
||||||
|
# mb = Mailbox.create(user_id=user.id, email=user.email, verified=True)
|
||||||
|
# db.session.flush()
|
||||||
|
# user.full_mailbox = True
|
||||||
|
# user.default_mailbox_id = mb.id
|
||||||
|
|
||||||
# Schedule onboarding emails
|
# Schedule onboarding emails
|
||||||
Job.create(
|
Job.create(
|
||||||
name=JOB_ONBOARDING_1,
|
name=JOB_ONBOARDING_1,
|
||||||
@ -270,6 +291,18 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||||||
def verified_custom_domains(self):
|
def verified_custom_domains(self):
|
||||||
return CustomDomain.query.filter_by(user_id=self.id, verified=True).all()
|
return CustomDomain.query.filter_by(user_id=self.id, verified=True).all()
|
||||||
|
|
||||||
|
def mailboxes(self) -> [str]:
|
||||||
|
"""list of mailbox emails that user own"""
|
||||||
|
if self.full_mailbox:
|
||||||
|
mailboxes = []
|
||||||
|
else:
|
||||||
|
mailboxes = [self.email]
|
||||||
|
|
||||||
|
for mailbox in Mailbox.query.filter_by(user_id=self.id, verified=True):
|
||||||
|
mailboxes.append(mailbox.email)
|
||||||
|
|
||||||
|
return mailboxes
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<User {self.id} {self.name} {self.email}>"
|
return f"<User {self.id} {self.name} {self.email}>"
|
||||||
|
|
||||||
@ -491,7 +524,7 @@ class GenEmail(db.Model, ModelMixin):
|
|||||||
mailbox = db.relationship("Mailbox")
|
mailbox = db.relationship("Mailbox")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_new(cls, user_id, prefix, note=None):
|
def create_new(cls, user_id, prefix, note=None, mailbox_id=None):
|
||||||
if not prefix:
|
if not prefix:
|
||||||
raise Exception("alias prefix cannot be empty")
|
raise Exception("alias prefix cannot be empty")
|
||||||
|
|
||||||
@ -503,7 +536,9 @@ class GenEmail(db.Model, ModelMixin):
|
|||||||
if not cls.get_by(email=email):
|
if not cls.get_by(email=email):
|
||||||
break
|
break
|
||||||
|
|
||||||
return GenEmail.create(user_id=user_id, email=email, note=note)
|
return GenEmail.create(
|
||||||
|
user_id=user_id, email=email, note=note, mailbox_id=mailbox_id
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_new_random(
|
def create_new_random(
|
||||||
@ -828,7 +863,8 @@ class Mailbox(db.Model, ModelMixin):
|
|||||||
email = db.Column(db.String(256), unique=True, nullable=False)
|
email = db.Column(db.String(256), unique=True, nullable=False)
|
||||||
verified = db.Column(db.Boolean, default=False, nullable=False)
|
verified = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
user = db.relationship(User)
|
# used when user wants to update mailbox email
|
||||||
|
new_email = db.Column(db.String(256), unique=True)
|
||||||
|
|
||||||
def nb_alias(self):
|
def nb_alias(self):
|
||||||
return GenEmail.filter_by(mailbox_id=self.id).count()
|
return GenEmail.filter_by(mailbox_id=self.id).count()
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
<div class="col-sm-6"
|
<div class="col-sm-6"
|
||||||
style="padding-left: 5px">
|
style="padding-left: 5px">
|
||||||
<select class="form-control" name="suffix">
|
<select class="form-control custom-select" name="suffix">
|
||||||
{% for suffix in suffixes %}
|
{% for suffix in suffixes %}
|
||||||
<option value="{{ suffix[1] }}">
|
<option value="{{ suffix[1] }}">
|
||||||
{% if suffix[0] %}
|
{% if suffix[0] %}
|
||||||
|
33
migrations/versions/2020_022314_903ec5f566e8_.py
Normal file
33
migrations/versions/2020_022314_903ec5f566e8_.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 903ec5f566e8
|
||||||
|
Revises: 3fa3a648c8e7
|
||||||
|
Create Date: 2020-02-23 14:11:46.332532
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy_utils
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '903ec5f566e8'
|
||||||
|
down_revision = '3fa3a648c8e7'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('mailbox', sa.Column('new_email', sa.String(length=256), nullable=True))
|
||||||
|
op.create_unique_constraint(None, 'mailbox', ['new_email'])
|
||||||
|
op.add_column('users', sa.Column('full_mailbox', sa.Boolean(), server_default='0', nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'full_mailbox')
|
||||||
|
op.drop_constraint(None, 'mailbox', type_='unique')
|
||||||
|
op.drop_column('mailbox', 'new_email')
|
||||||
|
# ### end Alembic commands ###
|
31
migrations/versions/2020_022316_f580030d9beb_.py
Normal file
31
migrations/versions/2020_022316_f580030d9beb_.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: f580030d9beb
|
||||||
|
Revises: 903ec5f566e8
|
||||||
|
Create Date: 2020-02-23 16:03:46.064813
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy_utils
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f580030d9beb'
|
||||||
|
down_revision = '903ec5f566e8'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('default_mailbox_id', sa.Integer(), nullable=True))
|
||||||
|
op.create_foreign_key(None, 'users', 'mailbox', ['default_mailbox_id'], ['id'])
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'users', type_='foreignkey')
|
||||||
|
op.drop_column('users', 'default_mailbox_id')
|
||||||
|
# ### end Alembic commands ###
|
17
server.py
17
server.py
@ -132,6 +132,7 @@ def fake_data():
|
|||||||
is_admin=True,
|
is_admin=True,
|
||||||
otp_secret="base32secret3232",
|
otp_secret="base32secret3232",
|
||||||
can_use_multiple_mailbox=True,
|
can_use_multiple_mailbox=True,
|
||||||
|
full_mailbox=True,
|
||||||
)
|
)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@ -156,9 +157,15 @@ def fake_data():
|
|||||||
api_key = ApiKey.create(user_id=user.id, name="Firefox")
|
api_key = ApiKey.create(user_id=user.id, name="Firefox")
|
||||||
api_key.code = "codeFF"
|
api_key.code = "codeFF"
|
||||||
|
|
||||||
GenEmail.create_new(user.id, "e1@")
|
m1 = Mailbox.create(user_id=user.id, email="m1@cd.ef", verified=True)
|
||||||
GenEmail.create_new(user.id, "e2@")
|
m2 = Mailbox.create(user_id=user.id, email="m2@zt.com", verified=False)
|
||||||
GenEmail.create_new(user.id, "e3@")
|
m3 = Mailbox.create(user_id=user.id, email="m3@cd.ef", verified=True)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
user.default_mailbox_id = m1.id
|
||||||
|
|
||||||
|
GenEmail.create_new(user.id, "e1@", mailbox_id=m1.id)
|
||||||
|
GenEmail.create_new(user.id, "e2@", mailbox_id=m3.id)
|
||||||
|
|
||||||
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
|
CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True)
|
||||||
CustomDomain.create(
|
CustomDomain.create(
|
||||||
@ -185,10 +192,6 @@ def fake_data():
|
|||||||
client2.published = True
|
client2.published = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
Mailbox.create(user_id=user.id, email="ab@cd.ef", verified=True)
|
|
||||||
Mailbox.create(user_id=user.id, email="xy@zt.com", verified=False)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
DeletedAlias.create(user_id=user.id, email="d1@ab.cd")
|
DeletedAlias.create(user_id=user.id, email="d1@ab.cd")
|
||||||
DeletedAlias.create(user_id=user.id, email="d2@ab.cd")
|
DeletedAlias.create(user_id=user.id, email="d2@ab.cd")
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
18
shell.py
18
shell.py
@ -40,6 +40,24 @@ def send_safari_extension_newsletter():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_user_full_mailbox(user):
|
||||||
|
# create a default mailbox
|
||||||
|
default_mb = Mailbox.get_by(user_id=user.id, email=user.email)
|
||||||
|
if not default_mb:
|
||||||
|
LOG.d("create default mailbox for user %s", user)
|
||||||
|
default_mb = Mailbox.create(user_id=user.id, email=user.email, verified=True)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# assign existing alias to this mailbox
|
||||||
|
for gen_email in GenEmail.query.filter_by(user_id=user.id):
|
||||||
|
gen_email.mailbox_id = default_mb.id
|
||||||
|
|
||||||
|
# finally set user to full_mailbox
|
||||||
|
user.full_mailbox = True
|
||||||
|
user.default_mailbox_id = default_mb.id
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
and create your <b>business emails</b> using email alias. This is cheaper and more convenient than buying a dedicated solution like GSuite. By the way, all our business emails are actually aliases.') }}
|
and create your <b>business emails</b> using email alias. This is cheaper and more convenient than buying a dedicated solution like GSuite. By the way, all our business emails are actually aliases.') }}
|
||||||
|
|
||||||
{% if user.in_trial() %}
|
{% if user.in_trial() %}
|
||||||
{{ render_text('You can use all premium features like <em>custom domain</em> or <em>alias directory</em> during the <b>trial period</b>. Your trial will end ' + user.trial_end.humanize() + ".") }}
|
{{ render_text('You can use all premium features like <em>custom domain</em> or <em>alias directory</em> during the <b>trial period</b>. Your trial will end ' + user.trial_end.humanize() + ". All aliases you create during the trial will continue to work normally when the trial ends.") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ render_text('In the next coming days, you are going to receive some onboarding emails to quickly present SimpleLogin features. If you don\'t want to receive these emails, you can disable them on <a href="https://app.simplelogin.io/dashboard/setting#notification">Settings</a>.') }}
|
{{ render_text('In the next coming days, you are going to receive some onboarding emails to quickly present SimpleLogin features. If you don\'t want to receive these emails, you can disable them on <a href="https://app.simplelogin.io/dashboard/setting#notification">Settings</a>.') }}
|
||||||
|
@ -22,6 +22,7 @@ and create your business emails backed by your personal email! By the way, all o
|
|||||||
{% if user.in_trial() %}
|
{% if user.in_trial() %}
|
||||||
You can use all premium features like custom domain or alias directory during the trial period.
|
You can use all premium features like custom domain or alias directory during the trial period.
|
||||||
Your trial will end {{ user.trial_end.humanize() }}.
|
Your trial will end {{ user.trial_end.humanize() }}.
|
||||||
|
All aliases you create during the trial will continue to work normally when the trial ends.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
If there's anything that's bugging you, even the smallest of issues that could be done better, I want to hear about it - so hit the reply button.
|
If there's anything that's bugging you, even the smallest of issues that could be done better, I want to hear about it - so hit the reply button.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
{{ render_text("When the trial ends:") }}
|
{{ render_text("When the trial ends:") }}
|
||||||
|
|
||||||
{{ render_text("- All aliases/domains/directories you have created are <b>kept</b> and continue working.") }}
|
{{ render_text("- All aliases/domains/directories you have created are <b>kept</b> and continue working normally.") }}
|
||||||
{{ render_text("- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.") }}
|
{{ render_text("- You cannot create new aliases if you exceed the free plan limit, i.e. have more than 5 aliases.") }}
|
||||||
{{ render_text("- As features like <b>catch-all</b> or <b>directory</b> allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.") }}
|
{{ render_text("- As features like <b>catch-all</b> or <b>directory</b> allow you to create aliases on-the-fly, those aliases cannot be automatically created if you have more than 5 aliases.") }}
|
||||||
{{ render_text("- You cannot add new domain or directory.") }}
|
{{ render_text("- You cannot add new domain or directory.") }}
|
||||||
|
11
templates/emails/transactional/verify-mailbox-change.html
Normal file
11
templates/emails/transactional/verify-mailbox-change.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ render_text("Hi " + user.name) }}
|
||||||
|
{{ render_text("You recently requested to change mailbox <b>"+ mailbox_email +"</b> to <b>" + mailbox_new_email + "</b>.") }}
|
||||||
|
{{ render_text("To confirm, please click on the button below.") }}
|
||||||
|
{{ render_button("Confirm mailbox change", link) }}
|
||||||
|
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
|
||||||
|
{{ raw_url(link) }}
|
||||||
|
{% endblock %}
|
||||||
|
|
10
templates/emails/transactional/verify-mailbox-change.txt
Normal file
10
templates/emails/transactional/verify-mailbox-change.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Hi {{user.name}}
|
||||||
|
|
||||||
|
You recently requested to change mailbox {{mailbox_email}} to {{mailbox_new_email}}
|
||||||
|
|
||||||
|
To confirm, please click on this link:
|
||||||
|
|
||||||
|
{{link}}
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
Son - SimpleLogin founder.
|
@ -29,7 +29,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if current_user.can_use_multiple_mailbox %}
|
{% if current_user.can_use_multiple_mailbox or current_user.full_mailbox %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('dashboard.mailbox_route') }}"
|
<a href="{{ url_for('dashboard.mailbox_route') }}"
|
||||||
class="nav-link {{ 'active' if active_page == 'mailbox' }}">
|
class="nav-link {{ 'active' if active_page == 'mailbox' }}">
|
||||||
|
Loading…
Reference in New Issue
Block a user