diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88850133b..5afc57017 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Rate limiter is now disabled for localhost/socket (unless remoteip plug is enabled)
 - Logger: default log level changed from `warn` to `info`.
 - Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
+- Allow account registration without an email
 - Default to `prepare: :unnamed` in the database configuration.
 - Instance stats are now loaded on startup instead of being empty until next hourly job.
 <details>
diff --git a/docs/administration/CLI_tasks/database.md b/docs/administration/CLI_tasks/database.md
index 51c7484ba..ff400c8ed 100644
--- a/docs/administration/CLI_tasks/database.md
+++ b/docs/administration/CLI_tasks/database.md
@@ -10,11 +10,11 @@
 Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once if the instance was created before Pleroma 1.0.5. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
 
 ```sh tab="OTP"
-./bin/pleroma_ctl database remove_embedded_objects [<options>]
+./bin/pleroma_ctl database remove_embedded_objects [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.database remove_embedded_objects [<options>]
+mix pleroma.database remove_embedded_objects [option ...]
 ```
 
 ### Options
@@ -28,11 +28,11 @@ This will prune remote posts older than 90 days (configurable with [`config :ple
     The disk space will only be reclaimed after `VACUUM FULL`. You may run out of disk space during the execution of the task or vacuuming if you don't have about 1/3rds of the database size free.
 
 ```sh tab="OTP"
-./bin/pleroma_ctl database prune_objects [<options>]
+./bin/pleroma_ctl database prune_objects [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.database prune_objects [<options>]
+mix pleroma.database prune_objects [option ...]
 ```
 
 ### Options
diff --git a/docs/administration/CLI_tasks/digest.md b/docs/administration/CLI_tasks/digest.md
index 1badda8c3..2eb31379e 100644
--- a/docs/administration/CLI_tasks/digest.md
+++ b/docs/administration/CLI_tasks/digest.md
@@ -5,11 +5,11 @@
 ## Send digest email since given date (user registration date by default) ignoring user activity status.
 
 ```sh tab="OTP"
- ./bin/pleroma_ctl digest test <nickname> [<since_date>]
+ ./bin/pleroma_ctl digest test <nickname> [since_date]
 ```
 
 ```sh tab="From Source"
-mix pleroma.digest test <nickname> [<since_date>]
+mix pleroma.digest test <nickname> [since_date]
 ```
 
 
diff --git a/docs/administration/CLI_tasks/emoji.md b/docs/administration/CLI_tasks/emoji.md
index a3207bc6c..efec8222c 100644
--- a/docs/administration/CLI_tasks/emoji.md
+++ b/docs/administration/CLI_tasks/emoji.md
@@ -5,11 +5,11 @@
 ## Lists emoji packs and metadata specified in the manifest
 
 ```sh tab="OTP"
-./bin/pleroma_ctl emoji ls-packs [<options>]
+./bin/pleroma_ctl emoji ls-packs [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.emoji ls-packs [<options>]
+mix pleroma.emoji ls-packs [option ...]
 ```
 
 
@@ -19,11 +19,11 @@ mix pleroma.emoji ls-packs [<options>]
 ## Fetch, verify and install the specified packs from the manifest into `STATIC-DIR/emoji/PACK-NAME`
 
 ```sh tab="OTP"
-./bin/pleroma_ctl emoji get-packs [<options>] <packs>
+./bin/pleroma_ctl emoji get-packs [option ...] <pack ...>
 ```
 
 ```sh tab="From Source"
-mix pleroma.emoji get-packs [<options>] <packs>
+mix pleroma.emoji get-packs [option ...] <pack ...>
 ```
 
 ### Options
diff --git a/docs/administration/CLI_tasks/instance.md b/docs/administration/CLI_tasks/instance.md
index 1a3b268be..52e264bb1 100644
--- a/docs/administration/CLI_tasks/instance.md
+++ b/docs/administration/CLI_tasks/instance.md
@@ -4,11 +4,11 @@
 
 ## Generate a new configuration file
 ```sh tab="OTP"
- ./bin/pleroma_ctl instance gen [<options>]
+ ./bin/pleroma_ctl instance gen [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.instance gen [<options>]
+mix pleroma.instance gen [option ...]
 ```
 
 
diff --git a/docs/administration/CLI_tasks/uploads.md b/docs/administration/CLI_tasks/uploads.md
index e36c94c38..6a15d22f6 100644
--- a/docs/administration/CLI_tasks/uploads.md
+++ b/docs/administration/CLI_tasks/uploads.md
@@ -4,11 +4,11 @@
 
 ## Migrate uploads from local to remote storage
 ```sh tab="OTP"
- ./bin/pleroma_ctl uploads migrate_local <target_uploader> [<options>]
+ ./bin/pleroma_ctl uploads migrate_local <target_uploader> [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.uploads migrate_local <target_uploader> [<options>]
+mix pleroma.uploads migrate_local <target_uploader> [option ...]
 ```
 
 ### Options
diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md
index da8363131..f535dad82 100644
--- a/docs/administration/CLI_tasks/user.md
+++ b/docs/administration/CLI_tasks/user.md
@@ -5,11 +5,11 @@
 ## Create a user
 
 ```sh tab="OTP"
-./bin/pleroma_ctl user new <email> [<options>]
+./bin/pleroma_ctl user new <nickname> <email> [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.user new <email> [<options>]
+mix pleroma.user new <nickname> <email> [option ...]
 ```
 
 
@@ -33,11 +33,11 @@ mix pleroma.user list
 
 ## Generate an invite link
 ```sh tab="OTP"
- ./bin/pleroma_ctl user invite [<options>]
+ ./bin/pleroma_ctl user invite [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.user invite [<options>]
+mix pleroma.user invite [option ...]
 ```
 
 
@@ -137,11 +137,11 @@ mix pleroma.user reset_password <nickname>
 
 ## Set the value of the given user's settings
 ```sh tab="OTP"
- ./bin/pleroma_ctl user set <nickname> [<options>]
+ ./bin/pleroma_ctl user set <nickname> [option ...]
 ```
 
 ```sh tab="From Source"
-mix pleroma.user set <nickname> [<options>]
+mix pleroma.user set <nickname> [option ...]
 ```
 
 ### Options
diff --git a/docs/administration/backup.md b/docs/administration/backup.md
index 692aa7368..be57bf74a 100644
--- a/docs/administration/backup.md
+++ b/docs/administration/backup.md
@@ -18,9 +18,8 @@
 6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
 7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
 8. Restart the Pleroma service.
-9. After you've restarted Pleroma, you will notice that postgres will take up more cpu resources than usual. A lot in fact. To fix this you must do a VACUUM ANLAYZE. This can also be done while the instance is still running like so: 
-   $ sudo -u postgres psql pleroma_database_name
-   pleroma=# VACUUM ANALYZE;
+9. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries.
+
 [^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
 
 ## Remove
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 32551f7b6..fb99af699 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -156,8 +156,8 @@ cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/conf.d/pleroma.conf
 ```
 
 ```sh tab="Debian/Ubuntu"
-cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.nginx
-ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx
+cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/pleroma.conf
+ln -s /etc/nginx/sites-available/pleroma.conf /etc/nginx/sites-enabled/pleroma.conf
 ```
 
 If your distro does not have either of those you can append `include /etc/nginx/pleroma.conf` to the end of the http section in /etc/nginx/nginx.conf and
diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex
index 3c870f876..6088fc71d 100644
--- a/lib/mix/tasks/pleroma/docs.ex
+++ b/lib/mix/tasks/pleroma/docs.ex
@@ -28,7 +28,7 @@ def run(_) do
   defp do_run(implementation) do
     start_pleroma()
 
-    with descriptions <- Pleroma.Config.Loader.load("config/description.exs"),
+    with descriptions <- Pleroma.Config.Loader.read("config/description.exs"),
          {:ok, file_path} <-
            Pleroma.Docs.Generator.process(
              implementation,
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index c6ca888d4..c3312507e 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -35,7 +35,7 @@ def run(["unfollow", target]) do
   def run(["list"]) do
     start_pleroma()
 
-    with {:ok, list} <- Relay.list() do
+    with {:ok, list} <- Relay.list(true) do
       list |> Enum.each(&shell_info(&1))
     else
       {:error, e} -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 397eb6e3f..6ca05f74e 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -308,6 +308,13 @@ def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
     |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
   end
 
+  def following_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
+    Queries.by_type("Follow")
+    |> where([a], fragment("?->>'state' = 'pending'", a.data))
+    |> where([a], a.actor == ^ap_id)
+    |> Repo.all()
+  end
+
   def restrict_deactivated_users(query) do
     deactivated_users =
       from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 55b5be488..719114671 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -35,6 +35,7 @@ def user_agent do
   # See http://elixir-lang.org/docs/stable/elixir/Application.html
   # for more information on OTP Applications
   def start(_type, _args) do
+    Pleroma.Config.Holder.save_default()
     Pleroma.HTML.compile_scrubbers()
     Config.DeprecationWarnings.warn()
     Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex
index f1a339703..f037d5d48 100644
--- a/lib/pleroma/config/holder.ex
+++ b/lib/pleroma/config/holder.ex
@@ -3,14 +3,33 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Config.Holder do
-  @config Pleroma.Config.Loader.load_and_merge()
+  @config Pleroma.Config.Loader.default_config()
 
-  @spec config() :: keyword()
-  def config, do: @config
+  @spec save_default() :: :ok
+  def save_default do
+    default_config =
+      if System.get_env("RELEASE_NAME") do
+        release_config =
+          [:code.root_dir(), "releases", System.get_env("RELEASE_VSN"), "releases.exs"]
+          |> Path.join()
+          |> Pleroma.Config.Loader.read()
 
-  @spec config(atom()) :: any()
-  def config(group), do: @config[group]
+        Pleroma.Config.Loader.merge(@config, release_config)
+      else
+        @config
+      end
 
-  @spec config(atom(), atom()) :: any()
-  def config(group, key), do: @config[group][key]
+    Pleroma.Config.put(:default_config, default_config)
+  end
+
+  @spec default_config() :: keyword()
+  def default_config, do: get_default()
+
+  @spec default_config(atom()) :: keyword()
+  def default_config(group), do: Keyword.get(get_default(), group)
+
+  @spec default_config(atom(), atom()) :: keyword()
+  def default_config(group, key), do: get_in(get_default(), [group, key])
+
+  defp get_default, do: Pleroma.Config.get(:default_config)
 end
diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index df2d18725..6ca6550bd 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -13,32 +13,28 @@ defmodule Pleroma.Config.Loader do
   ]
 
   if Code.ensure_loaded?(Config.Reader) do
-    @spec load(Path.t()) :: keyword()
-    def load(path), do: Config.Reader.read!(path)
+    @reader Config.Reader
 
-    defp do_merge(conf1, conf2), do: Config.Reader.merge(conf1, conf2)
+    def read(path), do: @reader.read!(path)
   else
     # support for Elixir less than 1.9
-    @spec load(Path.t()) :: keyword()
-    def load(path) do
+    @reader Mix.Config
+    def read(path) do
       path
-      |> Mix.Config.eval!()
+      |> @reader.eval!()
       |> elem(0)
     end
-
-    defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2)
   end
 
-  @spec load_and_merge() :: keyword()
-  def load_and_merge do
-    all_paths =
-      if Pleroma.Config.get(:release),
-        do: ["config/config.exs", "config/releases.exs"],
-        else: ["config/config.exs"]
+  @spec read(Path.t()) :: keyword()
 
-    all_paths
-    |> Enum.map(&load(&1))
-    |> Enum.reduce([], &do_merge(&2, &1))
+  @spec merge(keyword(), keyword()) :: keyword()
+  def merge(c1, c2), do: @reader.merge(c1, c2)
+
+  @spec default_config() :: keyword()
+  def default_config do
+    "config/config.exs"
+    |> read()
     |> filter()
   end
 
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index b6d80adb7..936bc9ab1 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -104,7 +104,7 @@ defp merge_and_update(setting) do
       key = ConfigDB.from_string(setting.key)
       group = ConfigDB.from_string(setting.group)
 
-      default = Config.Holder.config(group, key)
+      default = Config.Holder.default_config(group, key)
       value = ConfigDB.from_binary(setting.value)
 
       merged_value =
diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex
index 6508a7bdb..74f8b2615 100644
--- a/lib/pleroma/docs/json.ex
+++ b/lib/pleroma/docs/json.ex
@@ -15,7 +15,7 @@ def process(descriptions) do
   end
 
   def compile do
-    with config <- Pleroma.Config.Loader.load("config/description.exs") do
+    with config <- Pleroma.Config.Loader.read("config/description.exs") do
       config[:pleroma][:config_description]
       |> Pleroma.Docs.Generator.convert_to_strings()
       |> Jason.encode!()
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 5fe79333e..7531757f5 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -530,7 +530,14 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
   end
 
   def maybe_validate_required_email(changeset, true), do: changeset
-  def maybe_validate_required_email(changeset, _), do: validate_required(changeset, [:email])
+
+  def maybe_validate_required_email(changeset, _) do
+    if Pleroma.Config.get([:instance, :account_activation_required]) do
+      validate_required(changeset, [:email])
+    else
+      changeset
+    end
+  end
 
   defp put_ap_id(changeset) do
     ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index bb5542c89..729c23af7 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -60,15 +60,28 @@ def publish(%Activity{data: %{"type" => "Create"}} = activity) do
 
   def publish(_), do: {:error, "Not implemented"}
 
-  @spec list() :: {:ok, [String.t()]} | {:error, any()}
-  def list do
+  @spec list(boolean()) :: {:ok, [String.t()]} | {:error, any()}
+  def list(with_not_accepted \\ false) do
     with %User{} = user <- get_actor() do
-      list =
+      accepted =
         user
         |> User.following()
         |> Enum.map(fn entry -> URI.parse(entry).host end)
         |> Enum.uniq()
 
+      list =
+        if with_not_accepted do
+          without_accept =
+            user
+            |> Pleroma.Activity.following_requests_for_actor()
+            |> Enum.map(fn a -> URI.parse(a.data["object"]).host <> " (no Accept received)" end)
+            |> Enum.uniq()
+
+          accepted ++ without_accept
+        else
+          accepted
+        end
+
       {:ok, list}
     else
       error -> format_error(error)
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index de0755ee5..47b7d2da3 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -834,7 +834,7 @@ def config_show(conn, _params) do
       configs = ConfigDB.get_all_as_keyword()
 
       merged =
-        Config.Holder.config()
+        Config.Holder.default_config()
         |> ConfigDB.merge(configs)
         |> Enum.map(fn {group, values} ->
           Enum.map(values, fn {key, value} ->
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 8746273c4..348fdedf1 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -591,7 +591,7 @@ def validate_character_limit(full_payload, _attachments) do
     limit = Pleroma.Config.get([:instance, :limit])
     length = String.length(full_payload)
 
-    if length < limit do
+    if length <= limit do
       :ok
     else
       {:error, dgettext("errors", "The status is over the character limit")}
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index dc3b47415..88c997b9f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -76,7 +76,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   @doc "POST /api/v1/accounts"
   def create(
         %{assigns: %{app: app}} = conn,
-        %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
+        %{"username" => nickname, "password" => _, "agreement" => true} = params
       ) do
     params =
       params
@@ -93,7 +93,8 @@ def create(
       |> Map.put("bio", params["bio"] || "")
       |> Map.put("confirm", params["password"])
 
-    with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
+    with :ok <- validate_email_param(params),
+         {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
          {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
       json(conn, %{
         token_type: "Bearer",
@@ -114,6 +115,15 @@ def create(conn, _) do
     render_error(conn, :forbidden, "Invalid credentials")
   end
 
+  defp validate_email_param(%{"email" => _}), do: :ok
+
+  defp validate_email_param(_) do
+    case Pleroma.Config.get([:instance, :account_activation_required]) do
+      true -> {:error, %{"error" => "Missing parameters"}}
+      _ -> :ok
+    end
+  end
+
   @doc "GET /api/v1/accounts/verify_credentials"
   def verify_credentials(%{assigns: %{user: user}} = conn, _) do
     chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
diff --git a/priv/static/static/static-fe.css b/priv/static/static/static-fe.css
new file mode 100644
index 000000000..19c56387b
--- /dev/null
+++ b/priv/static/static/static-fe.css
@@ -0,0 +1,176 @@
+body {
+    background-color: #282c37;
+    font-family: sans-serif;
+    color: white;
+}
+
+main {
+    margin: 50px auto;
+    max-width: 960px;
+    padding: 40px;
+    background-color: #313543;
+    border-radius: 4px;
+}
+
+header {
+    margin: 50px auto;
+    max-width: 960px;
+    padding: 40px;
+    background-color: #313543;
+    border-radius: 4px;
+}
+
+.activity {
+    border-radius: 4px;
+    padding: 1em;
+    padding-bottom: 2em;
+    margin-bottom: 1em;
+}
+
+.avatar {
+    cursor: pointer;
+}
+
+.avatar img {
+    float: left;
+    border-radius: 4px;
+    margin-right: 4px;
+}
+
+.activity-content img, video, audio {
+    padding: 1em;
+    max-width: 800px;
+    max-height: 800px;
+}
+
+#selected {
+    background-color: #1b2735;
+}
+
+.counts dt, .counts dd {
+    float: left;
+    margin-left: 1em;
+}
+
+a {
+    color: white;
+}
+
+.h-card {
+    min-height: 48px;
+    margin-bottom: 8px;
+}
+
+header a, .h-card a {
+    text-decoration: none;
+}
+
+header a:hover, .h-card a:hover {
+    text-decoration: underline;
+}
+
+.display-name {
+    padding-top: 4px;
+    display: block;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    color: white;
+}
+
+/* keep emoji from being hilariously huge */
+.display-name img {
+    max-height: 1em;
+}
+
+.display-name .nickname {
+    padding-top: 4px;
+    display: block;
+}
+
+.nickname:hover {
+    text-decoration: none;
+}
+
+.pull-right {
+    float: right;
+}
+
+.collapse {
+    margin: 0;
+    width: auto;
+}
+
+h1 {
+    margin: 0;
+}
+
+h2 {
+    color: #9baec8;
+    font-weight: normal;
+    font-size: 20px;
+    margin-bottom: 40px;
+}
+
+form {
+    width: 100%;
+}
+
+input {
+    box-sizing: border-box;
+    width: 100%;
+    padding: 10px;
+    margin-top: 20px;
+    background-color: rgba(0,0,0,.1);
+    color: white;
+    border: 0;
+    border-bottom: 2px solid #9baec8;
+    font-size: 14px;
+}
+
+input:focus {
+    border-bottom: 2px solid #4b8ed8;
+}
+
+input[type="checkbox"] {
+    width: auto;
+}
+
+button {
+    box-sizing: border-box;
+    width: 100%;
+    color: white;
+    background-color: #419bdd;
+    border-radius: 4px;
+    border: none;
+    padding: 10px;
+    margin-top: 30px;
+    text-transform: uppercase;
+    font-weight: 500;
+    font-size: 16px;
+}
+
+.alert-danger {
+    box-sizing: border-box;
+    width: 100%;
+    color: #D8000C;
+    background-color: #FFD2D2;
+    border-radius: 4px;
+    border: none;
+    padding: 10px;
+    margin-top: 20px;
+    font-weight: 500;
+    font-size: 16px;
+}
+
+.alert-info {
+    box-sizing: border-box;
+    width: 100%;
+    color: #00529B;
+    background-color: #BDE5F8;
+    border-radius: 4px;
+    border: none;
+    padding: 10px;
+    margin-top: 20px;
+    font-weight: 500;
+    font-size: 16px;
+}
diff --git a/test/config/holder_test.exs b/test/config/holder_test.exs
index 2368d4856..15d48b5c7 100644
--- a/test/config/holder_test.exs
+++ b/test/config/holder_test.exs
@@ -7,8 +7,8 @@ defmodule Pleroma.Config.HolderTest do
 
   alias Pleroma.Config.Holder
 
-  test "config/0" do
-    config = Holder.config()
+  test "default_config/0" do
+    config = Holder.default_config()
     assert config[:pleroma][Pleroma.Uploaders.Local][:uploads] == "test/uploads"
     assert config[:tesla][:adapter] == Tesla.Mock
 
@@ -20,15 +20,15 @@ test "config/0" do
     refute config[:phoenix][:serve_endpoints]
   end
 
-  test "config/1" do
-    pleroma_config = Holder.config(:pleroma)
+  test "default_config/1" do
+    pleroma_config = Holder.default_config(:pleroma)
     assert pleroma_config[Pleroma.Uploaders.Local][:uploads] == "test/uploads"
-    tesla_config = Holder.config(:tesla)
+    tesla_config = Holder.default_config(:tesla)
     assert tesla_config[:adapter] == Tesla.Mock
   end
 
-  test "config/2" do
-    assert Holder.config(:pleroma, Pleroma.Uploaders.Local) == [uploads: "test/uploads"]
-    assert Holder.config(:tesla, :adapter) == Tesla.Mock
+  test "default_config/2" do
+    assert Holder.default_config(:pleroma, Pleroma.Uploaders.Local) == [uploads: "test/uploads"]
+    assert Holder.default_config(:tesla, :adapter) == Tesla.Mock
   end
 end
diff --git a/test/config/loader_test.exs b/test/config/loader_test.exs
index 4c93e5d4d..607572f4e 100644
--- a/test/config/loader_test.exs
+++ b/test/config/loader_test.exs
@@ -7,28 +7,13 @@ defmodule Pleroma.Config.LoaderTest do
 
   alias Pleroma.Config.Loader
 
-  test "load/1" do
-    config = Loader.load("test/fixtures/config/temp.secret.exs")
+  test "read/1" do
+    config = Loader.read("test/fixtures/config/temp.secret.exs")
     assert config[:pleroma][:first_setting][:key] == "value"
     assert config[:pleroma][:first_setting][:key2] == [Pleroma.Repo]
     assert config[:quack][:level] == :info
   end
 
-  test "load_and_merge/0" do
-    config = Loader.load_and_merge()
-
-    refute config[:pleroma][Pleroma.Repo]
-    refute config[:pleroma][Pleroma.Web.Endpoint]
-    refute config[:pleroma][:env]
-    refute config[:pleroma][:configurable_from_database]
-    refute config[:pleroma][:database]
-    refute config[:phoenix][:serve_endpoints]
-
-    assert config[:pleroma][:ecto_repos] == [Pleroma.Repo]
-    assert config[:pleroma][Pleroma.Uploaders.Local][:uploads] == "test/uploads"
-    assert config[:tesla][:adapter] == Tesla.Mock
-  end
-
   test "filter_group/2" do
     assert Loader.filter_group(:pleroma,
              pleroma: [
diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs
index ce31d1e87..01d04761d 100644
--- a/test/config/transfer_task_test.exs
+++ b/test/config/transfer_task_test.exs
@@ -70,7 +70,7 @@ test "transfer config values for 1 group and some keys" do
 
     assert Application.get_env(:quack, :level) == :info
     assert Application.get_env(:quack, :meta) == [:none]
-    default = Pleroma.Config.Holder.config(:quack, :webhook_url)
+    default = Pleroma.Config.Holder.default_config(:quack, :webhook_url)
     assert Application.get_env(:quack, :webhook_url) == default
 
     on_exit(fn ->
diff --git a/test/fixtures/relay/accept-follow.json b/test/fixtures/relay/accept-follow.json
new file mode 100644
index 000000000..1b166f2da
--- /dev/null
+++ b/test/fixtures/relay/accept-follow.json
@@ -0,0 +1,15 @@
+{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "actor": "https://relay.mastodon.host/actor",
+  "id": "https://relay.mastodon.host/activities/ec477b69-db26-4019-923e-cf809de516ab",
+  "object": {
+    "actor": "{{ap_id}}",
+    "id": "{{activity_id}}",
+    "object": "https://relay.mastodon.host/actor",
+    "type": "Follow"
+  },
+  "to": [
+    "{{ap_id}}"
+  ],
+  "type": "Accept"
+}
\ No newline at end of file
diff --git a/test/fixtures/relay/relay.json b/test/fixtures/relay/relay.json
new file mode 100644
index 000000000..77ae7f06c
--- /dev/null
+++ b/test/fixtures/relay/relay.json
@@ -0,0 +1,20 @@
+{
+  "@context": "https://www.w3.org/ns/activitystreams",
+  "endpoints": {
+    "sharedInbox": "https://relay.mastodon.host/inbox"
+  },
+  "followers": "https://relay.mastodon.host/followers",
+  "following": "https://relay.mastodon.host/following",
+  "inbox": "https://relay.mastodon.host/inbox",
+  "name": "ActivityRelay",
+  "type": "Application",
+  "id": "https://relay.mastodon.host/actor",
+  "publicKey": {
+    "id": "https://relay.mastodon.host/actor#main-key",
+    "owner": "https://relay.mastodon.host/actor",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNYHNYETdsZFsdcTTEQo\nlsTP9yz4ZjOGrQ1EjoBA7NkjBUxxUAPxZbBjWPT9F+L3IbCX1IwI2OrBM/KwDlug\nV41xnjNmxSCUNpxX5IMZtFaAz9/hWu6xkRTs9Bh6XWZxi+db905aOqszb9Mo3H2g\nQJiAYemXwTh2kBO7XlBDbsMhO11Tu8FxcWTMdR54vlGv4RoiVh8dJRa06yyiTs+m\njbj/OJwR06mHHwlKYTVT/587NUb+e9QtCK6t/dqpyZ1o7vKSK5PSldZVjwHt292E\nXVxFOQVXi7JazTwpdPww79ECSe8ThCykOYCNkm3RjsKuLuokp7Vzq1hXIoeBJ7z2\ndU8vbgg/JyazsOsTxkVs2nd2i9/QW2SH+sX9X3357+XLSCh/A8p8fv/GeoN7UCXe\n4DWHFJZDlItNFfymiPbQH+omuju8qrfW9ngk1gFeI2mahXFQVu7x0qsaZYioCIrZ\nwq0zPnUGl9u0tLUXQz+ZkInRrEz+JepDVauy5/3QdzMLG420zCj/ygDrFzpBQIrc\n62Z6URueUBJox0UK71K+usxqOrepgw8haFGMvg3STFo34pNYjoK4oKO+h5qZEDFD\nb1n57t6JWUaBocZbJns9RGASq5gih+iMk2+zPLWp1x64yvuLsYVLPLBHxjCxS6lA\ndWcopZHi7R/OsRz+vTT7420CAwEAAQ==\n-----END PUBLIC KEY-----"
+  },
+  "summary": "ActivityRelay bot",
+  "preferredUsername": "relay",
+  "url": "https://relay.mastodon.host/actor"
+}
\ No newline at end of file
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index f1300637e..8a09e089b 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -1287,6 +1287,10 @@ def get("http://example.com/rel_me/error", _, _, _) do
     {:ok, %Tesla.Env{status: 404, body: ""}}
   end
 
+  def get("https://relay.mastodon.host/actor", _, _, _) do
+    {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/relay/relay.json")}}
+  end
+
   def get(url, query, body, headers) do
     {:error,
      "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
@@ -1299,6 +1303,10 @@ def get(url, query, body, headers) do
 
   def post(url, query \\ [], body \\ [], headers \\ [])
 
+  def post("https://relay.mastodon.host/inbox", _, _, _) do
+    {:ok, %Tesla.Env{status: 200, body: ""}}
+  end
+
   def post("http://example.org/needs_refresh", _, _, _) do
     {:ok,
      %Tesla.Env{
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index 08855f245..d3d88467d 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -38,6 +38,9 @@ test "relay is followed" do
       assert activity.data["type"] == "Follow"
       assert activity.data["actor"] == local_user.ap_id
       assert activity.data["object"] == target_user.ap_id
+
+      :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
+      assert_receive {:mix_shell, :info, ["mastodon.example.org (no Accept received)"]}
     end
   end
 
diff --git a/test/user_test.exs b/test/user_test.exs
index 84d7f5727..b07fed42b 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -412,7 +412,11 @@ test "it sends a welcome message if it is set" do
       assert activity.actor == welcome_user.ap_id
     end
 
-    test "it requires an email, name, nickname and password, bio is optional" do
+    clear_config([:instance, :account_activation_required])
+
+    test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
+      Pleroma.Config.put([:instance, :account_activation_required], true)
+
       @full_user_data
       |> Map.keys()
       |> Enum.each(fn key ->
@@ -423,6 +427,19 @@ test "it requires an email, name, nickname and password, bio is optional" do
       end)
     end
 
+    test "it requires an name, nickname and password, bio and email are optional when account_activation_required is disabled" do
+      Pleroma.Config.put([:instance, :account_activation_required], false)
+
+      @full_user_data
+      |> Map.keys()
+      |> Enum.each(fn key ->
+        params = Map.delete(@full_user_data, key)
+        changeset = User.register_changeset(%User{}, params)
+
+        assert if key in [:bio, :email], do: changeset.valid?, else: not changeset.valid?
+      end)
+    end
+
     test "it restricts certain nicknames" do
       [restricted_name | _] = Pleroma.Config.get([User, :restricted_nicknames])
 
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 9151034da..b2352538a 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -341,6 +341,44 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
       assert "ok" == json_response(conn, 200)
       assert Instances.reachable?(sender_url)
     end
+
+    test "accept follow activity", %{conn: conn} do
+      Pleroma.Config.put([:instance, :federating], true)
+      relay = Relay.get_actor()
+
+      assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
+
+      followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
+      relay = refresh_record(relay)
+
+      accept =
+        File.read!("test/fixtures/relay/accept-follow.json")
+        |> String.replace("{{ap_id}}", relay.ap_id)
+        |> String.replace("{{activity_id}}", activity.data["id"])
+
+      assert "ok" ==
+               conn
+               |> assign(:valid_signature, true)
+               |> put_req_header("content-type", "application/activity+json")
+               |> post("/inbox", accept)
+               |> json_response(200)
+
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+
+      assert Pleroma.FollowingRelationship.following?(
+               relay,
+               followed_relay
+             )
+
+      Mix.shell(Mix.Shell.Process)
+
+      on_exit(fn ->
+        Mix.shell(Mix.Shell.IO)
+      end)
+
+      :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
+      assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
+    end
   end
 
   describe "/users/:nickname/inbox" do
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 299d968db..b80523160 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -202,13 +202,15 @@ test "it returns error when status is empty and no attachments" do
                CommonAPI.post(user, %{"status" => ""})
     end
 
-    test "it returns error when character limit is exceeded" do
+    test "it validates character limits are correctly enforced" do
       Pleroma.Config.put([:instance, :limit], 5)
 
       user = insert(:user)
 
       assert {:error, "The status is over the character limit"} =
                CommonAPI.post(user, %{"status" => "foobar"})
+
+      assert {:ok, activity} = CommonAPI.post(user, %{"status" => "12345"})
     end
 
     test "it can handle activities that expire" do
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 57d0f4416..7f7d8cea3 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -601,6 +601,8 @@ test "blocking / unblocking a user" do
       [valid_params: valid_params]
     end
 
+    clear_config([:instance, :account_activation_required])
+
     test "Account registration via Application", %{conn: conn} do
       conn =
         post(conn, "/api/v1/apps", %{
@@ -685,7 +687,7 @@ test "returns bad_request if missing required params", %{
       assert json_response(res, 200)
 
       [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
-      |> Stream.zip(valid_params)
+      |> Stream.zip(Map.delete(valid_params, :email))
       |> Enum.each(fn {ip, {attr, _}} ->
         res =
           conn
@@ -697,6 +699,54 @@ test "returns bad_request if missing required params", %{
       end)
     end
 
+    clear_config([:instance, :account_activation_required])
+
+    test "returns bad_request if missing email params when :account_activation_required is enabled",
+         %{conn: conn, valid_params: valid_params} do
+      Pleroma.Config.put([:instance, :account_activation_required], true)
+
+      app_token = insert(:oauth_token, user: nil)
+      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+      res =
+        conn
+        |> Map.put(:remote_ip, {127, 0, 0, 5})
+        |> post("/api/v1/accounts", Map.delete(valid_params, :email))
+
+      assert json_response(res, 400) == %{"error" => "Missing parameters"}
+
+      res =
+        conn
+        |> Map.put(:remote_ip, {127, 0, 0, 6})
+        |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
+
+      assert json_response(res, 400) == %{"error" => "{\"email\":[\"can't be blank\"]}"}
+    end
+
+    test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
+      app_token = insert(:oauth_token, user: nil)
+      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+      res =
+        conn
+        |> Map.put(:remote_ip, {127, 0, 0, 7})
+        |> post("/api/v1/accounts", Map.delete(valid_params, :email))
+
+      assert json_response(res, 200)
+    end
+
+    test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
+      app_token = insert(:oauth_token, user: nil)
+      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+      res =
+        conn
+        |> Map.put(:remote_ip, {127, 0, 0, 8})
+        |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
+
+      assert json_response(res, 200)
+    end
+
     test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
       conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")