Repository: Couchers-org/couchers (language: python)

Issue:
Notify v2.x
Todo:

* ~`handle_email_notifications.SCHEDULE = timedelta(seconds=5)`~
* ~push notifications enabled~
* email coalescing
* notification escaping
* turning off push notifications (by user)
* ~move rest of templates over to new system~
* ~remove old templating~
* ~testing of push itself~
* testing of all email templates/pushes
* ~Cleaning up unsub type "all"~
* More principled `context` when not in req (instead of `SimpleNamespace`)
* ~Double check notification defaults~
* ~Return user agent on subscription setup~
Notify v2.1
Todo:

* ~~`handle_email_notifications.SCHEDULE = timedelta(seconds=5)`~~
* ~~push notifications enabled~~
* ~~email coalescing~~
* notification escaping
* turning off push notifications
* move rest of templates over to new system
* remove old templating
* testing of push itself
* testing of all email templates/pushes
* ~~Cleaning up unsub type "all"~~
* More principled `context` when not in req (instead of `SimpleNamespace`)
* Double check notification defaults
* ~~Send user agent on subscription setup~~


Discussion / hints:

Fix patch:
diff --git a/app/backend/src/couchers/email/__init__.py b/app/backend/src/couchers/email/__init__.py
index 888fa63a0d..ae13f4e2ae 100644
--- a/app/backend/src/couchers/email/__init__.py
+++ b/app/backend/src/couchers/email/__init__.py
@@ -1,5 +1,4 @@
 import logging
-from html import escape
 from pathlib import Path
 
 import yaml
@@ -7,7 +6,6 @@
 
 from couchers.config import config
 from couchers.jobs.enqueue import queue_job
-from couchers.notifications.unsubscribe import generate_do_not_email
 from proto.internal import jobs_pb2
 
 logger = logging.getLogger(__name__)
@@ -16,79 +14,30 @@
 env = Environment(loader=loader, trim_blocks=True)
 
 
