diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6eb928c6..b49f77428 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ## Added
 - Config: HTTP timeout options, :pool\_timeout and :receive\_timeout
 - Added statistic gathering about instances which do/don't have signed fetches when they request from us
+- Ability to set a default post expiry time, after which the post will be deleted. If used in concert with ActivityExpiration MRF, the expiry which comes _sooner_ will be applied.
 
 ## Changed
 - MastoAPI: Accept BooleanLike input on `/api/v1/accounts/:id/follow` (fixes follows with mastodon.py)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index eb907a2d8..b0ab9d0cd 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -151,6 +151,7 @@ defmodule Pleroma.User do
     field(:is_suggested, :boolean, default: false)
     field(:last_status_at, :naive_datetime)
     field(:language, :string)
+    field(:status_ttl_days, :integer, default: nil)
 
     embeds_one(
       :notification_settings,
@@ -516,7 +517,8 @@ def update_changeset(struct, params \\ %{}) do
         :pleroma_settings_store,
         :is_discoverable,
         :actor_type,
-        :disclose_client
+        :disclose_client,
+        :status_ttl_days
       ]
     )
     |> unique_constraint(:nickname)
@@ -524,6 +526,7 @@ def update_changeset(struct, params \\ %{}) do
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, min: 1, max: name_limit)
     |> validate_inclusion(:actor_type, ["Person", "Service"])
+    |> validate_number(:status_ttl_days, greater_than: 0)
     |> put_fields()
     |> put_emoji()
     |> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 359a73ac0..e20f57fec 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -700,7 +700,13 @@ defp update_credentials_request do
           description:
             "Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
         },
-        actor_type: ActorType
+        actor_type: ActorType,
+        status_ttl_days: %Schema{
+          type: :integer,
+          nullable: true,
+          description:
+            "Number of days after which statuses will be deleted. Set to -1 to disable."
+        }
       },
       example: %{
         bot: false,
@@ -720,7 +726,8 @@ defp update_credentials_request do
         allow_following_move: false,
         also_known_as: ["https://foo.bar/users/foo"],
         discoverable: false,
-        actor_type: "Person"
+        actor_type: "Person",
+        status_ttl_days: 30
       }
     }
   end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 5d3ac9cd0..2693eaceb 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -109,6 +109,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
           }
         }
       },
+      akkoma: %Schema{
+        type: :object,
+        properties: %{
+          note_ttl_days: %Schema{type: :integer}
+        }
+      },
       source: %Schema{
         type: :object,
         properties: %{
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 946c8544f..80497a252 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -175,6 +175,11 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
       value -> {:ok, value}
     end
 
+    status_ttl_days_value = fn
+      -1 -> {:ok, nil}
+      value -> {:ok, value}
+    end
+
     user_params =
       [
         :no_rich_text,
@@ -215,7 +220,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
       # Note: param name is indeed :discoverable (not an error)
       |> Maps.put_if_present(:is_discoverable, params[:discoverable])
       |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
+      |> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value)
 
+    IO.inspect(user_params)
     # What happens here:
     #
     # We want to update the user through the pipeline, but the ActivityPub
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 31f3b3a8d..338a35052 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -171,6 +171,16 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
       Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
       |> put_application(conn)
 
+    expires_in_seconds =
+      if is_nil(user.status_ttl_days),
+        do: nil,
+        else: 60 * 60 * 24 * user.status_ttl_days
+
+    params =
+      if is_nil(expires_in_seconds),
+        do: params,
+        else: Map.put(params, :expires_in, expires_in_seconds)
+
     with {:ok, activity} <- CommonAPI.post(user, params) do
       try_render(conn, "show.json",
         activity: activity,
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index a04ffaaf3..653a50e20 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -287,7 +287,8 @@ defp do_render("show.json", %{user: user} = opts) do
       },
       last_status_at: user.last_status_at,
       akkoma: %{
-        instance: render("instance.json", %{instance: instance})
+        instance: render("instance.json", %{instance: instance}),
+        status_ttl_days: user.status_ttl_days
       },
       # Pleroma extensions
       # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 929641b84..cc58f803e 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -65,7 +65,7 @@ defp get_replied_to_activities(activities) do
   # This should be removed in a future version of Pleroma. Pleroma-FE currently
   # depends on this field, as well.
   defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
-    use Bitwise
+    import Bitwise
 
     :erlang.crc32(context)
     |> band(bnot(0x8000_0000))
diff --git a/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs
index 43bc7100b..3b04bb4f3 100644
--- a/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs
+++ b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs
@@ -14,14 +14,14 @@ def change do
       from(u in User,
         where: u.local == true,
         where: is_nil(u.keys),
-        select: u
+        select: u.id
       )
 
     Repo.stream(query)
     |> Enum.each(fn user ->
       with {:ok, pem} <- Keys.generate_rsa_pem() do
-        Ecto.Changeset.cast(user, %{keys: pem}, [:keys])
-        |> Repo.update()
+        Ecto.Changeset.cast(%User{id: user}, %{keys: pem}, [:keys])
+        |> Repo.update(returning: false)
       end
     end)
   end
diff --git a/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs b/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs
new file mode 100644
index 000000000..95c585cf4
--- /dev/null
+++ b/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.AddPerUserPostExpiry do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add(:status_ttl_days, :integer, null: true)
+    end
+  end
+end
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index ea6ace69f..cda4c9e03 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -126,6 +126,35 @@ test "posting a status", %{conn: conn} do
       )
     end
 
+    test "automatically setting a post expiry if status_ttl_days is set" do
+      user = insert(:user, status_ttl_days: 1)
+      %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
+
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("api/v1/statuses", %{
+          "status" => "aa chikichiki banban"
+        })
+
+      assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
+
+      activity = Activity.get_by_id_with_object(id)
+      {:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
+
+      assert Timex.diff(
+               expires_at,
+               DateTime.utc_now(),
+               :hours
+             ) == 23
+
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: id},
+        scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
+      )
+    end
+
     test "it fails to create a status if `expires_in` is less or equal than an hour", %{
       conn: conn
     } do
diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
index 606467fa9..435782d0a 100644
--- a/test/pleroma/web/mastodon_api/update_credentials_test.exs
+++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs
@@ -209,6 +209,26 @@ test "updates the user's name", %{conn: conn} do
       assert update_activity.data["object"]["name"] == "markorepairs"
     end
 
+    test "updates the user's default post expiry", %{conn: conn} do
+      conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "1"})
+
+      assert user_data = json_response_and_validate_schema(conn, 200)
+      assert user_data["akkoma"]["status_ttl_days"] == 1
+    end
+
+    test "resets the user's default post expiry", %{conn: conn} do
+      conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "-1"})
+
+      assert user_data = json_response_and_validate_schema(conn, 200)
+      assert is_nil(user_data["akkoma"]["status_ttl_days"])
+    end
+
+    test "does not allow negative integers other than -1 for TTL", %{conn: conn} do
+      conn = patch(conn, "/api/v1/accounts/update_credentials", %{"status_ttl_days" => "-2"})
+
+      assert user_data = json_response_and_validate_schema(conn, 403)
+    end
+
     test "updates the user's AKAs", %{conn: conn} do
       conn =
         patch(conn, "/api/v1/accounts/update_credentials", %{
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
index f4a5f4d50..c9036d67d 100644
--- a/test/pleroma/web/mastodon_api/views/account_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -37,7 +37,8 @@ test "Represent a user account" do
         inserted_at: ~N[2017-08-15 15:47:06.597036],
         emoji: %{"karjalanpiirakka" => "/file.png"},
         raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"",
-        also_known_as: ["https://shitposter.zone/users/shp"]
+        also_known_as: ["https://shitposter.zone/users/shp"],
+        status_ttl_days: 5
       })
 
     insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}})
@@ -61,7 +62,8 @@ test "Represent a user account" do
             "version" => "2.1"
           },
           favicon: nil
-        }
+        },
+        status_ttl_days: 5
       },
       avatar: "http://localhost:4001/images/avi.png",
       avatar_static: "http://localhost:4001/images/avi.png",
@@ -243,7 +245,8 @@ test "Represent a Service(bot) account" do
           name: "localhost",
           favicon: "http://localhost:4001/favicon.png",
           nodeinfo: %{version: "2.0"}
-        }
+        },
+        status_ttl_days: nil
       },
       pleroma: %{
         ap_id: user.ap_id,