-def couchers_escape(value):
-    return escape(str(value))
-
-
-def couchers_safe(value):
-    return value
-
-
-env.filters["couchers_escape"] = couchers_escape
-env.filters["couchers_safe"] = couchers_safe
-
-plain_base_template = env.get_template("email_base_plain.md")
-html_base_template = env.get_template("email_base_html.html")
-
-
-def render_html(text):
-    stripped_paragraphs = text.strip().split("\n\n")
-    return "\n\n".join(f"<p>{paragraph.strip()}</p>" for paragraph in stripped_paragraphs)
-
-
-def _render_email(template_file, template_args, _footer_unsub_link):
-    """
-    Renders both a plain-text and a HTML version of an email, and embeds both in their base templates
-
-    The template should look something like this:
-
-    ```
-    ---
-    subject: "Email for {{ user.name|couchers_escape }}"
-    ---
-
-    Hello {{ user.name|couchers_escape }}, this is a sample email
-    ```
-
-    The first bit, sandwiched between the first two `---`s is the "frontmatter", some YAML stuff. Currently it just
-    contains the subject.
-
-    We split these into the frontmatter and the message text (note the text may contain more `---`s which then denote
-    horizontal rules `<hr />` in HTML). The frontmatter is rendered through jinja2 so that you can use the template
-    arguments to modify it, e.g. use the user name or something in the subject.
-
-    The body is run through twice, once for the plaintext, once for HTML, and then the HTML version is run through
-    render_html above to turn it into HTML.
-    """
-    source, _, _ = loader.get_source(env, f"{template_file}.md")
-
-    # the file should start with a "---"
-    stub, frontmatter_source, text_source = source.split("---", 2)
-    assert stub == ""
-
-    frontmatter_template = env.from_string(frontmatter_source)
-    template = env.from_string(text_source)
-
-    rendered_frontmatter = frontmatter_template.render(**template_args, plain=True, html=False)
-    frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader)
-
-    plain_content = template.render({**template_args, "frontmatter": frontmatter}, plain=True, html=False)
-    html_content = render_html(template.render({**template_args, "frontmatter": frontmatter}, plain=False, html=True))
-
-    plain = plain_base_template.render(
-        frontmatter=frontmatter, content=plain_content, _footer_unsub_link=_footer_unsub_link
+def _queue_email(sender_name, sender_email, recipient, subject, plain, html, list_unsubscribe_header, source_data):
+    payload = jobs_pb2.SendEmailPayload(
+        sender_name=sender_name,
+        sender_email=sender_email,
+        recipient=recipient,
+        subject=subject,
+        plain=plain,
+        html=html,
+        list_unsubscribe_header=list_unsubscribe_header,
+        source_data=source_data,
     )
-    html = html_base_template.render(
-        frontmatter=frontmatter, content=html_content, _footer_unsub_link=_footer_unsub_link
+    queue_job(
+        job_type="send_email",
+        payload=payload,
     )
 
-    return frontmatter, plain, html
-
 
 def queue_email(
     sender_name, sender_email, recipient, subject, plain, html, list_unsubscribe_header=None, source_data=None
 ):
-    payload = jobs_pb2.SendEmailPayload(
+    """
+    This indirection is so that this can be easily mocked. Not sure how to do it better :(
+    """
+    _queue_email(
         sender_name=sender_name,
         sender_email=sender_email,
         recipient=recipient,
@@ -98,34 +47,23 @@ def queue_email(
         list_unsubscribe_header=list_unsubscribe_header,
         source_data=source_data,
     )
-    queue_job(
-        job_type="send_email",
-        payload=payload,
-    )
 
 
-def enqueue_email_from_template(recipient, template_file, template_args=None, _footer_unsub_link=None):
-    template_args = template_args or {}
-    frontmatter, plain, html = _render_email(template_file, template_args, _footer_unsub_link=_footer_unsub_link)
+def enqueue_system_email(recipient, template_name, template_args):
+    source, _, _ = loader.get_source(env, f"system/{template_name}.md")
+    _, frontmatter_source, text_source = source.split("---", 2)
+
+    rendered_frontmatter = env.from_string(frontmatter_source).render(**template_args, plain=True, html=False)
+    frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader)
+
+    plain = env.from_string(text_source).render({**template_args, "frontmatter": frontmatter}, plain=True, html=False)
+
     queue_email(
         config["NOTIFICATION_EMAIL_SENDER"],
         config["NOTIFICATION_EMAIL_ADDRESS"],
         recipient,
         config["NOTIFICATION_EMAIL_PREFIX"] + frontmatter["subject"],
         plain,
-        html,
-    )
-
-
-def enqueue_email_from_template_to_user(user, template_file, template_args=None, is_critical_email=False):
-    template_args = template_args or {}
-    if user.do_not_email and not is_critical_email:
-        logger.info(f"Not emailing {user} based on template {template_file} due to emails turned off")
-        return
-    enqueue_email_from_template(
-        user.email,
-        template_file,
-        template_args=template_args,
-        # don't include the link on security emails
-        _footer_unsub_link=generate_do_not_email(user.id) if not is_critical_email else None,
+        None,
+        source_data=template_name,
     )
diff --git a/app/backend/src/couchers/email/smtp.py b/app/backend/src/couchers/email/smtp.py
index a3f8de4247..542d7d1fcb 100644
--- a/app/backend/src/couchers/email/smtp.py
+++ b/app/backend/src/couchers/email/smtp.py
@@ -37,23 +37,25 @@ def send_smtp_email(sender_name, sender_email, recipient, subject, plain, html,
         msg["X-Couchers-Source-Data"] = source_data
         msg["X-Couchers-Source-Sig"] = simple_hash_signature(source_data, EMAIL_SOURCE_DATA_KEY_NAME)
 
-    # for any png files in attachment_imgs/, goes through and replaces instances of the filename with attachment
-    used_attachments = []
-    for attachment in (template_base / "attachment_imgs").glob("*.png"):
-        attachment_html_path = str(attachment.relative_to(template_base))
-        if attachment_html_path not in html:
-            continue
-        # it's used in this template, so attach and replace it
-        data = attachment.read_bytes()
-        cid, wcid = make_cid(sender_email)
-        html = html.replace(attachment_html_path, f"cid:{wcid}")
-        used_attachments.append((cid, "image", "png", data))
-
     msg.set_content(plain)
-    msg.add_alternative(html, subtype="html")
 
-    for cid, mime_type, mime_subtype, data in used_attachments:
-        msg.get_payload()[1].add_related(data, mime_type, mime_subtype, cid=cid)
+    if html:
+        # for any png files in attachment_imgs/, goes through and replaces instances of the filename with attachment
+        used_attachments = []
+        for attachment in (template_base / "attachment_imgs").glob("*.png"):
+            attachment_html_path = str(attachment.relative_to(template_base))
+            if attachment_html_path not in html:
+                continue
+            # it's used in this template, so attach and replace it
+            data = attachment.read_bytes()
+            cid, wcid = make_cid(sender_email)
+            html = html.replace(attachment_html_path, f"cid:{wcid}")
+            used_attachments.append((cid, "image", "png", data))
+
+        msg.add_alternative(html, subtype="html")
+
+        for cid, mime_type, mime_subtype, data in used_attachments:
+            msg.get_payload()[1].add_related(data, mime_type, mime_subtype, cid=cid)
 
     with smtplib.SMTP(config["SMTP_HOST"], config["SMTP_PORT"]) as server:
         server.ehlo()
diff --git a/app/backend/src/couchers/jobs/handlers.py b/app/backend/src/couchers/jobs/handlers.py
index a406f5ab59..bd702aa447 100644
--- a/app/backend/src/couchers/jobs/handlers.py
+++ b/app/backend/src/couchers/jobs/handlers.py
@@ -15,7 +15,6 @@
 from sqlalchemy.sql import and_, case, cast, delete, distinct, extract, func, literal, not_, or_, select, union_all
 from sqlalchemy.sql.functions import percentile_disc
 
-from couchers import email, urls
 from couchers.config import config
 from couchers.crypto import asym_encrypt, b64decode, simple_decrypt
 from couchers.db import session_scope
@@ -57,9 +56,9 @@
     generate_event_delete_notifications,
     generate_event_update_notifications,
 )
+from couchers.servicers.requests import host_request_to_pb
 from couchers.sql import couchers_select as select
 from couchers.tasks import enforce_community_memberships as tasks_enforce_community_memberships
-from couchers.tasks import send_onboarding_email
 from couchers.utils import now
 from proto import notification_data_pb2
 from proto.internal import jobs_pb2, verification_pb2
@@ -200,22 +199,33 @@ def send_message_notifications(payload):
             ).all()
 
             user.last_notified_message_id = max(message.id for _, message, _ in unseen_messages)
-            session.commit()
-
-            total_unseen_message_count = sum(count for _, _, count in unseen_messages)
 
-            email.enqueue_email_from_template_to_user(
-                user,
-                "unseen_messages",
-                template_args={
-                    "user": user,
-                    "total_unseen_message_count": total_unseen_message_count,
-                    "unseen_messages": [  # noqa: C416
-                        (group_chat, latest_message, count) for group_chat, latest_message, count in unseen_messages
+            def format_title(message, group_chat, count_unseen):
+                if group_chat.is_dm:
+                    return f"You missed {count_unseen} message(s) from {message.author.name}"
+                else:
+                    return f"You missed {count_unseen} message(s) in {group_chat.title}"
+
+            notify(
+                user_id=user.id,
+                topic_action="chat:missed_messages",
+                data=notification_data_pb2.ChatMissedMessages(
+                    messages=[
+                        notification_data_pb2.ChatMessage(
+                            author=user_model_to_pb(
+                                message.author,
+                                session,
+                                SimpleNamespace(user_id=user.id),
+                            ),
+                            message=format_title(message, group_chat, count_unseen),
+                            text=message.text,
+                            group_chat_id=message.conversation_id,
+                        )
+                        for group_chat, message, count_unseen in unseen_messages
                     ],
-                    "group_chats_link": urls.messages_link(),
-                },
+                ),
             )
+            session.commit()
 
 
 send_message_notifications.PAYLOAD = empty_pb2.Empty
@@ -257,30 +267,34 @@ def send_request_notifications(payload):
 
         for user, host_request, max_message_id in surfing_reqs:
             user.last_notified_request_message_id = max(user.last_notified_request_message_id, max_message_id)
-            session.commit()
-
-            email.enqueue_email_from_template_to_user(
-                user,
-                "unseen_message_guest",
-                template_args={
-                    "user": user,
-                    "host_request": host_request,
-                    "host_request_link": urls.host_request_link_guest(),
-                },
+            session.flush()
+
+            context = SimpleNamespace(user_id=user.id)
+            notify(
+                user_id=user.id,
+                topic_action="host_request:missed_messages",
+                key=host_request.conversation_id,
+                data=notification_data_pb2.HostRequestMissedMessages(
+                    host_request=host_request_to_pb(host_request, session, context),
+                    user=user_model_to_pb(host_request.host, session, context),
+                    am_host=False,
+                ),
             )
 
         for user, host_request, max_message_id in hosting_reqs:
             user.last_notified_request_message_id = max(user.last_notified_request_message_id, max_message_id)
-            session.commit()
-
-            email.enqueue_email_from_template_to_user(
-                user,
-                "unseen_message_host",
-                template_args={
-                    "user": user,
-                    "host_request": host_request,
-                    "host_request_link": urls.host_request_link_host(),
-                },
+            session.flush()
+
+            context = SimpleNamespace(user_id=user.id)
+            notify(
+                user_id=user.id,
+                topic_action="host_request:missed_messages",
+                key=host_request.conversation_id,
+                data=notification_data_pb2.HostRequestMissedMessages(
+                    host_request=host_request_to_pb(host_request, session, context),
+                    user=user_model_to_pb(host_request.surfer, session, context),
+                    am_host=True,
+                ),
             )
 
 
@@ -301,7 +315,11 @@ def send_onboarding_emails(payload):
         )
 
         for user in users:
-            send_onboarding_email(user, email_number=1)
+            notify(
+                user_id=user.id,
+                topic_action="onboarding:reminder",
+                key="1",
+            )
             user.onboarding_emails_sent = 1
             user.last_onboarding_email_sent = now()
             session.commit()
@@ -321,7 +339,11 @@ def send_onboarding_emails(payload):
         )
 
         for user in users:
-            send_onboarding_email(user, email_number=2)
+            notify(
+                user_id=user.id,
+                topic_action="onboarding:reminder",
+                key="2",
+            )
             user.onboarding_emails_sent = 2
             user.last_onboarding_email_sent = now()
             session.commit()
diff --git a/app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v3.py b/app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v2_1.py
similarity index 98%
rename from app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v3.py
rename to app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v2_1.py
index 9e9b9033c7..93dfdd0f1f 100644
--- a/app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v3.py
+++ b/app/backend/src/couchers/migrations/versions/bc79d9a6fd26_notify_v2_1.py
@@ -1,4 +1,4 @@
-"""Notify v3
+"""Notify v2.1
 
 Revision ID: bc79d9a6fd26
 Revises: ed8f8aad559c
diff --git a/app/backend/src/couchers/migrations/versions/f53ecea60964_notify_v2_2.py b/app/backend/src/couchers/migrations/versions/f53ecea60964_notify_v2_2.py
new file mode 100644
index 0000000000..b869b64f40
--- /dev/null
+++ b/app/backend/src/couchers/migrations/versions/f53ecea60964_notify_v2_2.py
@@ -0,0 +1,25 @@
+"""Notify v2.2
+
+Revision ID: f53ecea60964
+Revises: 49669627f688
+Create Date: 2024-06-11 21:43:29.125853
+
+"""
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "f53ecea60964"
+down_revision = "49669627f688"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    op.execute("ALTER TYPE notificationtopicaction ADD VALUE 'chat__missed_messages'")
+    op.execute("ALTER TYPE notificationtopicaction ADD VALUE 'onboarding__reminder'")
+    op.execute("ALTER TYPE notificationtopicaction ADD VALUE 'host_request__missed_messages'")
+
+
+def downgrade():
+    raise Exception("Can't downgrade")
diff --git a/app/backend/src/couchers/models.py b/app/backend/src/couchers/models.py
index 9d446ff3d7..c40dceb1f9 100644
--- a/app/backend/src/couchers/models.py
+++ b/app/backend/src/couchers/models.py
@@ -2185,6 +2185,7 @@ def __str__(self):
     host_request__confirm = ("host_request:confirm", dt_all, True, nd.HostRequestConfirm)
     host_request__cancel = ("host_request:cancel", dt_all, True, nd.HostRequestCancel)
     host_request__message = ("host_request:message", [dt.push, dt.digest], True, nd.HostRequestMessage)
+    host_request__missed_messages = ("host_request:missed_messages", [dt.email], True, nd.HostRequestMissedMessages)
 
     # you receive a friend ref
     reference__receive_friend = ("reference:receive_friend", dt_all, True, nd.ReferenceReceiveFriend)
@@ -2203,6 +2204,7 @@ def __str__(self):
 
     # group chats
     chat__message = ("chat:message", [dt.push, dt.digest], True, nd.ChatMessage)
+    chat__missed_messages = ("chat:missed_messages", [dt.email], True, nd.ChatMissedMessages)
 
     # events
     # approved by mods
@@ -2238,6 +2240,8 @@ def __str__(self):
 
     donation__received = ("donation:received", dt_sec, True, nd.DonationReceived)
 
+    onboarding__reminder = ("onboarding:reminder", dt_sec, True, empty_pb2.Empty)
+
 
 class NotificationPreference(Base):
     __tablename__ = "notification_preferences"
diff --git a/app/backend/src/couchers/notifications/background.py b/app/backend/src/couchers/notifications/background.py
index fdf53849f7..675630742a 100644
--- a/app/backend/src/couchers/notifications/background.py
+++ b/app/backend/src/couchers/notifications/background.py
@@ -23,7 +23,6 @@
 from couchers.notifications.settings import get_preference
 from couchers.notifications.unsubscribe import generate_unsub
 from couchers.sql import couchers_select as select
-from couchers.tasks import send_digest_email
 from couchers.templates.v2 import add_filters
 from couchers.utils import get_tz_as_text, now
 from proto.internal import jobs_pb2
@@ -51,7 +50,7 @@ def _generate_unsub(unsub_type, one_click=False):
         **rendered.email_template_args,
     }
 
-    manage_link = urls.feature_preview_link()
+    manage_link = urls.account_settings_link()
 
     template_args["_year"] = now().year
     template_args["_timezone_display"] = get_tz_as_text(user.timezone or "Etc/UTC")
@@ -278,7 +277,7 @@ def handle_email_digests(payload: empty_pb2.Empty):
             if notifications_and_deliveries:
                 notifications, deliveries = zip(*notifications_and_deliveries)
                 logger.info(f"Sending {user.id=} a digest with {len(notifications)} notifications")
-                send_digest_email(user, notifications)
+                logger.info("TODO: supposed to send digest email")
                 for delivery in deliveries:
                     delivery.delivered = func.now()
                 user.last_digest_sent = func.now()
diff --git a/app/backend/src/couchers/notifications/render.py b/app/backend/src/couchers/notifications/render.py
index ecf8910425..3f92ed41f7 100644
--- a/app/backend/src/couchers/notifications/render.py
+++ b/app/backend/src/couchers/notifications/render.py
@@ -40,7 +40,28 @@ def render_notification(user, notification) -> RenderedNotification:
     data = notification.topic_action.data_type.FromString(notification.data)
     if notification.topic == "host_request":
         view_link = urls.host_request(host_request_id=data.host_request.host_request_id)
-        if notification.action in ["create", "message"]:
+        if notification.action == "missed_messages":
+            their_your = "their" if data.am_host else "your"
+            other = data.user
+            # "rejected your host request", or similar
+            message = f"{other.name} sent you message(s) in {their_your} host request"
+            return RenderedNotification(
+                email_subject=message,
+                email_preview=message,
+                email_template_name="host_request__plain",
+                email_template_args={
+                    "view_link": view_link,
+                    "host_request": data.host_request,
+                    "message": message,
+                    "other": other,
+                },
+                email_topic_action_unsubscribe_text="missed messages in host requests",
+                push_title=message,
+                push_body="Check the app for more info.",
+                push_icon=v2avatar(other),
+                push_url=view_link,
+            )
+        elif notification.action in ["create", "message"]:
             if notification.action == "create":
                 other = data.surfer
                 message = f"{other.name} sent you a host request"
@@ -412,6 +433,28 @@ def render_notification(user, notification) -> RenderedNotification:
             push_icon=v2avatar(data.author),
             push_url=urls.chat_link(chat_id=data.group_chat_id),
         )
+    elif notification.topic_action.display == "chat:missed_messages":
+        return RenderedNotification(
+            email_subject="You have unseen messages on Couchers.org!",
+            email_preview="You missed some messages on the platform.",
+            email_template_name="chat_unseen_messages",
+            email_template_args={
+                "items": [
+                    {
+                        "author": item.author,
+                        "message": item.message,
+                        "text": item.text,
+                        "view_link": urls.chat_link(chat_id=item.group_chat_id),
+                    }
+                    for item in data.messages
+                ]
+            },
+            email_topic_action_unsubscribe_text="unseen chat messages",
+            push_title="You have unseen messages on Couchers.org",
+            push_body="Please check out any messages you missed.",
+            push_icon=urls.icon_url(),
+            push_url=urls.messages_link(),
+        )
     elif notification.topic == "event":
         event = data.event
         time_display = f"{v2timestamp(event.start_time, user)} - {v2timestamp(event.end_time, user)}"
@@ -606,5 +649,35 @@ def render_notification(user, notification) -> RenderedNotification:
                 push_icon=v2avatar(data.other_user),
                 push_url=leave_reference_link,
             )
+    elif notification.topic_action.display == "onboarding:reminder":
+        if notification.key == "1":
+            return RenderedNotification(
+                email_subject="Welcome to Couchers.org and the future of couch surfing",
+                email_preview="We are so excited to have you join our community!",
+                email_template_name="onboarding1",
+                email_template_args={
+                    "app_link": urls.app_link(),
+                    "edit_profile_link": urls.edit_profile_link(),
+                },
+                email_topic_action_unsubscribe_text="onboarding emails",
+                push_title="Welcome to Couchers.org and the future of couch surfing",
+                push_body=f"Hi {v2esc(user.name)}! We are excited that you have joined us! Please take a moment to complete your profile with a picture and a bit of text about yourself!",
+                push_icon=urls.icon_url(),
+                push_url=urls.edit_profile_link(),
+            )
+        elif notification.key == "2":
+            return RenderedNotification(
+                email_subject="Complete your profile on Couchers.org",
+                email_preview="We would ask one big favour of you: please fill out your profile by adding a photo and some text.",
+                email_template_name="onboarding2",
+                email_template_args={
+                    "edit_profile_link": urls.edit_profile_link(),
+                },
+                email_topic_action_unsubscribe_text="onboarding emails",
+                push_title="Please complete your profile on Couchers.org!",
+                push_body=f"Hi {v2esc(user.name)}! We would ask one big favour of you: please fill out your profile by adding a photo and some text.",
+                push_icon=urls.icon_url(),
+                push_url=urls.edit_profile_link(),
+            )
     else:
         raise NotImplementedError(f"Unknown topic-action: {notification.topic}:{notification.action}")
diff --git a/app/backend/src/couchers/notifications/settings.py b/app/backend/src/couchers/notifications/settings.py
index 2440974ca2..cea5781003 100644
--- a/app/backend/src/couchers/notifications/settings.py
+++ b/app/backend/src/couchers/notifications/settings.py
@@ -89,6 +89,7 @@ def set_preference(session, user_id, topic_action: NotificationTopicAction, deli
                     ("reject", "Someone rejects your host request"),
                     ("cancel", "Someone cancels their host request"),
                     ("message", "Someone sends a message in a host request"),
+                    ("missed_messages", "You miss messages in a host request"),
                 ],
             ),
             (
@@ -96,6 +97,7 @@ def set_preference(session, user_id, topic_action: NotificationTopicAction, deli
                 "Messaging",
                 [
                     ("message", "Someone sends you a message"),
+                    ("missed_messages", "You miss messages in a chat"),
                 ],
             ),
             (
@@ -139,6 +141,13 @@ def set_preference(session, user_id, topic_action: NotificationTopicAction, deli
     (
         "Account Settings",
         [
+            (
+                "onboarding",
+                "Onboarding",
+                [
+                    ("reminder", "Reminder to complete your profile after signing up"),
+                ],
+            ),
             (
                 "password",
                 "Password change",
diff --git a/app/backend/src/couchers/servicers/auth.py b/app/backend/src/couchers/servicers/auth.py
index cbb9dc4c09..e0d55a7ffd 100644
--- a/app/backend/src/couchers/servicers/auth.py
+++ b/app/backend/src/couchers/servicers/auth.py
@@ -27,7 +27,6 @@
     enforce_community_memberships_for_user,
     maybe_send_contributor_form_email,
     send_login_email,
-    send_onboarding_email,
     send_signup_email,
 )
 from couchers.utils import (
@@ -320,7 +319,12 @@ def SignupFlow(self, request, context):
 
                 maybe_send_contributor_form_email(form)
 
-                send_onboarding_email(user, email_number=1)
+                # sends onboarding email
+                notify(
+                    user_id=user.id,
+                    topic_action="onboarding:reminder",
+                    key="1",
+                )
 
                 create_session(context, session, user, False)
                 return auth_pb2.SignupFlowRes(
diff --git a/app/backend/src/couchers/tasks.py b/app/backend/src/couchers/tasks.py
index 80ac0315da..7eb836bfb2 100644
--- a/app/backend/src/couchers/tasks.py
+++ b/app/backend/src/couchers/tasks.py
@@ -1,6 +1,5 @@
 import logging
 from datetime import timedelta
-from typing import List
 
 from sqlalchemy.sql import func
 
@@ -16,16 +15,31 @@
     EventCommunityInviteRequest,
     LoginToken,
     Node,
-    Notification,
     User,
 )
 from couchers.sql import couchers_select as select
-from couchers.templates.v2 import email_user
+from couchers.templates.v2 import send_simple_pretty_email
 from couchers.utils import now
 
 logger = logging.getLogger(__name__)
 
 
+def send_login_email(session, user):
+    # This is going away soon
+    login_token = LoginToken(token=urlsafe_secure_token(), user=user, expiry=now() + timedelta(hours=2))
+    session.add(login_token)
+
+    logger.info(f"Sending login email to {user=}:")
+    send_simple_pretty_email(
+        user.email,
+        "Your login link for Couchers.org",
+        "login_link",
+        template_args={"user": user, "login_link": urls.login_link(login_token=login_token.token)},
+    )
+
+    return login_token
+
+
 def send_signup_email(flow):
     logger.info(f"Sending signup email to {flow.email=}:")
 
@@ -47,26 +61,13 @@ def send_signup_email(flow):
 
     flow.email_sent = True
 
-    logger.info(f"Link is: {signup_link}")
-    template = "signup_verify" if not email_sent_before else "signup_continue"
-    email.enqueue_email_from_template(flow.email, template, template_args={"flow": flow, "signup_link": signup_link})
-
-
-def send_login_email(session, user):
-    login_token = LoginToken(token=urlsafe_secure_token(), user=user, expiry=now() + timedelta(hours=2))
-    session.add(login_token)
-
-    logger.info(f"Sending login email to {user=}:")
-    logger.info(f"Email for {user.username=} to {user.email=}")
-    logger.info(f"Token: {login_token=} ({login_token.created=}")
-    login_link = urls.login_link(login_token=login_token.token)
-    logger.info(f"Link is: {login_link}")
-    email.enqueue_email_from_template_to_user(
-        user, "login", template_args={"user": user, "login_link": login_link}, is_critical_email=True
+    send_simple_pretty_email(
+        flow.email,
+        "Finish signing up for Couchers.org",
+        "signup_verify" if not email_sent_before else "signup_continue",
+        template_args={"flow": flow, "signup_link": signup_link},
     )
 
-    return login_token
-
 
 def send_email_changed_confirmation_to_new_email(user):
     """
@@ -77,34 +78,18 @@ def send_email_changed_confirmation_to_new_email(user):
     )
 
     confirmation_link = urls.change_email_link(confirmation_token=user.new_email_token)
-    email_user(
-        user,
+    send_simple_pretty_email(
+        user.new_email,
+        "Confirm your new email for Couchers.org",
         "email_changed_confirmation_new_email",
         template_args={"user": user, "confirmation_link": confirmation_link},
-        override_recipient=user.new_email,
-    )
-
-
-def send_onboarding_email(user, email_number):
-    # todo(notify2): replace with notification
-    email.enqueue_email_from_template_to_user(
-        user,
-        f"onboarding{email_number}",
-        template_args={
-            "user": user,
-            "app_link": urls.app_link(),
-            "profile_link": urls.profile_link(),
-            "edit_profile_link": urls.edit_profile_link(),
-        },
     )
 
 
 def send_content_report_email(content_report):
-    target_email = config["REPORTS_EMAIL_RECIPIENT"]
-
-    logger.info(f"Sending content report email to {target_email=}")
-    email.enqueue_email_from_template(
-        target_email,
+    logger.info("Sending content report email")
+    email.enqueue_system_email(
+        config["REPORTS_EMAIL_RECIPIENT"],
         "content_report",
         template_args={
             "report": content_report,
@@ -115,12 +100,10 @@ def send_content_report_email(content_report):
 
 
 def maybe_send_reference_report_email(reference):
-    target_email = config["REPORTS_EMAIL_RECIPIENT"]
-
     if reference.should_report:
-        logger.info(f"Sending reference report email to {target_email=}")
-        email.enqueue_email_from_template(
-            target_email,
+        logger.info("Sending reference report email")
+        email.enqueue_system_email(
+            config["REPORTS_EMAIL_RECIPIENT"],
             "reference_report",
             template_args={
                 "reference": reference,
@@ -131,18 +114,16 @@ def maybe_send_reference_report_email(reference):
 
 
 def maybe_send_contributor_form_email(form):
-    target_email = config["CONTRIBUTOR_FORM_EMAIL_RECIPIENT"]
-
     if form.should_notify:
-        email.enqueue_email_from_template(
-            target_email,
+        email.enqueue_system_email(
+            config["CONTRIBUTOR_FORM_EMAIL_RECIPIENT"],
             "contributor_form",
             template_args={"form": form, "user_link": urls.user_link(username=form.user.username)},
         )
 
 
 def send_event_community_invite_request_email(request: EventCommunityInviteRequest):
-    email.enqueue_email_from_template(
+    email.enqueue_system_email(
         config["MODS_EMAIL_RECIPIENT"],
         "event_community_invite_request",
         template_args={
@@ -153,12 +134,14 @@ def send_event_community_invite_request_email(request: EventCommunityInviteReque
     )
 
 
-def send_digest_email(user, notifications: List[Notification]):
-    logger.info(f"Sending digest email to {user=}:")
-    email.enqueue_email_from_template_to_user(
-        user,
-        "digest",
-        template_args={"user": user, "notifications": notifications},
+def send_account_deletion_report_email(reason):
+    logger.info("Sending account deletion report email")
+    email.enqueue_system_email(
+        config["REPORTS_EMAIL_RECIPIENT"],
+        "account_deletion_report",
+        template_args={
+            "reason": reason,
+        },
     )
 
 
@@ -191,19 +174,6 @@ def enforce_community_memberships():
             session.commit()
 
 
-def send_account_deletion_report_email(reason):
-    target_email = config["REPORTS_EMAIL_RECIPIENT"]
-
-    logger.info(f"Sending account deletion report email to {target_email=}")
-    email.enqueue_email_from_template(
-        target_email,
-        "account_deletion_report",
-        template_args={
-            "reason": reason,
-        },
-    )
-
-
 def enforce_community_memberships_for_user(session, user):
     """
     Adds a given user to all the communities they belong in based on their location.
diff --git a/app/backend/src/couchers/templates/v2.py b/app/backend/src/couchers/templates/v2.py
index 519547f8fc..cb0da2dfea 100644
--- a/app/backend/src/couchers/templates/v2.py
+++ b/app/backend/src/couchers/templates/v2.py
@@ -9,13 +9,11 @@
 from zoneinfo import ZoneInfo
 
 import phonenumbers
-import yaml
 from jinja2 import Environment, FileSystemLoader
 
 from couchers import urls
 from couchers.config import config
 from couchers.email import queue_email
-from couchers.notifications.unsubscribe import generate_do_not_email
 from couchers.utils import get_tz_as_text, now, to_aware_datetime
 
 logger = logging.getLogger(__name__)
@@ -92,45 +90,29 @@ def add_filters(env):
 add_filters(env)
 
 
-def email_user(user, template_name, template_args=None, frontmatter=None, override_recipient=None):
-    template_args = template_args or {}
-    if not frontmatter:
-        # Titles/config are from {template_name}.yaml, plaintext from {template_name}.txt, and html from generated_html/{template_name}.html (generated from {template_name}.mjml)
-        frontmatter_template = env.get_template(f"{template_name}.yaml")
-        rendered_frontmatter = frontmatter_template.render(**template_args)
-        frontmatter = yaml.load(rendered_frontmatter, Loader=yaml.FullLoader)
-        assert "subject" in frontmatter
-        assert "is_critical" in frontmatter
-        assert "preview" in frontmatter
-
-    if not frontmatter["is_critical"]:
-        template_args["_footer_unsub_link"] = generate_do_not_email(user.id)
+def send_simple_pretty_email(recipient, subject, template_name, template_args):
+    """
+    This is a simplified version of couchers.notifications.background._send_email_notification
 
+    It's for the few security emails where we don't have a user to email but send directly to an email address.
+    """
     template_args["_year"] = now().year
-    template_args["_timezone_display"] = get_tz_as_text(user.timezone or "Etc/UTC")
-
-    plain_template = env.get_template(f"{template_name}.txt")
-    html_template = env.get_template(f"generated_html/{template_name}.html")
-
-    plain = plain_template.render({**template_args, "frontmatter": frontmatter})
-    html = html_template.render({**template_args, "frontmatter": frontmatter})
+    template_args["_timezone_display"] = get_tz_as_text("Etc/UTC")
 
-    if user.do_not_email and not frontmatter["is_critical"]:
-        logger.info(f"Not emailing {user} based on template {template_name} due to emails turned off")
-        return
+    plain_unsub_section = "\n\n---\n\nThis is a security email, you cannot unsubscribe from it."
+    html_unsub_section = "This is a security email, you cannot unsubscribe from it."
 
-    list_unsubscribe_header = None
-    if "list_unsubscribe_url" in frontmatter:
-        url = frontmatter["list_unsubscribe_url"]
-        list_unsubscribe_header = f"<{url}>"
+    plain_tmplt = (template_folder / f"{template_name}.txt").read_text()
+    plain = env.from_string(plain_tmplt + plain_unsub_section).render(template_args)
+    html_tmplt = (template_folder / "generated_html" / f"{template_name}.html").read_text()
+    html = env.from_string(html_tmplt.replace("___UNSUB_SECTION___", html_unsub_section)).render(template_args)
 
     queue_email(
         sender_name=config["NOTIFICATION_EMAIL_SENDER"],
         sender_email=config["NOTIFICATION_EMAIL_ADDRESS"],
-        recipient=user.email if not override_recipient else override_recipient,
-        subject=config["NOTIFICATION_EMAIL_PREFIX"] + frontmatter["subject"],
+        recipient=recipient,
+        subject=config["NOTIFICATION_EMAIL_PREFIX"] + subject,
         plain=plain,
         html=html,
         source_data=config["VERSION"] + f"/{template_name}",
-        list_unsubscribe_header=list_unsubscribe_header,
     )
diff --git a/app/backend/templates/account_deletion_report.md b/app/backend/templates/account_deletion_report.md
deleted file mode 100644
index f96dee1d0a..0000000000
--- a/app/backend/templates/account_deletion_report.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-subject: "Account deletion reason #{{ reason.id|couchers_escape }}"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Someone deleted their account and wrote a reason.
-
-
-* Reason{{ newline(html)|couchers_safe }}
-{{ reason.reason|couchers_escape }}
-
-
-* Deleted user{{ newline(html)|couchers_safe }}
-Name: {{ reason.user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ reason.user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ reason.user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-User ID: {{ reason.user.id|couchers_escape }}{{ newline(html)|couchers_safe }}
-
-
-* Time{{ newline(html)|couchers_safe }}
-{{ reason.created|couchers_escape }}
diff --git a/app/backend/templates/content_report.md b/app/backend/templates/content_report.md
deleted file mode 100644
index c200d7dbd6..0000000000
--- a/app/backend/templates/content_report.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-subject: "Content Report #{{ report.id|couchers_escape }}: {{ report.reason|couchers_escape }}"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Content Report #{{ report.id|couchers_escape }}.
-
-
-* Reason{{ newline(html)|couchers_safe }}
-{{ report.reason|couchers_escape }}
-
-
-* Description{{ newline(html)|couchers_safe }}
-{{ report.description|couchers_escape }}
-
-
-* Content reference{{ newline(html)|couchers_safe }}
-{{ report.content_ref|couchers_escape }}
-
-
-* User who reported content{{ newline(html)|couchers_safe }}
-Name: {{ report.reporting_user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ report.reporting_user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ report.reporting_user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-User ID: {{ report.reporting_user.id|couchers_escape }}{{ newline(html)|couchers_safe }}
-Profile: {{ link(reporting_user_user_link, html)|couchers_safe }}
-
-
-* User who authored content{{ newline(html)|couchers_safe }}
-Name: {{ report.author_user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ report.author_user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ report.author_user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-User ID: {{ report.author_user.id|couchers_escape }}{{ newline(html)|couchers_safe }}
-Profile: {{ link(author_user_user_link, html)|couchers_safe }}
-
-
-* Other info{{ newline(html)|couchers_safe }}
-Page: {{ report.page|couchers_escape }}{{ newline(html)|couchers_safe }}
-User-agent: {{ report.user_agent|couchers_escape }}{{ newline(html)|couchers_safe }}
-Reported at: {{ report.time|couchers_escape }}{{ newline(html)|couchers_safe }}
diff --git a/app/backend/templates/contributor_form.md b/app/backend/templates/contributor_form.md
deleted file mode 100644
index 24175150f8..0000000000
--- a/app/backend/templates/contributor_form.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-subject: "New sign up"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Name: {{ form.user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ form.user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ form.user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-Profile: {{ link(user_link, html)|couchers_safe }}
-
-Wants to contribute: {{ ("missing" if not form.contribute else form.contribute.name)|couchers_escape }}{{ newline(html)|couchers_safe }}
-Ways: {{ ("missing" if not form.contribute_ways else ", ".join(form.contribute_ways))|couchers_escape }}
-
-* Age{{ newline(html)|couchers_safe }}
-{{ form.user.age|couchers_escape }}
-
-* Gender{{ newline(html)|couchers_safe }}
-{{ form.user.gender|couchers_escape }}
-
-* Country and city{{ newline(html)|couchers_safe }}
-{{ form.user.city|couchers_escape }} (tz: {{ form.user.timezone|couchers_escape }})
-
-* What expertise do you have in this area(s)?{{ newline(html)|couchers_safe }}
-{{ (form.expertise or "missing")|couchers_escape }}
-
-* Briefly describe your experience as a couch surfer.{{ newline(html)|couchers_safe }}
-{{ (form.experience or "missing")|couchers_escape }}
-
-* What improvements on Couchsurfing™ would you like to see?{{ newline(html)|couchers_safe }}
-{{ (form.ideas or "missing")|couchers_escape }}
-
-* Which features are most important to you?{{ newline(html)|couchers_safe }}
-{{ (form.features or "missing")|couchers_escape }}{{ newline(html)|couchers_safe }}
diff --git a/app/backend/templates/digest.md b/app/backend/templates/digest.md
deleted file mode 100644
index 703e347b87..0000000000
--- a/app/backend/templates/digest.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-subject: "You missed {{ notifications|length }} notifications on Couchers.org"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-This is your Couchers.org digest of notifications you missed.
-
-{% for notification in notifications %}
-
-Title: {{ notification.title|couchers_escape }}
-Content: {{ notification.content|couchers_escape }}
-
-{% endfor %}
diff --git a/app/backend/templates/email_base_html.html b/app/backend/templates/email_base_html.html
deleted file mode 100644
index 88aa558010..0000000000
--- a/app/backend/templates/email_base_html.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <title>{{ frontmatter.subject|couchers_escape }}</title>
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-  </head>
-  <body>
-      {% if content %}
-      {{ content|couchers_safe }}
-      {% else %}
-      <p>Sorry, an unknown error occurred, please forward this email to <a href="mailto:bugs@couchers.org">bugs@couchers.org</a> so we can investigate further.</p>
-      {% endif %}
-      <hr />
-      <p>You're receiving this email because you signed up for Couchers.org. If you have any issues, email us at <a href="mailto:support@couchers.org">support@couchers.org</a>.</p>
-      {% if _footer_unsub_link %}
-      <p>If you wish to stop receiving emails from Couchers, click on this link: <a href="{{ _footer_unsub_link|couchers_safe }}">unsubscribe all</a>.</p>
-      {% endif %}
-  </body>
-</html>
diff --git a/app/backend/templates/email_base_plain.md b/app/backend/templates/email_base_plain.md
deleted file mode 100644
index 4e640057e9..0000000000
--- a/app/backend/templates/email_base_plain.md
+++ /dev/null
@@ -1,13 +0,0 @@
-{% if content %}
-{{ content|couchers_safe }}
-{% else %}
-Sorry, an unknown error occurred, please forward this email to <bugs@couchers.org> so we can investigate further.
-{% endif %}
-
----
-
-You're receiving this email because you signed up for Couchers.org. If you have any issues, email us at <support@couchers.org>.
-
-{% if _footer_unsub_link %}
-If you wish to stop receiving emails from Couchers, click on this link: <{{ _footer_unsub_link|couchers_safe }}>
-{% endif %}
diff --git a/app/backend/templates/event_community_invite_request.md b/app/backend/templates/event_community_invite_request.md
deleted file mode 100644
index 459bbc7965..0000000000
--- a/app/backend/templates/event_community_invite_request.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-subject: "New request for a community invite"
----
-
-{% from "macros.html" import link %}
-
-Profile: {{ link(user_link, html)|couchers_safe }}
-
-Event Link: {{ link(event_link, html)|couchers_safe }}
-
-Approve/deny here: {{ link(view_link, html)|couchers_safe }}
diff --git a/app/backend/templates/login.md b/app/backend/templates/login.md
deleted file mode 100644
index 05e46b6f75..0000000000
--- a/app/backend/templates/login.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-subject: "Your login link for Couchers.org"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-Hi {{ user.name|couchers_escape }}!
-
-Here's a login link for Couchers.org:
-
-{% if html %}
-
-{{ button("Sign in", login_link)|couchers_safe }}
-
-Alternatively, click the following link: {{ link(login_link, html)|couchers_safe }}
-
-{% else %}
-
-<{{ login_link|couchers_safe }}>
-
-{% endif %}
-
-See you in a bit :)
-
-The Couchers.org team
diff --git a/app/backend/templates/macros.html b/app/backend/templates/macros.html
deleted file mode 100644
index 777bdc2f23..0000000000
--- a/app/backend/templates/macros.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% macro button(text, link) -%}
-<a style="background-color: #00a398; border-radius: 5px; margin: 20px; padding: 10px 20px; color: #ffffff; text-decoration: none; box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);" href="{{ link|couchers_safe }}">{{ text|couchers_escape }}</a>
-{%- endmacro %}
-
-{% macro link(link, html) -%}
-{%if html %}<a href="{{ link|couchers_safe }}">{{ link|couchers_safe }}</a>{% else %}<{{ link|couchers_safe }}>{% endif %}
-{%- endmacro %}
-
-{% macro email_link(address, html) -%}
-{% if html %}<a href="mailto:{{ address|couchers_safe }}">{{ address|couchers_safe }}</a>{% else %}<{{ address|couchers_safe }}>{% endif %}
-{%- endmacro %}
-
-{% macro support_email(html) -%}
-{{ email_link("support@couchers.org", html)|couchers_safe }}
-{%- endmacro %}
-
-{% macro newline(html) -%}
-{% if html %}<br />{% endif %}
-{%- endmacro %}
diff --git a/app/backend/templates/notification.md b/app/backend/templates/notification.md
deleted file mode 100644
index db5538a553..0000000000
--- a/app/backend/templates/notification.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-subject: "{{ notification.plain_title|couchers_escape }}"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ notification.user.name|couchers_escape }}!
-
-You missed a notification on Couchers.org.
-
-Title: {{ notification.title|couchers_escape }}
-
-Content: {{ notification.content|couchers_escape }}
-
-To unsubscribe from this item click on this link: {{ link(unsub_topic_key, html)|couchers_safe }}
-
-To unsubscribe from these types of notifications click on this link: {{ link(unsub_topic_action, html)|couchers_safe }}
diff --git a/app/backend/templates/onboarding1.md b/app/backend/templates/onboarding1.md
deleted file mode 100644
index 5365a3d509..0000000000
--- a/app/backend/templates/onboarding1.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-subject: "Welcome to Couchers.org and the future of couch surfing"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-Welcome to the growing online community of people who have joined the Couchers.org project. It's great to have you here!
-
-As one of the first users, you have a vital role in shaping the future of the platform. You can help us test the functionality, provide feedback, and set the scene and culture as we grow bigger.
-
-Please take some time to explore the platform. As a first step, please head to {{ link(edit_profile_link, html)|couchers_safe }} and edit your profile. Please upload a photo and fill in some profile text, you can even copy your description from your account on other platforms if you're feeling lazy :P
-
-{% if html %}
-
-{{ button("Edit your profile", edit_profile_link)|couchers_safe }}
-
-{% endif %}
-
-Otherwise, please share the link with any of your couch surfing friends that you trust! This platform can only grow with your help bringing the best people over to start it.
-
-Link: {{ link(app_link, html)|couchers_safe }}
-
-The platform is under rapid development, and features are actively being built by our amazing team of Couchers.org contributors around the world. If there's some features you want to see like events, forums, hangouts or public trips, you'll be happy to hear that we're going to build them all, and you'll see some features come out very soon! As you explore the platform, please report any bugs that you see by hitting the "Report a problem" button in the top right corner.
-
-You can also head on over to our forum at {{ link("https://community.couchers.org", html)|couchers_safe }} to discuss features, ideas, or anything else to do with Couchers.org or couch surfing in general. From there you can also join our regular online events if you'd like to meet other passionate couch surfers who are part of this incredible new community!
-
-{% if html %}
-
-{{ button("Visit the forum", "https://community.couchers.org")|couchers_safe }}
-
-{% endif %}
-
-Thanks so much for joining, especially this early. We're really excited to make something great for all of us to use!
-
-
-Best,
-
-Itsi from Couchers.org{{ newline(html)|couchers_safe }}
-Co-founder and Community Team Lead{{ newline(html)|couchers_safe }}
-{{ email_link("itsi@couchers.org", html)|couchers_safe }} (always feel free to shoot me an email)
diff --git a/app/backend/templates/onboarding2.md b/app/backend/templates/onboarding2.md
deleted file mode 100644
index 6aecd93129..0000000000
--- a/app/backend/templates/onboarding2.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-subject: "Complete your profile on Couchers.org"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-We hope you've taken a bit of time to look around the new Couchers.org platform.
-
-We would ask one big favour of you: please fill out your profile by adding a photo and some text.
-
-{% if html %}
-
-{{ button("Go to edit your profile", edit_profile_link)|couchers_safe }}
-
-{% else %}
-
-Link to profile editing page: {{ link(edit_profile_link, html)|couchers_safe }}
-
-{% endif %}
-
-A big problem that platforms like Couchsurfing have is that there are so many empty accounts, and we want to avoid that, especially at this early stage.
-
-We would really appreciate it if you could add a photo and fill in at least the "Who I am" section of your profile, although a full profile would be even better! It's an easy way to make this new platform look great, and it'll mean you're set up for when people start using the platform to start couch surfing.
-
-
-Thank you so much!
-
-Emily from Couchers.org{{ newline(html)|couchers_safe }}
-{{ email_link("community@couchers.org", html)|couchers_safe }}
diff --git a/app/backend/templates/reference_report.md b/app/backend/templates/reference_report.md
deleted file mode 100644
index e568e6aa63..0000000000
--- a/app/backend/templates/reference_report.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-subject: "Reference report about ref #{{ reference.id|couchers_escape }}"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Someone wrote a bad reference.
-
-
-* Rating{{ newline(html)|couchers_safe }}
-{{ reference.rating|couchers_escape }}
-
-
-* Was appropriate?{{ newline(html)|couchers_safe }}
-{{ reference.was_appropriate|couchers_escape }}
-
-
-* Reference type{{ newline(html)|couchers_safe }}
-{{ reference.reference_type|couchers_escape }}
-
-
-* Reference text{{ newline(html)|couchers_safe }}
-{{ reference.text|couchers_escape }}
-
-
-* User who wrote the reference{{ newline(html)|couchers_safe }}
-Name: {{ reference.from_user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ reference.from_user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ reference.from_user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-User ID: {{ reference.from_user.id|couchers_escape }}{{ newline(html)|couchers_safe }}
-Profile: {{ link(from_user_user_link, html)|couchers_safe }}
-
-
-* User who the reference is about{{ newline(html)|couchers_safe }}
-Name: {{ reference.to_user.name|couchers_escape }}{{ newline(html)|couchers_safe }}
-Email: {{ reference.to_user.email|couchers_escape }}{{ newline(html)|couchers_safe }}
-Username: {{ reference.to_user.username|couchers_escape }}{{ newline(html)|couchers_safe }}
-User ID: {{ reference.to_user.id|couchers_escape }}{{ newline(html)|couchers_safe }}
-Profile: {{ link(to_user_user_link, html)|couchers_safe }}
-
-
-* Time{{ newline(html)|couchers_safe }}
-{{ reference.time|couchers_escape }}
diff --git a/app/backend/templates/signup_continue.md b/app/backend/templates/signup_continue.md
deleted file mode 100644
index 84a3162e6f..0000000000
--- a/app/backend/templates/signup_continue.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-subject: "Finish signing up for Couchers.org"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-Hi {{ flow.name|couchers_escape }}!
-
-Please finish signing up for Couchers.org.
-
-{% if html %}
-
-{{ button("Continue signing up", signup_link)|couchers_safe }}
-
-Please click on the above button to continue, or click the following link: {{ link(signup_link, html)|couchers_safe }}
-
-{% else %}
-
-Please click on this link to continue: <{{ signup_link|couchers_safe }}>.
-
-{% endif %}
-
-See you in a bit :)
-
-The Couchers.org team
-
-If you did not request a signup, someone else did with your email, and you can safely ignore this message.
diff --git a/app/backend/templates/signup_verify.md b/app/backend/templates/signup_verify.md
deleted file mode 100644
index 87c55e4f8c..0000000000
--- a/app/backend/templates/signup_verify.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-subject: "Finish signing up for Couchers.org"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-Hi {{ flow.name|couchers_escape }}!
-
-Thanks for signing up for Couchers.org!
-
-{% if html %}
-
-{{ button("Confirm your email address", signup_link)|couchers_safe }}
-
-To finish setting up your account, please click on the above button, or click the following link: {{ link(signup_link, html)|couchers_safe }}
-
-{% else %}
-
-To finish setting up your account, please click on this link to confirm your email address: <{{ signup_link|couchers_safe }}>.
-
-{% endif %}
-
-See you in a bit :)
-
-The Couchers.org team
diff --git a/app/backend/templates/system/account_deletion_report.md b/app/backend/templates/system/account_deletion_report.md
new file mode 100644
index 0000000000..ff9ce7ffb9
--- /dev/null
+++ b/app/backend/templates/system/account_deletion_report.md
@@ -0,0 +1,20 @@
+---
+subject: "Account deletion reason #{{ reason.id }}"
+---
+
+Someone deleted their account and wrote a reason.
+
+
+* Reason
+{{ reason.reason }}
+
+
+* Deleted user
+Name: {{ reason.user.name }}
+Email: {{ reason.user.email }}
+Username: {{ reason.user.username }}
+User ID: {{ reason.user.id }}
+
+
+* Time
+{{ reason.created }}
diff --git a/app/backend/templates/system/content_report.md b/app/backend/templates/system/content_report.md
new file mode 100644
index 0000000000..8870125b4d
--- /dev/null
+++ b/app/backend/templates/system/content_report.md
@@ -0,0 +1,39 @@
+---
+subject: "Content Report #{{ report.id }}: {{ report.reason }}"
+---
+
+Content Report #{{ report.id }}.
+
+
+* Reason
+{{ report.reason }}
+
+
+* Description
+{{ report.description }}
+
+
+* Content reference
+{{ report.content_ref }}
+
+
+* User who reported content
+Name: {{ report.reporting_user.name }}
+Email: {{ report.reporting_user.email }}
+Username: {{ report.reporting_user.username }}
+User ID: {{ report.reporting_user.id }}
+Profile: <{{ reporting_user_user_link }}
+
+
+* User who authored content
+Name: {{ report.author_user.name }}
+Email: {{ report.author_user.email }}
+Username: {{ report.author_user.username }}
+User ID: {{ report.author_user.id }}
+Profile: <{{ author_user_user_link }}
+
+
+* Other info
+Page: {{ report.page }}
+User-agent: {{ report.user_agent }}
+Reported at: {{ report.time }}
diff --git a/app/backend/templates/system/contributor_form.md b/app/backend/templates/system/contributor_form.md
new file mode 100644
index 0000000000..78990c8751
--- /dev/null
+++ b/app/backend/templates/system/contributor_form.md
@@ -0,0 +1,32 @@
+---
+subject: "New sign up"
+---
+
+Name: {{ form.user.name }}
+Email: {{ form.user.email }}
+Username: {{ form.user.username }}
+Profile: <{{ user_link }}>
+
+Wants to contribute: {{ ("missing" if not form.contribute else form.contribute.name) }}
+Ways: {{ ("missing" if not form.contribute_ways else ", ".join(form.contribute_ways)) }}
+
+* Age
+{{ form.user.age }}
+
+* Gender
+{{ form.user.gender }}
+
+* Country and city
+{{ form.user.city }} (tz: {{ form.user.timezone }})
+
+* What expertise do you have in this area(s)?
+{{ (form.expertise or "missing") }}
+
+* Briefly describe your experience as a couch surfer.
+{{ (form.experience or "missing") }}
+
+* What improvements on Couchsurfing™ would you like to see?
+{{ (form.ideas or "missing") }}
+
+* Which features are most important to you?
+{{ (form.features or "missing") }}
diff --git a/app/backend/templates/system/event_community_invite_request.md b/app/backend/templates/system/event_community_invite_request.md
new file mode 100644
index 0000000000..cd39b4b017
--- /dev/null
+++ b/app/backend/templates/system/event_community_invite_request.md
@@ -0,0 +1,9 @@
+---
+subject: "New request for a community invite"
+---
+
+Profile: <{{ user_link }}>
+
+Event Link: <{{ event_link }}>
+
+Approve/deny here: <{{ view_link }}>
diff --git a/app/backend/templates/system/reference_report.md b/app/backend/templates/system/reference_report.md
new file mode 100644
index 0000000000..60c47c43c1
--- /dev/null
+++ b/app/backend/templates/system/reference_report.md
@@ -0,0 +1,41 @@
+---
+subject: "Reference report about ref #{{ reference.id }}"
+---
+
+Someone wrote a bad reference.
+
+
+* Rating
+{{ reference.rating }}
+
+
+* Was appropriate?
+{{ reference.was_appropriate }}
+
+
+* Reference type
+{{ reference.reference_type }}
+
+
+* Reference text
+{{ reference.text }}
+
+
+* User who wrote the reference
+Name: {{ reference.from_user.name }}
+Email: {{ reference.from_user.email }}
+Username: {{ reference.from_user.username }}
+User ID: {{ reference.from_user.id }}
+Profile: <{{ from_user_user_link }}>
+
+
+* User who the reference is about
+Name: {{ reference.to_user.name }}
+Email: {{ reference.to_user.email }}
+Username: {{ reference.to_user.username }}
+User ID: {{ reference.to_user.id }}
+Profile: <{{ to_user_user_link }}>
+
+
+* Time
+{{ reference.time }}
diff --git a/app/backend/templates/unseen_message_guest.md b/app/backend/templates/unseen_message_guest.md
deleted file mode 100644
index 26a4b8a09e..0000000000
--- a/app/backend/templates/unseen_message_guest.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-subject: "{{ host_request.host.name|couchers_escape }} wrote a message in your hosting request"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-You have an unseen message from {{ host_request.host.name|couchers_escape }} regarding your hosting request from {{ host_request.from_date|couchers_escape }} until {{ host_request.to_date|couchers_escape }}.
-
-Check it out here:
-
-{% if html %}
-
-{{ button("Host Requests", host_request_link)|couchers_safe }}
-
-Alternatively, click the following link: {{ link(host_request_link, html)|couchers_safe }}
-
-{% else %}
-<{{ host_request_link|couchers_safe }}>
-{% endif %}
-
-The Couchers.org team
diff --git a/app/backend/templates/unseen_message_host.md b/app/backend/templates/unseen_message_host.md
deleted file mode 100644
index 515a6fd3fa..0000000000
--- a/app/backend/templates/unseen_message_host.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-subject: "{{ host_request.surfer.name|couchers_escape }} wrote a message in their hosting request"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-You have an unseen message from {{ host_request.surfer.name|couchers_escape }} regarding their hosting request from {{ host_request.from_date|couchers_escape }} until {{ host_request.to_date|couchers_escape }}.
-
-Check it out here:
-
-{% if html %}
-
-{{ button("Host Requests", host_request_link)|couchers_safe }}
-
-Alternatively, click the following link: {{ link(host_request_link, html)|couchers_safe }}
-
-{% else %}
-<{{ host_request_link|couchers_safe }}>
-{% endif %}
-
-The Couchers.org team
diff --git a/app/backend/templates/unseen_messages.md b/app/backend/templates/unseen_messages.md
deleted file mode 100644
index d88bdc0917..0000000000
--- a/app/backend/templates/unseen_messages.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-subject: "You have {{ total_unseen_message_count|couchers_escape }} unseen messages in {{ unseen_messages|length|couchers_escape }} chats on Couchers.org!"
----
-
-{% from "macros.html" import button, link, support_email, email_link, newline %}
-
-Hi {{ user.name|couchers_escape }}!
-
-You have unseen messages on Couchers.org, here's the latest:
-
-{% for group_chat, latest_message, count in unseen_messages[:5] %}
-{% if group_chat.title %}
-"{{ group_chat.title|couchers_escape }}": {{ latest_message.author.name|couchers_escape }} wrote on {{ latest_message.time|couchers_escape }}: "{{ latest_message.text|couchers_escape }}" ({{ (count - 1)|couchers_escape }} more messages)
-{% else %}
-Private message from {{ latest_message.author.name|couchers_escape }} on {{ latest_message.time|couchers_escape }}: "{{ latest_message.text|couchers_escape }}" ({{ (count - 1)|couchers_escape }} more messages)
-{% endif %}
-
-{% endfor %}
-
-{% if unseen_messages|length > 5 %}
-... and {{ (total_unseen_message_count - 5)|couchers_escape }} more messages in {{ (unseen_messages|length - 5)|couchers_escape }} other chats!
-{% endif %}
-
-Check it out here:
-
-{% if html %}
-
-{{ button("See Messages", group_chats_link)|couchers_safe }}
-
-Alternatively, click the following link: {{ link(group_chats_link, html)|couchers_safe }}
-
-{% else %}
-<{{ group_chats_link|couchers_safe }}>
-{% endif %}
-
-The Couchers.org team
diff --git a/app/backend/templates/v2/chat_unseen_messages.mjml b/app/backend/templates/v2/chat_unseen_messages.mjml
new file mode 100644
index 0000000000..1ce810437e
--- /dev/null
+++ b/app/backend/templates/v2/chat_unseen_messages.mjml
@@ -0,0 +1,38 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
+    <mj-text>You have unseen messages on Couchers.org, here's the latest:</mj-text>
+  </mj-column>
+</mj-section>
+<!-- {% for item in items %} -->
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text><b>{{ item.message|v2esc }}</b>.</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px" text-align="left">
+  <mj-column width="120px">
+    <mj-image border-radius="200px" src="{{ item.author|v2avatar }}" />
+  </mj-column>
+  <mj-column width="440px">
+    <mj-text padding="15px 0px 0px 0px" line-height="20px"><b>{{ item.author.name|v2esc }}</b>, {{ item.author.age|v2esc }}<br />{{ item.author.city|v2esc }}</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="10px 20px" text-align="left">
+  <mj-column background-color="#eee" border-radius="10px" padding="10px 0px">
+    <mj-text>{{ item.text|v2esc|v2multiline }}</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ item.view_link|v2url }}">
+      <b>View this message</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<!-- {% endfor %} -->
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text><b>Do not reply to this email.</b> Use one of the buttons above, or log in to your account to reply to this request.</mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/chat_unseen_messages.txt b/app/backend/templates/v2/chat_unseen_messages.txt
new file mode 100644
index 0000000000..386d895de6
--- /dev/null
+++ b/app/backend/templates/v2/chat_unseen_messages.txt
@@ -0,0 +1,19 @@
+Hi {{ user.name }},
+
+You have unseen messages on Couchers.org, here's the latest:
+
+---
+
+{% for item in items %}
+
+{{ item.message }}.
+
+{{ item.text|v2quote }}
+
+View this message: <{{ item.view_link }}>
+
+---
+{% endfor %}
+
+
+Do not reply to this email. Log in to your account to reply.
diff --git a/app/backend/templates/v2/email_changed_confirmation_new_email.yaml b/app/backend/templates/v2/email_changed_confirmation_new_email.yaml
deleted file mode 100644
index 0bf4833fe8..0000000000
--- a/app/backend/templates/v2/email_changed_confirmation_new_email.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-subject: Confirm your new email for Couchers.org
-is_critical: true
-preview: Confirm your new email address for Couchers.org
diff --git a/app/backend/templates/v2/email_changed_confirmation_old_email.mjml b/app/backend/templates/v2/email_changed_confirmation_old_email.mjml
deleted file mode 100644
index a2e8feab90..0000000000
--- a/app/backend/templates/v2/email_changed_confirmation_old_email.mjml
+++ /dev/null
@@ -1,22 +0,0 @@
-<mj-section padding="0px">
-  <mj-column>
-    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
-    <mj-text>You requested that your email be changed on Couchers.org.</mj-text>
-    <mj-text>The new email address is <code>{{ user.new_email|v2esc }}</code>.</mj-text>
-    <mj-text>Please confirm you requested this change by clicking the following link:</mj-text>
-  </mj-column>
-</mj-section>
-<mj-section padding="0px">
-  <mj-column>
-    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ confirmation_link|v2url }}">
-      <b>Confirm email change</b>
-    </mj-button>
-  </mj-column>
-</mj-section>
-<mj-section padding="0px">
-  <mj-column>
-    <mj-text>If you haven't already confirmed this change via your new email address, you will still need a final confirmation via a similar email sent to that address.</mj-text>
-    <mj-text>If this wasn't you, please contact us by emailing <a href="mailto:support@couchers.org">support@couchers.org</a> so we can sort this out as soon as possible!</mj-text>
-    <mj-text>Best,<br />The Couchers.org Team</mj-text>
-  </mj-column>
-</mj-section>
diff --git a/app/backend/templates/v2/email_changed_confirmation_old_email.txt b/app/backend/templates/v2/email_changed_confirmation_old_email.txt
deleted file mode 100644
index 467db6c91f..0000000000
--- a/app/backend/templates/v2/email_changed_confirmation_old_email.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-Hi {{ user.name }}!
-
-You requested that your email be changed on Couchers.org.
-
-The new email address is {{ user.new_email }}.
-
-Please confirm you requested this change by clicking the following link:
-
-<{{ confirmation_link }}>
-
-If you haven't already confirmed this change via your new email address, you will still need a final confirmation via a similar email sent to that address.
-
-If this wasn't you, please contact us by emailing <support@couchers.org> so we can sort this out as soon as possible!
-
-Best,
-The Couchers.org team
diff --git a/app/backend/templates/v2/email_changed_confirmation_old_email.yaml b/app/backend/templates/v2/email_changed_confirmation_old_email.yaml
deleted file mode 100644
index e52507586d..0000000000
--- a/app/backend/templates/v2/email_changed_confirmation_old_email.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-subject: Confirm your email change for Couchers.org
-is_critical: true
-preview: Confirm your intention to change your email address on Couchers.org
diff --git a/app/backend/templates/v2/email_changed_notification.mjml b/app/backend/templates/v2/email_changed_notification.mjml
deleted file mode 100644
index 53c3a97f8d..0000000000
--- a/app/backend/templates/v2/email_changed_notification.mjml
+++ /dev/null
@@ -1,9 +0,0 @@
-<mj-section padding="0px">
-  <mj-column>
-    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
-    <mj-text>You requested that your email on Couchers.org be changed to: <code>{{ user.new_email|v2esc }}</code>.</mj-text>
-    <mj-text>A confirmation was sent to that email address, and you'll need to confirm it to complete the change.</mj-text>
-    <mj-text>If this wasn't you, please contact us by emailing <a href="mailto:support@couchers.org">support@couchers.org</a> so we can sort this out as soon as possible!</mj-text>
-    <mj-text>Best,<br />The Couchers.org Team</mj-text>
-  </mj-column>
-</mj-section>
diff --git a/app/backend/templates/v2/email_changed_notification.txt b/app/backend/templates/v2/email_changed_notification.txt
deleted file mode 100644
index d85a244f08..0000000000
--- a/app/backend/templates/v2/email_changed_notification.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Hi {{ user.name }}!
-
-You requested that your email on Couchers.org be changed to:
-
-{{ user.new_email }}
-
-A confirmation was sent to that email address, and you'll need to confirm it to complete the change.
-
-If this wasn't you, please contact us by emailing <support@couchers.org> so we can sort this out as soon as possible!
-
-Best,
-The Couchers.org team
diff --git a/app/backend/templates/v2/email_changed_notification.yaml b/app/backend/templates/v2/email_changed_notification.yaml
deleted file mode 100644
index 738af39d31..0000000000
--- a/app/backend/templates/v2/email_changed_notification.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-subject: Couchers.org email change requested
-is_critical: true
-preview: Someone initiated an email change for your Couchers.org account
diff --git a/app/backend/templates/v2/generated_html/chat_unseen_messages.html b/app/backend/templates/v2/generated_html/chat_unseen_messages.html
new file mode 100644
index 0000000000..e8d71ce31d
--- /dev/null
+++ b/app/backend/templates/v2/generated_html/chat_unseen_messages.html
@@ -0,0 +1,465 @@
+<!doctype html>
+<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
+
+<head>
+  <title>{{ subject }}</title>
+  <!--[if !mso]><!-->
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!--<![endif]-->
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    #outlook a {
+      padding: 0;
+    }
+
+    body {
+      margin: 0;
+      padding: 0;
+      -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+    }
+
+    table,
+    td {
+      border-collapse: collapse;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+    }
+
+    img {
+      border: 0;
+      height: auto;
+      line-height: 100%;
+      outline: none;
+      text-decoration: none;
+      -ms-interpolation-mode: bicubic;
+    }
+
+    p {
+      display: block;
+      margin: 13px 0;
+    }
+
+  </style>
+  <!--[if mso]>
+    <noscript>
+    <xml>
+    <o:OfficeDocumentSettings>
+      <o:AllowPNG/>
+      <o:PixelsPerInch>96</o:PixelsPerInch>
+    </o:OfficeDocumentSettings>
+    </xml>
+    </noscript>
+    <![endif]-->
+  <!--[if lte mso 11]>
+    <style type="text/css">
+      .mj-outlook-group-fix { width:100% !important; }
+    </style>
+    <![endif]-->
+  <!--[if !mso]><!-->
+  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
+  <style type="text/css">
+    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
+
+  </style>
+  <!--<![endif]-->
+  <style type="text/css">
+    @media only screen and (min-width:480px) {
+      .mj-column-per-100 {
+        width: 100% !important;
+        max-width: 100%;
+      }
+
+      .mj-column-px-120 {
+        width: 120px !important;
+        max-width: 120px;
+      }
+
+      .mj-column-px-440 {
+        width: 440px !important;
+        max-width: 440px;
+      }
+    }
+
+  </style>
+  <style media="screen and (min-width:480px)">
+    .moz-text-html .mj-column-per-100 {
+      width: 100% !important;
+      max-width: 100%;
+    }
+
+    .moz-text-html .mj-column-px-120 {
+      width: 120px !important;
+      max-width: 120px;
+    }
+
+    .moz-text-html .mj-column-px-440 {
+      width: 440px !important;
+      max-width: 440px;
+    }
+
+  </style>
+  <style type="text/css">
+    @media only screen and (max-width:479px) {
+      table.mj-full-width-mobile {
+        width: 100% !important;
+      }
+
+      td.mj-full-width-mobile {
+        width: auto !important;
+      }
+    }
+
+  </style>
+</head>
+
+<body style="word-spacing:normal;background-color:#eeeeee;">
+  <div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">{{ preview }}</div>
+  <div style="background-color:#eeeeee;" lang="und" dir="auto">
+    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="body-outlook" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div class="body" style="background:#ffffff;background-color:#ffffff;margin:0px auto;border-radius:5px;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;border-radius:5px;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                                    <tbody>
+                                      <tr>
+                                        <td style="width:300px;">
+                                          <img alt="Couchers.org" src="attachment_imgs/logo-with-couchers.org-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="300" height="auto">
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Hi {{ user.name|v2esc }},</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">You have unseen messages on Couchers.org, here's the latest:</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><![endif]-->
+              <!-- {% for item in items %} -->
+              <!--[if mso | IE]><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;"><b>{{ item.message|v2esc }}</b>.</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:left;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:120px;" ><![endif]-->
+                        <div class="mj-column-px-120 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                                    <tbody>
+                                      <tr>
+                                        <td style="width:70px;">
+                                          <img alt src="{{ item.author|v2avatar }}" style="border:0;border-radius:200px;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="70" height="auto">
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td><td class="" style="vertical-align:top;width:440px;" ><![endif]-->
+                        <div class="mj-column-px-440 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:15px 0px 0px 0px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:20px;text-align:left;color:#333333;"><b>{{ item.author.name|v2esc }}</b>, {{ item.author.age|v2esc }}<br>{{ item.author.city|v2esc }}</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:10px 20px;text-align:left;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:520px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
+                            <tbody>
+                              <tr>
+                                <td style="background-color:#eeeeee;border-radius:10px;vertical-align:top;padding:10px 0px;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style width="100%">
+                                    <tbody>
+                                      <tr>
+                                        <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                          <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">{{ item.text|v2esc|v2multiline }}</div>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
+                                          <a href="{{ item.view_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>View this message</b>
+                                          </a>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><![endif]-->
+              <!-- {% endfor %} -->
+              <!--[if mso | IE]><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;"><b>Do not reply to this email.</b> Use one of the buttons above, or log in to your account to reply to this request.</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:12px;text-align:center;color:#333333;">You're receiving this email because you signed up for Couchers.org.<br> You can reach our support team at <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a>.<br> All times are in {{ _timezone_display|v2sf }}, based on your profile location.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:1;text-align:center;color:#333333;"><a href="https://community.couchers.org/" style="color: #00a398;">Forum</a> | <a href="https://couchers.org/donate" style="color: #00a398;">Donate</a> | <a href="https://github.com/Couchers-org/couchers" style="color: #00a398;">GitHub</a> | <a href="https://couchers.org/volunteer" style="color: #00a398;">Volunteer</a> | <a href="https://couchers.org/blog" style="color: #00a398;">Blog</a></div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:10px;text-align:center;color:#333333;">&copy; {{ _year|v2sf }} Couchers, Inc.<br> 3120 Winkler Ave, Unit 9. Fort Myers, FL 33916, USA.<br>
+                                    <i>Couchers.org is a project of Couchers, Inc. a U.S. 501(c)(3) non-profit, tax-exempt organization.</i>
+                                  </div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                  <tbody>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:16px;text-align:center;color:#777777;">___UNSUB_SECTION___</div>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                          <tbody>
+                            <tr>
+                              <td style="width:50px;">
+                                <img alt src="attachment_imgs/logo-grey.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="50" height="auto">
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:16px;text-align:center;color:#777777;">The happy Couchers couch hopes you have a great rest of the day.</div>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><![endif]-->
+  </div>
+</body>
+
+</html>
diff --git a/app/backend/templates/v2/generated_html/host_request__plain.html b/app/backend/templates/v2/generated_html/host_request__plain.html
index 11521edd12..9791ca2351 100644
--- a/app/backend/templates/v2/generated_html/host_request__plain.html
+++ b/app/backend/templates/v2/generated_html/host_request__plain.html
@@ -294,7 +294,7 @@
                             <tbody>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;"><b>Do not reply to this email.</b> Use one of the buttons above, or log in to your account to reply to this request.</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;"><b>Do not reply to this email.</b> Use the buttons above, or log in to your account to reply to this request.</div>
                                 </td>
                               </tr>
                             </tbody>
diff --git a/app/backend/templates/v2/generated_html/email_changed_notification.html b/app/backend/templates/v2/generated_html/login_link.html
similarity index 80%
rename from app/backend/templates/v2/generated_html/email_changed_notification.html
rename to app/backend/templates/v2/generated_html/login_link.html
index a49eeab4d8..69319287dd 100644
--- a/app/backend/templates/v2/generated_html/email_changed_notification.html
+++ b/app/backend/templates/v2/generated_html/login_link.html
@@ -172,17 +172,65 @@
                               </tr>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">You requested that your email on Couchers.org be changed to: <code>{{ user.new_email|v2esc }}</code>.</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Here's a login link for Couchers.org:</div>
                                 </td>
                               </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
                               <tr>
-                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">A confirmation was sent to that email address, and you'll need to confirm it to complete the change.</div>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
+                                          <a href="{{ login_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>Log in</b>
+                                          </a>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
                                 </td>
                               </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">If this wasn't you, please contact us by emailing <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a> so we can sort this out as soon as possible!</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">See you in a bit :)</div>
                                 </td>
                               </tr>
                               <tr>
diff --git a/app/backend/templates/v2/generated_html/onboarding1.html b/app/backend/templates/v2/generated_html/onboarding1.html
new file mode 100644
index 0000000000..cdedf05bf1
--- /dev/null
+++ b/app/backend/templates/v2/generated_html/onboarding1.html
@@ -0,0 +1,377 @@
+<!doctype html>
+<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
+
+<head>
+  <title>{{ subject }}</title>
+  <!--[if !mso]><!-->
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!--<![endif]-->
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    #outlook a {
+      padding: 0;
+    }
+
+    body {
+      margin: 0;
+      padding: 0;
+      -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+    }
+
+    table,
+    td {
+      border-collapse: collapse;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+    }
+
+    img {
+      border: 0;
+      height: auto;
+      line-height: 100%;
+      outline: none;
+      text-decoration: none;
+      -ms-interpolation-mode: bicubic;
+    }
+
+    p {
+      display: block;
+      margin: 13px 0;
+    }
+
+  </style>
+  <!--[if mso]>
+    <noscript>
+    <xml>
+    <o:OfficeDocumentSettings>
+      <o:AllowPNG/>
+      <o:PixelsPerInch>96</o:PixelsPerInch>
+    </o:OfficeDocumentSettings>
+    </xml>
+    </noscript>
+    <![endif]-->
+  <!--[if lte mso 11]>
+    <style type="text/css">
+      .mj-outlook-group-fix { width:100% !important; }
+    </style>
+    <![endif]-->
+  <!--[if !mso]><!-->
+  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
+  <style type="text/css">
+    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
+
+  </style>
+  <!--<![endif]-->
+  <style type="text/css">
+    @media only screen and (min-width:480px) {
+      .mj-column-per-100 {
+        width: 100% !important;
+        max-width: 100%;
+      }
+    }
+
+  </style>
+  <style media="screen and (min-width:480px)">
+    .moz-text-html .mj-column-per-100 {
+      width: 100% !important;
+      max-width: 100%;
+    }
+
+  </style>
+  <style type="text/css">
+    @media only screen and (max-width:479px) {
+      table.mj-full-width-mobile {
+        width: 100% !important;
+      }
+
+      td.mj-full-width-mobile {
+        width: auto !important;
+      }
+    }
+
+  </style>
+</head>
+
+<body style="word-spacing:normal;background-color:#eeeeee;">
+  <div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">{{ preview }}</div>
+  <div style="background-color:#eeeeee;" lang="und" dir="auto">
+    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="body-outlook" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div class="body" style="background:#ffffff;background-color:#ffffff;margin:0px auto;border-radius:5px;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;border-radius:5px;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                                    <tbody>
+                                      <tr>
+                                        <td style="width:300px;">
+                                          <img alt="Couchers.org" src="attachment_imgs/logo-with-couchers.org-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="300" height="auto">
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Hi {{ user.name|v2esc }},</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Welcome to Couchers.org, and our growing worldwide community of couch surfers. We are excited that you have joined us!</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">We are still a small platform and are growing quickly. As an early user, you have a vital role in shaping the future of the platform. You can help us set the scene and culture as we grow bigger, as well as test functionality and provide feedback as we develop new features.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Please take some time to explore the platform. As a first step, please write a bit about yourself on your profile. Please upload a photo and fill in some profile text, you can even copy your description from your account on other platforms if you're feeling lazy :P</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">To edit your profile, click on the following link:</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
+                                          <a href="{{ edit_profile_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>Complete your profile</b>
+                                          </a>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Otherwise, please share the link with any of your couch surfing friends that you trust! This platform can only grow with your help bringing the best people to it.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Link: <a href="{{ app_link|v2url }}" style="color: #00a398;">{{ app_link|v2url }}</a></div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">The platform is still under development, and features are actively being built by our amazing team of Couchers.org contributors around the world. We would like to hear your feedback and thoughts on what we've built and what we're working on. You can head on over to our forum at <https: community.couchers.org> if you'd like to join the conversation.</https:>
+                                  </div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Thanks so much for joining the platform. We're really excited to have you here and as part of our growing community!</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Best,<br>Aapeli<br>Couchers.org Co-founder<br><a href="mailto:aapeli@couchers.org" style="color: #00a398;">aapeli@couchers.org</a> (feel free to shoot me an email at any time)<br><br><a href="https://couchers.org/user/aapeli" style="color: #00a398;">https://couchers.org/user/aapeli</a></div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:12px;text-align:center;color:#333333;">You're receiving this email because you signed up for Couchers.org.<br> You can reach our support team at <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a>.<br> All times are in {{ _timezone_display|v2sf }}, based on your profile location.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:1;text-align:center;color:#333333;"><a href="https://community.couchers.org/" style="color: #00a398;">Forum</a> | <a href="https://couchers.org/donate" style="color: #00a398;">Donate</a> | <a href="https://github.com/Couchers-org/couchers" style="color: #00a398;">GitHub</a> | <a href="https://couchers.org/volunteer" style="color: #00a398;">Volunteer</a> | <a href="https://couchers.org/blog" style="color: #00a398;">Blog</a></div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:10px;text-align:center;color:#333333;">&copy; {{ _year|v2sf }} Couchers, Inc.<br> 3120 Winkler Ave, Unit 9. Fort Myers, FL 33916, USA.<br>
+                                    <i>Couchers.org is a project of Couchers, Inc. a U.S. 501(c)(3) non-profit, tax-exempt organization.</i>
+                                  </div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                  <tbody>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:16px;text-align:center;color:#777777;">___UNSUB_SECTION___</div>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                          <tbody>
+                            <tr>
+                              <td style="width:50px;">
+                                <img alt src="attachment_imgs/logo-grey.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="50" height="auto">
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:16px;text-align:center;color:#777777;">The happy Couchers couch hopes you have a great rest of the day.</div>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><![endif]-->
+  </div>
+</body>
+
+</html>
diff --git a/app/backend/templates/v2/generated_html/email_changed_confirmation_old_email.html b/app/backend/templates/v2/generated_html/onboarding2.html
similarity index 94%
rename from app/backend/templates/v2/generated_html/email_changed_confirmation_old_email.html
rename to app/backend/templates/v2/generated_html/onboarding2.html
index 5ea6a394f4..82f0050995 100644
--- a/app/backend/templates/v2/generated_html/email_changed_confirmation_old_email.html
+++ b/app/backend/templates/v2/generated_html/onboarding2.html
@@ -172,17 +172,12 @@
                               </tr>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">You requested that your email be changed on Couchers.org.</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">I hope you've taken a bit of time to look around the Couchers.org platform!</div>
                                 </td>
                               </tr>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">The new email address is <code>{{ user.new_email|v2esc }}</code>.</div>
-                                </td>
-                              </tr>
-                              <tr>
-                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Please confirm you requested this change by clicking the following link:</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">We would ask one big favour of you: please fill out your profile by adding a photo and some text.</div>
                                 </td>
                               </tr>
                             </tbody>
@@ -210,8 +205,8 @@
                                     <tbody>
                                       <tr>
                                         <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
-                                          <a href="{{ confirmation_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
-                                            <b>Confirm email change</b>
+                                          <a href="{{ edit_profile_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>Go to edit your profile</b>
                                           </a>
                                         </td>
                                       </tr>
@@ -240,17 +235,17 @@
                             <tbody>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">If you haven't already confirmed this change via your new email address, you will still need a final confirmation via a similar email sent to that address.</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">A big problem that some platforms have is that there are so many empty accounts, and we really want to avoid that!</div>
                                 </td>
                               </tr>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">If this wasn't you, please contact us by emailing <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a> so we can sort this out as soon as possible!</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">We would really appreciate it if you could add a photo and fill in at least the "Who I am" section of your profile, although a full profile would be even better! It's an easy way to make this new platform look great, and it'll mean you're set up for when people start using the platform to start couch surfing.</div>
                                 </td>
                               </tr>
                               <tr>
                                 <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
-                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Best,<br>The Couchers.org Team</div>
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Thank you so much!<br>Emily from Couchers.org<br><a href="mailto:community@couchers.org" style="color: #00a398;">community@couchers.org</a><br><br><a href="https://couchers.org/user/emily" style="color: #00a398;">https://couchers.org/user/emily</a></div>
                                 </td>
                               </tr>
                             </tbody>
diff --git a/app/backend/templates/v2/generated_html/signup_continue.html b/app/backend/templates/v2/generated_html/signup_continue.html
new file mode 100644
index 0000000000..28501ecf65
--- /dev/null
+++ b/app/backend/templates/v2/generated_html/signup_continue.html
@@ -0,0 +1,356 @@
+<!doctype html>
+<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
+
+<head>
+  <title>{{ subject }}</title>
+  <!--[if !mso]><!-->
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!--<![endif]-->
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    #outlook a {
+      padding: 0;
+    }
+
+    body {
+      margin: 0;
+      padding: 0;
+      -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+    }
+
+    table,
+    td {
+      border-collapse: collapse;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+    }
+
+    img {
+      border: 0;
+      height: auto;
+      line-height: 100%;
+      outline: none;
+      text-decoration: none;
+      -ms-interpolation-mode: bicubic;
+    }
+
+    p {
+      display: block;
+      margin: 13px 0;
+    }
+
+  </style>
+  <!--[if mso]>
+    <noscript>
+    <xml>
+    <o:OfficeDocumentSettings>
+      <o:AllowPNG/>
+      <o:PixelsPerInch>96</o:PixelsPerInch>
+    </o:OfficeDocumentSettings>
+    </xml>
+    </noscript>
+    <![endif]-->
+  <!--[if lte mso 11]>
+    <style type="text/css">
+      .mj-outlook-group-fix { width:100% !important; }
+    </style>
+    <![endif]-->
+  <!--[if !mso]><!-->
+  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
+  <style type="text/css">
+    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
+
+  </style>
+  <!--<![endif]-->
+  <style type="text/css">
+    @media only screen and (min-width:480px) {
+      .mj-column-per-100 {
+        width: 100% !important;
+        max-width: 100%;
+      }
+    }
+
+  </style>
+  <style media="screen and (min-width:480px)">
+    .moz-text-html .mj-column-per-100 {
+      width: 100% !important;
+      max-width: 100%;
+    }
+
+  </style>
+  <style type="text/css">
+    @media only screen and (max-width:479px) {
+      table.mj-full-width-mobile {
+        width: 100% !important;
+      }
+
+      td.mj-full-width-mobile {
+        width: auto !important;
+      }
+    }
+
+  </style>
+</head>
+
+<body style="word-spacing:normal;background-color:#eeeeee;">
+  <div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">{{ preview }}</div>
+  <div style="background-color:#eeeeee;" lang="und" dir="auto">
+    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="body-outlook" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div class="body" style="background:#ffffff;background-color:#ffffff;margin:0px auto;border-radius:5px;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;border-radius:5px;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                                    <tbody>
+                                      <tr>
+                                        <td style="width:300px;">
+                                          <img alt="Couchers.org" src="attachment_imgs/logo-with-couchers.org-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="300" height="auto">
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Hi {{ flow.name|v2esc }},</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Please finish signing up for Couchers.org.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Please click on this link to continue:</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
+                                          <a href="{{ signup_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>Finish signing up</b>
+                                          </a>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">See you in a bit :)</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Best,<br>The Couchers.org Team</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">If you did not request a signup, someone else did with your email, and you can safely ignore this message.</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:12px;text-align:center;color:#333333;">You're receiving this email because you signed up for Couchers.org.<br> You can reach our support team at <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a>.<br> All times are in {{ _timezone_display|v2sf }}, based on your profile location.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:1;text-align:center;color:#333333;"><a href="https://community.couchers.org/" style="color: #00a398;">Forum</a> | <a href="https://couchers.org/donate" style="color: #00a398;">Donate</a> | <a href="https://github.com/Couchers-org/couchers" style="color: #00a398;">GitHub</a> | <a href="https://couchers.org/volunteer" style="color: #00a398;">Volunteer</a> | <a href="https://couchers.org/blog" style="color: #00a398;">Blog</a></div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:10px;text-align:center;color:#333333;">&copy; {{ _year|v2sf }} Couchers, Inc.<br> 3120 Winkler Ave, Unit 9. Fort Myers, FL 33916, USA.<br>
+                                    <i>Couchers.org is a project of Couchers, Inc. a U.S. 501(c)(3) non-profit, tax-exempt organization.</i>
+                                  </div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                  <tbody>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:16px;text-align:center;color:#777777;">___UNSUB_SECTION___</div>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                          <tbody>
+                            <tr>
+                              <td style="width:50px;">
+                                <img alt src="attachment_imgs/logo-grey.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="50" height="auto">
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:16px;text-align:center;color:#777777;">The happy Couchers couch hopes you have a great rest of the day.</div>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><![endif]-->
+  </div>
+</body>
+
+</html>
diff --git a/app/backend/templates/v2/generated_html/signup_verify.html b/app/backend/templates/v2/generated_html/signup_verify.html
new file mode 100644
index 0000000000..95bd04504e
--- /dev/null
+++ b/app/backend/templates/v2/generated_html/signup_verify.html
@@ -0,0 +1,351 @@
+<!doctype html>
+<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
+
+<head>
+  <title>{{ subject }}</title>
+  <!--[if !mso]><!-->
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!--<![endif]-->
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    #outlook a {
+      padding: 0;
+    }
+
+    body {
+      margin: 0;
+      padding: 0;
+      -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+    }
+
+    table,
+    td {
+      border-collapse: collapse;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+    }
+
+    img {
+      border: 0;
+      height: auto;
+      line-height: 100%;
+      outline: none;
+      text-decoration: none;
+      -ms-interpolation-mode: bicubic;
+    }
+
+    p {
+      display: block;
+      margin: 13px 0;
+    }
+
+  </style>
+  <!--[if mso]>
+    <noscript>
+    <xml>
+    <o:OfficeDocumentSettings>
+      <o:AllowPNG/>
+      <o:PixelsPerInch>96</o:PixelsPerInch>
+    </o:OfficeDocumentSettings>
+    </xml>
+    </noscript>
+    <![endif]-->
+  <!--[if lte mso 11]>
+    <style type="text/css">
+      .mj-outlook-group-fix { width:100% !important; }
+    </style>
+    <![endif]-->
+  <!--[if !mso]><!-->
+  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
+  <style type="text/css">
+    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
+
+  </style>
+  <!--<![endif]-->
+  <style type="text/css">
+    @media only screen and (min-width:480px) {
+      .mj-column-per-100 {
+        width: 100% !important;
+        max-width: 100%;
+      }
+    }
+
+  </style>
+  <style media="screen and (min-width:480px)">
+    .moz-text-html .mj-column-per-100 {
+      width: 100% !important;
+      max-width: 100%;
+    }
+
+  </style>
+  <style type="text/css">
+    @media only screen and (max-width:479px) {
+      table.mj-full-width-mobile {
+        width: 100% !important;
+      }
+
+      td.mj-full-width-mobile {
+        width: auto !important;
+      }
+    }
+
+  </style>
+</head>
+
+<body style="word-spacing:normal;background-color:#eeeeee;">
+  <div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">{{ preview }}</div>
+  <div style="background-color:#eeeeee;" lang="und" dir="auto">
+    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="body-outlook" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div class="body" style="background:#ffffff;background-color:#ffffff;margin:0px auto;border-radius:5px;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;border-radius:5px;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                                    <tbody>
+                                      <tr>
+                                        <td style="width:300px;">
+                                          <img alt="Couchers.org" src="attachment_imgs/logo-with-couchers.org-small.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="300" height="auto">
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Hi {{ flow.name|v2esc }},</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Thanks for signing up for Couchers.org!</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">To finish setting up your account, please click on the following link to activate your email:</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td align="center" bgcolor="#00a398" role="presentation" style="border:none;border-radius:5px;cursor:auto;mso-padding-alt:10px 25px;background:#00a398;" valign="middle">
+                                          <a href="{{ signup_link|v2url }}" style="display: inline-block; background: #00a398; color: white; font-family: Ubuntu, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; line-height: 120%; margin: 0; text-decoration: none; text-transform: none; padding: 10px 25px; mso-padding-alt: 0px; border-radius: 5px;" target="_blank">
+                                            <b>Confirm your email address</b>
+                                          </a>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">See you in a bit :)</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#333333;">Best,<br>The Couchers.org Team</div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:560px;" width="560" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+              <div style="margin:0px auto;max-width:560px;">
+                <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+                  <tbody>
+                    <tr>
+                      <td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
+                        <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:560px;" ><![endif]-->
+                        <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                          <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <p style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:100%;">
+                                  </p>
+                                  <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" style="border-top:solid 1px #00a398;font-size:1px;margin:0px auto;width:510px;" role="presentation" width="510px" ><tr><td style="height:0;line-height:0;"> &nbsp;
+</td></tr></table><![endif]-->
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:12px;text-align:center;color:#333333;">You're receiving this email because you signed up for Couchers.org.<br> You can reach our support team at <a href="mailto:support@couchers.org" style="color: #00a398;">support@couchers.org</a>.<br> All times are in {{ _timezone_display|v2sf }}, based on your profile location.</div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:1;text-align:center;color:#333333;"><a href="https://community.couchers.org/" style="color: #00a398;">Forum</a> | <a href="https://couchers.org/donate" style="color: #00a398;">Donate</a> | <a href="https://github.com/Couchers-org/couchers" style="color: #00a398;">GitHub</a> | <a href="https://couchers.org/volunteer" style="color: #00a398;">Volunteer</a> | <a href="https://couchers.org/blog" style="color: #00a398;">Blog</a></div>
+                                </td>
+                              </tr>
+                              <tr>
+                                <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                                  <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:10px;text-align:center;color:#333333;">&copy; {{ _year|v2sf }} Couchers, Inc.<br> 3120 Winkler Ave, Unit 9. Fort Myers, FL 33916, USA.<br>
+                                    <i>Couchers.org is a project of Couchers, Inc. a U.S. 501(c)(3) non-profit, tax-exempt organization.</i>
+                                  </div>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </div>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                  <tbody>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:10px;line-height:16px;text-align:center;color:#777777;">___UNSUB_SECTION___</div>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
+                          <tbody>
+                            <tr>
+                              <td style="width:50px;">
+                                <img alt src="attachment_imgs/logo-grey.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="50" height="auto">
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </td>
+                    </tr>
+                    <tr>
+                      <td align="center" class="unsub" style="font-size:0px;padding:0px;word-break:break-word;">
+                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:8px;line-height:16px;text-align:center;color:#777777;">The happy Couchers couch hopes you have a great rest of the day.</div>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><![endif]-->
+  </div>
+</body>
+
+</html>
diff --git a/app/backend/templates/v2/host_request__plain.mjml b/app/backend/templates/v2/host_request__plain.mjml
index d1d2e9e51d..858da77d64 100644
--- a/app/backend/templates/v2/host_request__plain.mjml
+++ b/app/backend/templates/v2/host_request__plain.mjml
@@ -21,6 +21,6 @@
 </mj-section>
 <mj-section padding="0px">
   <mj-column>
-    <mj-text><b>Do not reply to this email.</b> Use one of the buttons above, or log in to your account to reply to this request.</mj-text>
+    <mj-text><b>Do not reply to this email.</b> Use the buttons above, or log in to your account to reply to this request.</mj-text>
   </mj-column>
 </mj-section>
diff --git a/app/backend/templates/v2/login_link.mjml b/app/backend/templates/v2/login_link.mjml
new file mode 100644
index 0000000000..a73da4fcfa
--- /dev/null
+++ b/app/backend/templates/v2/login_link.mjml
@@ -0,0 +1,19 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
+    <mj-text>Here's a login link for Couchers.org:</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ login_link|v2url }}">
+      <b>Log in</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>See you in a bit :)</mj-text>
+    <mj-text>Best,<br />The Couchers.org Team</mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/login_link.txt b/app/backend/templates/v2/login_link.txt
new file mode 100644
index 0000000000..72428a411f
--- /dev/null
+++ b/app/backend/templates/v2/login_link.txt
@@ -0,0 +1,10 @@
+Hi {{ user.name }}!
+
+Here's a login link for Couchers.org:
+
+<{{ login_link }}>
+
+See you in a bit :)
+
+Best,
+The Couchers.org team
diff --git a/app/backend/templates/v2/onboarding1.mjml b/app/backend/templates/v2/onboarding1.mjml
new file mode 100644
index 0000000000..bc4e8905fd
--- /dev/null
+++ b/app/backend/templates/v2/onboarding1.mjml
@@ -0,0 +1,25 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
+    <mj-text>Welcome to Couchers.org, and our growing worldwide community of couch surfers. We are excited that you have joined us!</mj-text>
+    <mj-text>We are still a small platform and are growing quickly. As an early user, you have a vital role in shaping the future of the platform. You can help us set the scene and culture as we grow bigger, as well as test functionality and provide feedback as we develop new features.</mj-text>
+    <mj-text>Please take some time to explore the platform. As a first step, please write a bit about yourself on your profile. Please upload a photo and fill in some profile text, you can even copy your description from your account on other platforms if you're feeling lazy :P</mj-text>
+    <mj-text>To edit your profile, click on the following link:</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ edit_profile_link|v2url }}">
+      <b>Complete your profile</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Otherwise, please share the link with any of your couch surfing friends that you trust! This platform can only grow with your help bringing the best people to it.</mj-text>
+    <mj-text>Link: <a href="{{ app_link|v2url }}">{{ app_link|v2url }}</a></mj-text>
+    <mj-text>The platform is still under development, and features are actively being built by our amazing team of Couchers.org contributors around the world. We would like to hear your feedback and thoughts on what we've built and what we're working on. You can head on over to our forum at <https://community.couchers.org> if you'd like to join the conversation.</mj-text>
+    <mj-text>Thanks so much for joining the platform. We're really excited to have you here and as part of our growing community!</mj-text>
+    <mj-text>Best,<br />Aapeli<br />Couchers.org Co-founder<br /><a href="mailto:aapeli@couchers.org">aapeli@couchers.org</a> (feel free to shoot me an email at any time)<br /><br /><a href="https://couchers.org/user/aapeli">https://couchers.org/user/aapeli</a></mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/onboarding1.txt b/app/backend/templates/v2/onboarding1.txt
new file mode 100644
index 0000000000..834e7b2f84
--- /dev/null
+++ b/app/backend/templates/v2/onboarding1.txt
@@ -0,0 +1,30 @@
+Hi {{ user.name }}!
+
+Welcome to Couchers.org, and our growing worldwide community of couch surfers. We are excited that you have joined us!
+
+We are still a small platform and are growing quickly. As an early user, you have a vital role in shaping the future of the platform. You can help us set the scene and culture as we grow bigger, as well as test functionality and provide feedback as we develop new features.
+
+Please take some time to explore the platform. As a first step, please write a bit about yourself on your profile. Please upload a photo and fill in some profile text, you can even copy your description from your account on other platforms if you're feeling lazy :P
+
+To edit your profile, click on the following link:
+
+<{{ edit_profile_link }}>
+
+
+Otherwise, please share the link with any of your couch surfing friends that you trust! This platform can only grow with your help bringing the best people to it.
+
+Link: <{{ app_link }}>
+
+The platform is still under development, and features are actively being built by our amazing team of Couchers.org contributors around the world. We would like to hear your feedback and thoughts on what we've built and what we're working on. You can head on over to our forum at <https://community.couchers.org> if you'd like to join the conversation.
+
+
+Thanks so much for joining the platform. We're really excited to have you here and as part of our growing community!
+
+
+Best,
+
+Aapeli
+Couchers.org Co-founder
+aapeli@couchers.org (feel free to shoot me an email at any time)
+
+https://couchers.org/user/aapeli
diff --git a/app/backend/templates/v2/onboarding2.mjml b/app/backend/templates/v2/onboarding2.mjml
new file mode 100644
index 0000000000..a13023a50c
--- /dev/null
+++ b/app/backend/templates/v2/onboarding2.mjml
@@ -0,0 +1,21 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ user.name|v2esc }},</mj-text>
+    <mj-text>I hope you've taken a bit of time to look around the Couchers.org platform!</mj-text>
+    <mj-text>We would ask one big favour of you: please fill out your profile by adding a photo and some text.</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ edit_profile_link|v2url }}">
+      <b>Go to edit your profile</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>A big problem that some platforms have is that there are so many empty accounts, and we really want to avoid that!</mj-text>
+    <mj-text>We would really appreciate it if you could add a photo and fill in at least the "Who I am" section of your profile, although a full profile would be even better! It's an easy way to make this new platform look great, and it'll mean you're set up for when people start using the platform to start couch surfing.</mj-text>
+    <mj-text>Thank you so much!<br />Emily from Couchers.org<br /><a href="mailto:community@couchers.org">community@couchers.org</a><br /><br /><a href="https://couchers.org/user/emily">https://couchers.org/user/emily</a></mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/onboarding2.txt b/app/backend/templates/v2/onboarding2.txt
new file mode 100644
index 0000000000..8c44601c9f
--- /dev/null
+++ b/app/backend/templates/v2/onboarding2.txt
@@ -0,0 +1,21 @@
+Hi {{ user.name }}!
+
+I hope you've taken a bit of time to look around the Couchers.org platform!
+
+We would ask one big favour of you: please fill out your profile by adding a photo and some text.
+
+Link to profile editing page: <{{ edit_profile_link }}>
+
+
+A big problem that some platforms have is that there are so many empty accounts, and we really want to avoid that!
+
+We would really appreciate it if you could add a photo and fill in at least the "Who I am" section of your profile, although a full profile would be even better! It's an easy way to make this new platform look great, and it'll mean you're set up for when people start using the platform to start couch surfing.
+
+
+Thank you so much!
+
+Emily from Couchers.org
+
+<community@couchers.org>
+
+<https://couchers.org/user/emily>
diff --git a/app/backend/templates/v2/signup_continue.mjml b/app/backend/templates/v2/signup_continue.mjml
new file mode 100644
index 0000000000..f49a86a49c
--- /dev/null
+++ b/app/backend/templates/v2/signup_continue.mjml
@@ -0,0 +1,21 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ flow.name|v2esc }},</mj-text>
+    <mj-text>Please finish signing up for Couchers.org.</mj-text>
+    <mj-text>Please click on this link to continue:</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ signup_link|v2url }}">
+      <b>Finish signing up</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>See you in a bit :)</mj-text>
+    <mj-text>Best,<br />The Couchers.org Team</mj-text>
+    <mj-text>If you did not request a signup, someone else did with your email, and you can safely ignore this message.</mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/signup_continue.txt b/app/backend/templates/v2/signup_continue.txt
new file mode 100644
index 0000000000..5ee58fd330
--- /dev/null
+++ b/app/backend/templates/v2/signup_continue.txt
@@ -0,0 +1,15 @@
+Hi {{ flow.name }}!
+
+Please finish signing up for Couchers.org.
+
+Please click on this link to continue:
+
+<{{ signup_link }}>
+
+
+See you in a bit :)
+
+Best,
+The Couchers.org team
+
+If you did not request a signup, someone else did with your email, and you can safely ignore this message.
diff --git a/app/backend/templates/v2/signup_verify.mjml b/app/backend/templates/v2/signup_verify.mjml
new file mode 100644
index 0000000000..a404bbde8f
--- /dev/null
+++ b/app/backend/templates/v2/signup_verify.mjml
@@ -0,0 +1,20 @@
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>Hi {{ flow.name|v2esc }},</mj-text>
+    <mj-text>Thanks for signing up for Couchers.org!</mj-text>
+    <mj-text>To finish setting up your account, please click on the following link to activate your email:</mj-text>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-button background-color="#00a398" color="white" border-radius="5px" href="{{ signup_link|v2url }}">
+      <b>Confirm your email address</b>
+    </mj-button>
+  </mj-column>
+</mj-section>
+<mj-section padding="0px">
+  <mj-column>
+    <mj-text>See you in a bit :)</mj-text>
+    <mj-text>Best,<br />The Couchers.org Team</mj-text>
+  </mj-column>
+</mj-section>
diff --git a/app/backend/templates/v2/signup_verify.txt b/app/backend/templates/v2/signup_verify.txt
new file mode 100644
index 0000000000..619e009c94
--- /dev/null
+++ b/app/backend/templates/v2/signup_verify.txt
@@ -0,0 +1,13 @@
+Hi {{ flow.name }}!
+
+Thanks for signing up for Couchers.org!
+
+To finish setting up your account, please click on the following link to activate your email:
+
+<{{ signup_link }}>
+
+
+See you in a bit :)
+
+Best,
+The Couchers.org team
diff --git a/app/proto/notification_data.proto b/app/proto/notification_data.proto
index a0a73fedd2..edf4247197 100644
--- a/app/proto/notification_data.proto
+++ b/app/proto/notification_data.proto
@@ -42,6 +42,12 @@ message HostRequestMessage {
   bool am_host = 4;
 }
 
+message HostRequestMissedMessages {
+  org.couchers.api.requests.HostRequest host_request = 1;
+  org.couchers.api.core.User user = 2;
+  bool am_host = 3;
+}
+
 message BadgeAdd {
   string badge_id = 1;
   string badge_name = 2;
@@ -142,6 +148,10 @@ message ChatMessage {
   uint64 group_chat_id = 4;
 }
 
+message ChatMissedMessages {
+  repeated ChatMessage messages = 1;
+}
+
 message ReferenceReceiveFriend {
   org.couchers.api.core.User from_user = 1;
   string text = 2;


Test patch:
diff --git a/app/backend/src/tests/test_account.py b/app/backend/src/tests/test_account.py
index 39ff0828b7..02d5304201 100644
--- a/app/backend/src/tests/test_account.py
+++ b/app/backend/src/tests/test_account.py
@@ -20,8 +20,8 @@
     email_fields,
     fast_passwords,
     generate_user,
-    handle_notifications_bg,
     mock_notification_email,
+    process_jobs,
     push_collector,
     testconfig,
 )
@@ -68,7 +68,7 @@ def test_GetAccountInfo_regression(db):
         res = account.GetAccountInfo(empty_pb2.Empty())
 
 
-def test_ChangePassword_normal(db, fast_passwords):
+def test_ChangePassword_normal(db, fast_passwords, push_collector):
     # user has old password and is changing to new password
     old_password = random_hex()
     new_password = random_hex()
@@ -83,8 +83,12 @@ def test_ChangePassword_normal(db, fast_passwords):
                 )
             )
 
-        mock.assert_called_once()
-        assert email_fields(mock).subject == "[TEST] Your password was changed"
+    mock.assert_called_once()
+    assert email_fields(mock).subject == "[TEST] Your password was changed"
+
+    push_collector.assert_user_has_single_matching(
+        user.id, title="Your password was changed", body="Your login password for Couchers.org was changed."
+    )
 
     with session_scope() as session:
         updated_user = session.execute(select(User).where(User.id == user.id)).scalar_one()
@@ -550,7 +554,7 @@ def test_ChangeEmail_has_password(db, fast_passwords):
         assert not user.need_to_confirm_via_new_email
 
 
-def test_ChangeEmail_sends_proper_emails_has_password(db, fast_passwords):
+def test_ChangeEmail_sends_proper_emails_has_password(db, fast_passwords, push_collector):
     password = random_hex()
     new_email = f"{random_hex()}@couchers.org.invalid"
     user, token = generate_user(hashed_password=hash_password(password))
@@ -563,7 +567,7 @@ def test_ChangeEmail_sends_proper_emails_has_password(db, fast_passwords):
             )
         )
 
-    handle_notifications_bg()
+    process_jobs()
 
     with session_scope() as session:
         jobs = session.execute(select(BackgroundJob).where(BackgroundJob.job_type == "send_email")).scalars().all()
@@ -578,6 +582,12 @@ def test_ChangeEmail_sends_proper_emails_has_password(db, fast_passwords):
             uq_str2 in jobs[0].payload and uq_str1 in jobs[1].payload
         )
 
+    push_collector.assert_user_has_single_matching(
+        user.id,
+        title="An email change was initiated on your account",
+        body=f"An email change to the email {new_email} was initiated on your account.",
+    )
+
 
 def test_contributor_form(db):
     user, token = generate_user()
diff --git a/app/backend/src/tests/test_admin.py b/app/backend/src/tests/test_admin.py
index ea17de02bb..32ae159cb4 100644
--- a/app/backend/src/tests/test_admin.py
+++ b/app/backend/src/tests/test_admin.py
@@ -31,153 +31,174 @@ def _(testconfig):
 
 
 def test_access_by_normal_user(db):
-    with session_scope():
-        normal_user, normal_token = generate_user()
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(normal_token) as api:
-            # all requests to the admin servicer should break when done by a non-super_user
-            with pytest.raises(grpc.RpcError) as e:
-                api.GetUserDetails(
-                    admin_pb2.GetUserDetailsReq(
-                        user=str(normal_user.id),
-                    )
+    with real_admin_session(normal_token) as api:
+        # all requests to the admin servicer should break when done by a non-super_user
+        with pytest.raises(grpc.RpcError) as e:
+            api.GetUserDetails(
+                admin_pb2.GetUserDetailsReq(
+                    user=str(normal_user.id),
                 )
-            assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
+            )
+        assert e.value.code() == grpc.StatusCode.PERMISSION_DENIED
 
 
 def test_GetUserDetails(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(super_token) as api:
-            res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=str(normal_user.id)))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert not res.deleted
+    with real_admin_session(super_token) as api:
+        res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=str(normal_user.id)))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert not res.deleted
+
+    with real_admin_session(super_token) as api:
+        res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert not res.deleted
+
+    with real_admin_session(super_token) as api:
+        res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.email))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert not res.deleted
+
+
+def test_ChangeUserGender(db, push_collector):
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(super_token) as api:
-            res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert not res.deleted
+    with real_admin_session(super_token) as api:
+        with mock_notification_email() as mock:
+            res = api.ChangeUserGender(admin_pb2.ChangeUserGenderReq(user=normal_user.username, gender="Machine"))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == "Machine"
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert not res.deleted
 
-        with real_admin_session(super_token) as api:
-            res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.email))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert not res.deleted
-
-
-def test_ChangeUserGender(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
+    mock.assert_called_once()
+    e = email_fields(mock)
+    assert e.subject == "[TEST] Your gender was changed"
+    assert e.recipient == normal_user.email
+    assert "Machine" in e.plain
+    assert "Machine" in e.html
 
-        with real_admin_session(super_token) as api:
-            res = api.ChangeUserGender(admin_pb2.ChangeUserGenderReq(user=normal_user.username, gender="Machine"))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == "Machine"
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert not res.deleted
+    push_collector.assert_user_has_single_matching(
+        normal_user.id,
+        title="Your gender was changed",
+        body="Your gender on Couchers.org was changed to Machine by an admin.",
+    )
 
 
-def test_ChangeUserBirthdate(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user(birthdate=date(year=2000, month=1, day=1))
+def test_ChangeUserBirthdate(db, push_collector):
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user(birthdate=date(year=2000, month=1, day=1))
 
-        with real_admin_session(super_token) as api:
-            res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username))
-            assert parse_date(res.birthdate) == date(year=2000, month=1, day=1)
+    with real_admin_session(super_token) as api:
+        res = api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username))
+        assert parse_date(res.birthdate) == date(year=2000, month=1, day=1)
 
+        with mock_notification_email() as mock:
             res = api.ChangeUserBirthdate(
                 admin_pb2.ChangeUserBirthdateReq(user=normal_user.username, birthdate="1990-05-25")
             )
 
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.birthdate == "1990-05-25"
-        assert res.gender == normal_user.gender
-        assert not res.banned
-        assert not res.deleted
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.birthdate == "1990-05-25"
+    assert res.gender == normal_user.gender
+    assert not res.banned
+    assert not res.deleted
 
+    mock.assert_called_once()
+    e = email_fields(mock)
+    assert e.subject == "[TEST] Your date of birth was changed"
+    assert e.recipient == normal_user.email
+    assert "1990" in e.plain
+    assert "1990" in e.html
 
-def test_BanUser(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, _ = generate_user()
-        admin_note = "A good reason"
-        utc_regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00"
-        prefix_regex = rf"\n\[{utc_regex}\] \(id: {super_user.id}, username: {super_user.username}\)"
-
-        with real_admin_session(super_token) as api:
-            res = api.BanUser(admin_pb2.BanUserReq(user=normal_user.username, admin_note=admin_note))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert res.banned
-        assert not res.deleted
-        assert match(rf"^{prefix_regex} {admin_note}\n$", res.admin_note)
+    push_collector.assert_user_has_single_matching(
+        normal_user.id,
+        title="Your date of birth was changed",
+        body="Your date of birth on Couchers.org was changed to Friday 25 May 1990 by an admin.",
+    )
 
 
-def test_AddAdminNote(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, _ = generate_user()
-        admin_note1 = "User reported strange behavior"
-        admin_note2 = "Insert private information here"
-        utc_regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00"
-        prefix_regex = rf"\n\[{utc_regex}\] \(id: {super_user.id}, username: {super_user.username}\)"
+def test_BanUser(db):
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, _ = generate_user()
+    admin_note = "A good reason"
+    utc_regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00"
+    prefix_regex = rf"\n\[{utc_regex}\] \(id: {super_user.id}, username: {super_user.username}\)"
+
+    with real_admin_session(super_token) as api:
+        res = api.BanUser(admin_pb2.BanUserReq(user=normal_user.username, admin_note=admin_note))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert res.banned
+    assert not res.deleted
+    assert match(rf"^{prefix_regex} {admin_note}\n$", res.admin_note)
 
-        with real_admin_session(super_token) as api:
-            res = api.AddAdminNote(admin_pb2.AddAdminNoteReq(user=normal_user.username, admin_note=admin_note1))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert not res.deleted
-        assert match(rf"^{prefix_regex} {admin_note1}\n$", res.admin_note)
 
-        with real_admin_session(super_token) as api:
-            res = api.AddAdminNote(admin_pb2.AddAdminNoteReq(user=normal_user.username, admin_note=admin_note2))
-        assert match(rf"^{prefix_regex} {admin_note1}\n{prefix_regex} {admin_note2}\n$", res.admin_note)
+def test_AddAdminNote(db):
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, _ = generate_user()
+    admin_note1 = "User reported strange behavior"
+    admin_note2 = "Insert private information here"
+    utc_regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00"
+    prefix_regex = rf"\n\[{utc_regex}\] \(id: {super_user.id}, username: {super_user.username}\)"
+
+    with real_admin_session(super_token) as api:
+        res = api.AddAdminNote(admin_pb2.AddAdminNoteReq(user=normal_user.username, admin_note=admin_note1))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert not res.deleted
+    assert match(rf"^{prefix_regex} {admin_note1}\n$", res.admin_note)
+
+    with real_admin_session(super_token) as api:
+        res = api.AddAdminNote(admin_pb2.AddAdminNoteReq(user=normal_user.username, admin_note=admin_note2))
+    assert match(rf"^{prefix_regex} {admin_note1}\n{prefix_regex} {admin_note2}\n$", res.admin_note)
 
 
 def test_DeleteUser(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(super_token) as api:
-            res = api.DeleteUser(admin_pb2.DeleteUserReq(user=normal_user.username))
-        assert res.user_id == normal_user.id
-        assert res.username == normal_user.username
-        assert res.email == normal_user.email
-        assert res.gender == normal_user.gender
-        assert parse_date(res.birthdate) == normal_user.birthdate
-        assert not res.banned
-        assert res.deleted
+    with real_admin_session(super_token) as api:
+        res = api.DeleteUser(admin_pb2.DeleteUserReq(user=normal_user.username))
+    assert res.user_id == normal_user.id
+    assert res.username == normal_user.username
+    assert res.email == normal_user.email
+    assert res.gender == normal_user.gender
+    assert parse_date(res.birthdate) == normal_user.birthdate
+    assert not res.banned
+    assert res.deleted
 
 
 def test_CreateApiKey(db, push_collector):
@@ -266,22 +287,21 @@ def test_CreateApiKey(db, push_collector):
 
 
 def test_CreateCommunity_invalid_geojson(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
-        with real_admin_session(super_token) as api:
-            with pytest.raises(grpc.RpcError) as e:
-                api.CreateCommunity(
-                    admin_pb2.CreateCommunityReq(
-                        name="test community",
-                        slug="test-community",
-                        description="community for testing",
-                        admin_ids=[],
-                        geojson=POINT_GEOJSON,
-                    )
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
+    with real_admin_session(super_token) as api:
+        with pytest.raises(grpc.RpcError) as e:
+            api.CreateCommunity(
+                admin_pb2.CreateCommunityReq(
+                    name="test community",
+                    slug="test-community",
+                    description="community for testing",
+                    admin_ids=[],
+                    geojson=POINT_GEOJSON,
                 )
-            assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
-            assert e.value.details() == errors.NO_MULTIPOLYGON
+            )
+        assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT
+        assert e.value.details() == errors.NO_MULTIPOLYGON
 
 
 def test_CreateCommunity(db):
@@ -304,55 +324,73 @@ def test_CreateCommunity(db):
 
 
 def test_GetChats(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(super_token) as api:
-            res = api.GetChats(admin_pb2.GetChatsReq(user=normal_user.username))
-        assert res.response
+    with real_admin_session(super_token) as api:
+        res = api.GetChats(admin_pb2.GetChatsReq(user=normal_user.username))
+    assert res.response
 
 
-def test_badges(db):
-    with session_scope():
-        super_user, super_token = generate_user(is_superuser=True)
-        normal_user, normal_token = generate_user()
+def test_badges(db, push_collector):
+    super_user, super_token = generate_user(is_superuser=True)
+    normal_user, normal_token = generate_user()
 
-        with real_admin_session(super_token) as api:
-            # can add a badge
-            assert "volunteer" not in api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username)).badges
+    with real_admin_session(super_token) as api:
+        # can add a badge
+        assert "volunteer" not in api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username)).badges
+        with mock_notification_email() as mock:
             res = api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="volunteer"))
-            # assert "volunteer" in api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username)).badges
-            assert "volunteer" in res.badges
-
-            # can't add/edit special tags
-            with pytest.raises(grpc.RpcError) as e:
-                api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="founder"))
-            assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
-            assert e.value.details() == errors.ADMIN_CANNOT_EDIT_BADGE
-
-            # double add badge
-            with pytest.raises(grpc.RpcError) as e:
-                api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="volunteer"))
-            assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
-            assert e.value.details() == errors.USER_ALREADY_HAS_BADGE
-
-            # can remove badge
-            assert "volunteer" in api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username)).badges
+        assert "volunteer" in res.badges
+
+        # badge emails are disabled by default
+        mock.assert_not_called()
+
+        push_collector.assert_user_has_single_matching(
+            normal_user.id,
+            title="The Active Volunteer badge was added to your profile",
+            body="Check out your profile to see the new badge!",
+        )
+
+        # can't add/edit special tags
+        with pytest.raises(grpc.RpcError) as e:
+            api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="founder"))
+        assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
+        assert e.value.details() == errors.ADMIN_CANNOT_EDIT_BADGE
+
+        # double add badge
+        with pytest.raises(grpc.RpcError) as e:
+            api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="volunteer"))
+        assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
+        assert e.value.details() == errors.USER_ALREADY_HAS_BADGE
+
+        # can remove badge
+        assert "volunteer" in api.GetUserDetails(admin_pb2.GetUserDetailsReq(user=normal_user.username)).badges
+        with mock_notification_email() as mock:
             res = api.RemoveBadge(admin_pb2.RemoveBadgeReq(user=normal_user.username, badge_id="volunteer"))
-            assert "volunteer" not in res.badges
-
-            # not found on user
-            with pytest.raises(grpc.RpcError) as e:
-                api.RemoveBadge(admin_pb2.RemoveBadgeReq(user=normal_user.username, badge_id="volunteer"))
-            assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
-            assert e.value.details() == errors.USER_DOES_NOT_HAVE_BADGE
-
-            # not found in general
-            with pytest.raises(grpc.RpcError) as e:
-                api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="nonexistentbadge"))
-            assert e.value.code() == grpc.StatusCode.NOT_FOUND
-            assert e.value.details() == errors.BADGE_NOT_FOUND
+        assert "volunteer" not in res.badges
+
+        # badge emails are disabled by default
+        mock.assert_not_called()
+
+        push_collector.assert_user_push_matches_fields(
+            normal_user.id,
+            ix=1,
+            title="The Active Volunteer badge was removed from your profile",
+            body="You can see all your badges on your profile.",
+        )
+
+        # not found on user
+        with pytest.raises(grpc.RpcError) as e:
+            api.RemoveBadge(admin_pb2.RemoveBadgeReq(user=normal_user.username, badge_id="volunteer"))
+        assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION
+        assert e.value.details() == errors.USER_DOES_NOT_HAVE_BADGE
+
+        # not found in general
+        with pytest.raises(grpc.RpcError) as e:
+            api.AddBadge(admin_pb2.AddBadgeReq(user=normal_user.username, badge_id="nonexistentbadge"))
+        assert e.value.code() == grpc.StatusCode.NOT_FOUND
+        assert e.value.details() == errors.BADGE_NOT_FOUND
 
 
 def test_DeleteEvent(db):
diff --git a/app/backend/src/tests/test_api.py b/app/backend/src/tests/test_api.py
index 318447ca07..30e5d7c15e 100644
--- a/app/backend/src/tests/test_api.py
+++ b/app/backend/src/tests/test_api.py
@@ -482,6 +482,7 @@ def test_friend_request_flow(db, push_collector):
     push_collector.assert_user_has_single_matching(
         user2.id,
         title=f"{user1.name} wants to be your friend",
+        body=f"You've received a friend request from {user1.name}",
     )
 
     mock.assert_called_once()
@@ -545,6 +546,7 @@ def test_friend_request_flow(db, push_collector):
     push_collector.assert_user_push_matches_fields(
         user1.id,
         title=f"{user2.name} accepted your friend request!",
+        body=f"{user2.name} has accepted your friend request",
     )
 
     mock.assert_called_once()
diff --git a/app/backend/src/tests/test_auth.py b/app/backend/src/tests/test_auth.py
index 7e0e665402..b85d93fe8d 100644
--- a/app/backend/src/tests/test_auth.py
+++ b/app/backend/src/tests/test_auth.py
@@ -1,5 +1,4 @@
 import http.cookies
-from unittest.mock import patch
 
 import grpc
 import pytest
@@ -314,17 +313,17 @@ def test_login_part_signed_up_verified_email(db):
     with auth_api_session() as (auth_api, metadata_interceptor):
         auth_api.SignupFlow(auth_pb2.SignupFlowReq(email_token=email_token))
 
-    with patch("couchers.email.queue_email") as mock:
+    with mock_notification_email() as mock:
         with auth_api_session() as (auth_api, metadata_interceptor):
             with pytest.raises(grpc.RpcError) as e:
                 res = auth_api.Login(auth_pb2.LoginReq(user="email@couchers.org.invalid"))
             assert e.value.details() == errors.SIGNUP_FLOW_EMAIL_STARTED_SIGNUP
 
     assert mock.call_count == 1
-    (_, _, recipient, _, plain, html), _ = mock.call_args
-    assert recipient == "email@couchers.org.invalid"
-    assert flow_token in plain
-    assert flow_token in html
+    e = email_fields(mock)
+    assert e.recipient == "email@couchers.org.invalid"
+    assert flow_token in e.plain
+    assert flow_token in e.html
 
 
 def test_login_part_signed_up_not_verified_email(db):
@@ -350,7 +349,7 @@ def test_login_part_signed_up_not_verified_email(db):
     flow_token = res.flow_token
     assert res.need_verify_email
 
-    with patch("couchers.email.queue_email") as mock:
+    with mock_notification_email() as mock:
         with auth_api_session() as (auth_api, metadata_interceptor):
             with pytest.raises(grpc.RpcError) as e:
                 res = auth_api.Login(auth_pb2.LoginReq(user="frodo"))
@@ -361,10 +360,10 @@ def test_login_part_signed_up_not_verified_email(db):
         email_token = flow.email_token
 
     assert mock.call_count == 1
-    (_, _, recipient, _, plain, html), _ = mock.call_args
-    assert recipient == "email@couchers.org.invalid"
-    assert email_token in plain
-    assert email_token in html
+    e = email_fields(mock)
+    assert e.recipient == "email@couchers.org.invalid"
+    assert email_token in e.plain
+    assert email_token in e.html
 
 
 def test_basic_login_without_password(db):
diff --git a/app/backend/src/tests/test_bg_jobs.py b/app/backend/src/tests/test_bg_jobs.py
index 6f534751bf..7ef8a7e179 100644
--- a/app/backend/src/tests/test_bg_jobs.py
+++ b/app/backend/src/tests/test_bg_jobs.py
@@ -44,6 +44,8 @@
     generate_user,
     make_friends,
     make_user_block,
+    process_jobs,
+    push_collector,
     requests_session,
     testconfig,
 )
@@ -497,6 +499,7 @@ def test_send_message_notifications_basic(db):
     make_friends(user2, user3)
 
     send_message_notifications(empty_pb2.Empty())
+    process_jobs()
 
     # should find no jobs, since there's no messages
     with session_scope() as session:
@@ -524,6 +527,7 @@ def test_send_message_notifications_basic(db):
         c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 6"))
 
     send_message_notifications(empty_pb2.Empty())
+    process_jobs()
 
     # no emails sent out
     with session_scope() as session:
@@ -537,6 +541,7 @@ def test_send_message_notifications_basic(db):
     # this should generate emails for both user2 and user3
     with patch("couchers.jobs.handlers.now", now_5_min_in_future):
         send_message_notifications(empty_pb2.Empty())
+        process_jobs()
 
     with session_scope() as session:
         assert (
@@ -551,6 +556,7 @@ def test_send_message_notifications_basic(db):
     # shouldn't generate any more emails
     with patch("couchers.jobs.handlers.now", now_5_min_in_future):
         send_message_notifications(empty_pb2.Empty())
+        process_jobs()
 
     with session_scope() as session:
         assert (
@@ -571,6 +577,7 @@ def test_send_message_notifications_muted(db):
     make_friends(user2, user3)
 
     send_message_notifications(empty_pb2.Empty())
+    process_jobs()
 
     # should find no jobs, since there's no messages
     with session_scope() as session:
@@ -604,6 +611,7 @@ def test_send_message_notifications_muted(db):
         c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text="Test message 6"))
 
     send_message_notifications(empty_pb2.Empty())
+    process_jobs()
 
     # no emails sent out
     with session_scope() as session:
@@ -617,6 +625,7 @@ def test_send_message_notifications_muted(db):
     # this should generate emails for both user2 and NOT user3
     with patch("couchers.jobs.handlers.now", now_5_min_in_future):
         send_message_notifications(empty_pb2.Empty())
+        process_jobs()
 
     with session_scope() as session:
         assert (
@@ -631,6 +640,7 @@ def test_send_message_notifications_muted(db):
     # shouldn't generate any more emails
     with patch("couchers.jobs.handlers.now", now_5_min_in_future):
         send_message_notifications(empty_pb2.Empty())
+        process_jobs()
 
     with session_scope() as session:
         assert (
@@ -649,6 +659,7 @@ def test_send_request_notifications_host_request(db):
     today_plus_3 = (today() + timedelta(days=3)).isoformat()
 
     send_request_notifications(empty_pb2.Empty())
+    process_jobs()
 
     # should find no jobs, since there's no messages
     with session_scope() as session:
@@ -669,6 +680,7 @@ def test_send_request_notifications_host_request(db):
         # check send_request_notifications successfully creates background job
         with patch("couchers.jobs.handlers.now", now_5_min_in_future):
             send_request_notifications(empty_pb2.Empty())
+            process_jobs()
         assert (
             session.execute(
                 select(func.count()).select_from(BackgroundJob).where(BackgroundJob.job_type == "send_email")
@@ -681,6 +693,7 @@ def test_send_request_notifications_host_request(db):
 
         with patch("couchers.jobs.handlers.now", now_5_min_in_future):
             send_request_notifications(empty_pb2.Empty())
+            process_jobs()
         # should find no messages since host has already been notified
         assert (
             session.execute(
@@ -706,6 +719,7 @@ def test_send_request_notifications_host_request(db):
         # check send_request_notifications successfully creates background job
         with patch("couchers.jobs.handlers.now", now_5_min_in_future):
             send_request_notifications(empty_pb2.Empty())
+            process_jobs()
         assert (
             session.execute(
                 select(func.count()).select_from(BackgroundJob).where(BackgroundJob.job_type == "send_email")
@@ -718,6 +732,7 @@ def test_send_request_notifications_host_request(db):
 
         with patch("couchers.jobs.handlers.now", now_5_min_in_future):
             send_request_notifications(empty_pb2.Empty())
+            process_jobs()
         # should find no messages since guest has already been notified
         assert (
             session.execute(
@@ -792,6 +807,7 @@ def test_send_onboarding_emails(db):
     user1, token1 = generate_user(onboarding_emails_sent=0, last_onboarding_email_sent=None)
 
     send_onboarding_emails(empty_pb2.Empty())
+    process_jobs()
 
     with session_scope() as session:
         assert (
@@ -805,6 +821,7 @@ def test_send_onboarding_emails(db):
     user2, token2 = generate_user(onboarding_emails_sent=1, last_onboarding_email_sent=now() - timedelta(days=6))
 
     send_onboarding_emails(empty_pb2.Empty())
+    process_jobs()
 
     with session_scope() as session:
         assert (
@@ -818,6 +835,7 @@ def test_send_onboarding_emails(db):
     user3, token3 = generate_user(onboarding_emails_sent=1, last_onboarding_email_sent=now() - timedelta(days=8))
 
     send_onboarding_emails(empty_pb2.Empty())
+    process_jobs()
 
     with session_scope() as session:
         assert (
@@ -970,7 +988,7 @@ def test_update_recommendation_scores(db):
     update_recommendation_scores(empty_pb2.Empty())
 
 
-def test_update_badges(db):
+def test_update_badges(db, push_collector):
     user1, _ = generate_user()
     user2, _ = generate_user()
     user3, _ = generate_user()
@@ -978,7 +996,11 @@ def test_update_badges(db):
     user5, _ = generate_user(phone="+15555555556", phone_verification_verified=func.now())
     user6, _ = generate_user()
 
+    with session_scope() as session:
+        session.add(UserBadge(user_id=user5.id, badge_id="board_member"))
+
     update_badges(empty_pb2.Empty())
+    process_jobs()
 
     with session_scope() as session:
         badge_tuples = session.execute(
@@ -995,3 +1017,48 @@ def test_update_badges(db):
     ]
 
     assert badge_tuples == expected
+
+    print(push_collector.pushes)
+
+    push_collector.assert_user_push_matches_fields(
+        user1.id,
+        ix=0,
+        title="The Founder badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user1.id,
+        ix=1,
+        title="The Board Member badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user2.id,
+        ix=0,
+        title="The Founder badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user2.id,
+        ix=1,
+        title="The Board Member badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user4.id,
+        ix=0,
+        title="The Verified Phone badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user5.id,
+        ix=0,
+        title="The Board Member badge was removed from your profile",
+        body="You can see all your badges on your profile.",
+    )
+    push_collector.assert_user_push_matches_fields(
+        user5.id,
+        ix=1,
+        title="The Verified Phone badge was added to your profile",
+        body="Check out your profile to see the new badge!",
+    )
diff --git a/app/backend/src/tests/test_email.py b/app/backend/src/tests/test_email.py
index 6d46ed4b57..8b6a032937 100644
--- a/app/backend/src/tests/test_email.py
+++ b/app/backend/src/tests/test_email.py
@@ -32,9 +32,9 @@
     db,
     email_fields,
     generate_user,
-    handle_notifications_bg,
     mock_notification_email,
     notifications_session,
+    process_jobs,
     push_collector,
     session_scope,
     testconfig,
@@ -50,72 +50,61 @@ def test_login_email(db):
     user, api_token = generate_user()
 
     with session_scope() as session:
-        with patch("couchers.email.queue_email") as mock:
+        with mock_notification_email() as mock:
             login_token = send_login_email(session, user)
 
         assert mock.call_count == 1
-        (sender_name, sender_email, recipient, subject, plain, html), _ = mock.call_args
-        assert recipient == user.email
-        assert "login" in subject.lower()
-        assert login_token.token in plain
-        assert login_token.token in html
+        e = email_fields(mock)
+        assert e.recipient == user.email
+        assert "login" in e.subject.lower()
+        assert login_token.token in e.plain
 
 
 def test_signup_verification_email(db):
     request_email = f"{random_hex(12)}@couchers.org.invalid"
 
-    with session_scope():
-        flow = SignupFlow(name="Frodo", email=request_email)
+    flow = SignupFlow(name="Frodo", email=request_email)
 
-        with patch("couchers.email.queue_email") as mock:
-            send_signup_email(flow)
+    with mock_notification_email() as mock:
+        send_signup_email(flow)
 
-        assert mock.call_count == 1
-        (sender_name, sender_email, recipient, subject, plain, html), _ = mock.call_args
-        assert recipient == request_email
-        assert flow.email_token in plain
-        assert flow.email_token in html
+    assert mock.call_count == 1
+    e = email_fields(mock)
+    assert e.recipient == request_email
+    assert flow.email_token in e.plain
+    assert flow.email_token in e.html
 
 
 def test_report_email(db):
-    with session_scope():
-        user_reporter, api_token_author = generate_user()
-        user_author, api_token_reported = generate_user()
-
-        report = ContentReport(
-            reporting_user=user_reporter,
-            reason="spam",
-            description="I think this is spam and does not belong on couchers",
-            content_ref="comment/123",
-            author_user=user_author,
-            user_agent="n/a",
-            page="https://couchers.org/comment/123",
-        )
+    user_reporter, api_token_author = generate_user()
+    user_author, api_token_reported = generate_user()
+
+    report = ContentReport(
+        reporting_user=user_reporter,
+        reason="spam",
+        description="I think this is spam and does not belong on couchers",
+        content_ref="comment/123",
+        author_user=user_author,
+        user_agent="n/a",
+        page="https://couchers.org/comment/123",
+    )
 
-        with patch("couchers.email.queue_email") as mock:
-            send_content_report_email(report)
+    with mock_notification_email() as mock:
+        send_content_report_email(report)
 
-        assert mock.call_count == 1
+    assert mock.call_count == 1
 
-        (sender_name, sender_email, recipient, subject, plain, html), _ = mock.call_args
-        assert recipient == "reports@couchers.org.invalid"
-        assert report.author_user.username in plain
-        assert str(report.author_user.id) in plain
-        assert report.author_user.email in plain
-        assert report.author_user.username in html
-        assert str(report.author_user.id) in html
-        assert report.author_user.email in html
-        assert report.reporting_user.username in plain
-        assert str(report.reporting_user.id) in plain
-        assert report.reporting_user.email in plain
-        assert report.reporting_user.username in html
-        assert str(report.reporting_user.id) in html
-        assert report.reporting_user.email in html
-        assert report.reason in plain
-        assert report.reason in html
-        assert report.description in plain
-        assert report.description in html
-        assert "report" in subject.lower()
+    e = email_fields(mock)
+    assert e.recipient == "reports@couchers.org.invalid"
+    assert report.author_user.username in e.plain
+    assert str(report.author_user.id) in e.plain
+    assert report.author_user.email in e.plain
+    assert report.reporting_user.username in e.plain
+    assert str(report.reporting_user.id) in e.plain
+    assert report.reporting_user.email in e.plain
+    assert report.reason in e.plain
+    assert report.description in e.plain
+    assert "report" in e.subject.lower()
 
 
 def test_reference_report_email_not_sent(db):
@@ -138,7 +127,7 @@ def test_reference_report_email_not_sent(db):
 
         # no email sent for a positive ref
 
-        with patch("couchers.email.queue_email") as mock:
+        with mock_notification_email() as mock:
             maybe_send_reference_report_email(reference)
 
         assert mock.call_count == 0
@@ -162,31 +151,22 @@ def test_reference_report_email(db):
             was_appropriate=False,
         )
 
-        with patch("couchers.email.queue_email") as mock:
+        with mock_notification_email() as mock:
             maybe_send_reference_report_email(reference)
 
         assert mock.call_count == 1
-
-        (sender_name, sender_email, recipient, subject, plain, html), _ = mock.call_args
-        assert recipient == "reports@couchers.org.invalid"
-        assert "report" in subject.lower()
-        assert "reference" in subject.lower()
-        assert reference.from_user.username in plain
-        assert str(reference.from_user.id) in plain
-        assert reference.from_user.email in plain
-        assert reference.from_user.username in html
-        assert str(reference.from_user.id) in html
-        assert reference.from_user.email in html
-        assert reference.to_user.username in plain
-        assert str(reference.to_user.id) in plain
-        assert reference.to_user.email in plain
-        assert reference.to_user.username in html
-        assert str(reference.to_user.id) in html
-        assert reference.to_user.email in html
-        assert reference.text in plain
-        assert reference.text in html
-        assert "friend" in plain.lower()
-        assert "friend" in html.lower()
+        e = email_fields(mock)
+        assert e.recipient == "reports@couchers.org.invalid"
+        assert "report" in e.subject.lower()
+        assert "reference" in e.subject.lower()
+        assert reference.from_user.username in e.plain
+        assert str(reference.from_user.id) in e.plain
+        assert reference.from_user.email in e.plain
+        assert reference.to_user.username in e.plain
+        assert str(reference.to_user.id) in e.plain
+        assert reference.to_user.email in e.plain
+        assert reference.text in e.plain
+        assert "friend" in e.plain.lower()
 
 
 def test_email_patching_fails(db):
@@ -195,22 +175,21 @@ def test_email_patching_fails(db):
     printing function was called instead, this makes sure the patching is
     actually done
     """
-    with session_scope():
-        to_user, to_token = generate_user()
-        from_user, from_token = generate_user()
+    to_user, to_token = generate_user()
+    from_user, from_token = generate_user()
 
-        patched_msg = random_hex(64)
+    patched_msg = random_hex(64)
 
-        def mock_queue_email(**kwargs):
-            raise Exception(patched_msg)
+    def mock_queue_email(**kwargs):
+        raise Exception(patched_msg)
 
-        with patch("couchers.notifications.background.queue_email", mock_queue_email):
-            with pytest.raises(Exception) as e:
-                with api_session(from_token) as api:
-                    api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=to_user.id))
-                handle_notifications_bg()
+    with patch("couchers.email._queue_email", mock_queue_email):
+        with pytest.raises(Exception) as e:
+            with api_session(from_token) as api:
+                api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=to_user.id))
+            process_jobs()
 
-        assert str(e.value) == patched_msg
+    assert str(e.value) == patched_msg
 
 
 def test_email_changed_confirmation_sent_to_new_email(db):
@@ -218,23 +197,23 @@ def test_email_changed_confirmation_sent_to_new_email(db):
     user, user_token = generate_user()
     user.new_email = f"{random_hex(12)}@couchers.org.invalid"
     user.new_email_token = confirmation_token
-    with patch("couchers.templates.v2.queue_email") as mock:
+    with mock_notification_email() as mock:
         send_email_changed_confirmation_to_new_email(user)
 
     assert mock.call_count == 1
-    _, kwargs = mock.call_args
-    assert "new email" in kwargs["subject"]
-    assert kwargs["recipient"] == user.new_email
-    assert user.name in kwargs["plain"]
-    assert user.name in kwargs["html"]
-    assert user.email in kwargs["plain"]
-    assert user.email in kwargs["html"]
-    assert "Your old email address is" in kwargs["plain"]
-    assert "Your old email address is" in kwargs["html"]
-    assert f"http://localhost:3000/confirm-email?token={confirmation_token}" in kwargs["plain"]
-    assert f"http://localhost:3000/confirm-email?token={confirmation_token}" in kwargs["html"]
-    assert "support@couchers.org" in kwargs["plain"]
-    assert "support@couchers.org" in kwargs["html"]
+    e = email_fields(mock)
+    assert "new email" in e.subject
+    assert e.recipient == user.new_email
+    assert user.name in e.plain
+    assert user.name in e.html
+    assert user.email in e.plain
+    assert user.email in e.html
+    assert "Your old email address is" in e.plain
+    assert "Your old email address is" in e.html
+    assert f"http://localhost:3000/confirm-email?token={confirmation_token}" in e.plain
+    assert f"http://localhost:3000/confirm-email?token={confirmation_token}" in e.html
+    assert "support@couchers.org" in e.plain
+    assert "support@couchers.org" in e.html
 
 
 def test_do_not_email_security(db):
@@ -319,11 +298,10 @@ def test_email_prefix_config(db, monkeypatch):
         )
 
     assert mock.call_count == 1
-    _, kwargs = mock.call_args
-
-    assert kwargs["sender_name"] == "Couchers.org"
-    assert kwargs["sender_email"] == "notify@couchers.org.invalid"
-    assert kwargs["subject"] == "[TEST] Thank you for your donation to Couchers.org!"
+    e = email_fields(mock)
+    assert e.sender_name == "Couchers.org"
+    assert e.sender_email == "notify@couchers.org.invalid"
+    assert e.subject == "[TEST] Thank you for your donation to Couchers.org!"
 
     new_config = config.copy()
     new_config["NOTIFICATION_EMAIL_SENDER"] = "TestCo"
@@ -343,11 +321,10 @@ def test_email_prefix_config(db, monkeypatch):
         )
 
     assert mock.call_count == 1
-    _, kwargs = mock.call_args
-
-    assert kwargs["sender_name"] == "TestCo"
-    assert kwargs["sender_email"] == "testco@testing.co.invalid"
-    assert kwargs["subject"] == "Thank you for your donation to Couchers.org!"
+    e = email_fields(mock)
+    assert e.sender_name == "TestCo"
+    assert e.sender_email == "testco@testing.co.invalid"
+    assert e.subject == "Thank you for your donation to Couchers.org!"
 
 
 def test_send_donation_email(db, monkeypatch):
@@ -368,7 +345,7 @@ def test_send_donation_email(db, monkeypatch):
     )
 
     with patch("couchers.email.smtp.smtplib.SMTP"):
-        handle_notifications_bg()
+        process_jobs()
 
     with session_scope() as session:
         email = session.execute(select(Email)).scalar_one()
diff --git a/app/backend/src/tests/test_events.py b/app/backend/src/tests/test_events.py
index 9112dd9d6e..0efa190bcd 100644
--- a/app/backend/src/tests/test_events.py
+++ b/app/backend/src/tests/test_events.py
@@ -1,5 +1,4 @@
 from datetime import timedelta
-from unittest.mock import patch
 
 import grpc
 import pytest
@@ -16,8 +15,10 @@
 from tests.test_communities import create_community, create_group
 from tests.test_fixtures import (  # noqa
     db,
+    email_fields,
     events_session,
     generate_user,
+    mock_notification_email,
     real_admin_session,
     testconfig,
     threads_session,
@@ -2244,17 +2245,14 @@ def test_community_invite_requests(db):
 
         event_id = res.event_id
 
-        with patch("couchers.email.queue_email") as mock:
+        with mock_notification_email() as mock:
             api.RequestCommunityInvite(events_pb2.RequestCommunityInviteReq(event_id=event_id))
         assert mock.call_count == 1
-        (_, _, recipient, subject, plain, html), _ = mock.call_args
-        assert recipient == "mods@couchers.org.invalid"
+        e = email_fields(mock)
+        assert e.recipient == "mods@couchers.org.invalid"
 
-        assert user_url in plain
-        assert user_url in html
-
-        assert event_url in plain
-        assert event_url in html
+        assert user_url in e.plain
+        assert event_url in e.plain
 
         # can't send another req
         with pytest.raises(grpc.RpcError) as e:
diff --git a/app/backend/src/tests/test_fixtures.py b/app/backend/src/tests/test_fixtures.py
index 873498b16d..3ada81cb0a 100644
--- a/app/backend/src/tests/test_fixtures.py
+++ b/app/backend/src/tests/test_fixtures.py
@@ -863,16 +863,16 @@ def fast_verify(hashed: bytes, password: bytes) -> bool:
             yield
 
 
-def handle_notifications_bg():
+def process_jobs():
     while process_job():
         pass
 
 
 @contextmanager
 def mock_notification_email():
-    with patch("couchers.notifications.background.queue_email") as mock:
+    with patch("couchers.email._queue_email") as mock:
         yield mock
-        handle_notifications_bg()
+        process_jobs()
 
 
 @dataclass
diff --git a/app/backend/src/tests/test_notifications.py b/app/backend/src/tests/test_notifications.py
index ab8c594f9d..c55b17c86e 100644
--- a/app/backend/src/tests/test_notifications.py
+++ b/app/backend/src/tests/test_notifications.py
@@ -29,9 +29,9 @@
     db,
     email_fields,
     generate_user,
-    handle_notifications_bg,
     mock_notification_email,
     notifications_session,
+    process_jobs,
     push_collector,
     session_scope,
     testconfig,
@@ -322,7 +322,7 @@ def test_list_notifications(db, push_collector):
         for i in range(17):
             c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=group_chat_id, text=f"Test message {i}"))
 
-    handle_notifications_bg()
+    process_jobs()
 
     all_notifs = []
     with notifications_session(token1) as notifications: