diff --git a/man/nheko.1.adoc b/man/nheko.1.adoc
index cc4b8f748c12ed40edf6df1d830bba8752a11860..5bd5d6227c5e2bd3d52f24f65c96948046288c4e 100644
--- a/man/nheko.1.adoc
+++ b/man/nheko.1.adoc
@@ -229,6 +229,14 @@ Inserts `┯━┯╭( º _ º╭)`
 */sovietflip*::
 Inserts `ノ┬─┬ノ ︵ ( \\o°o)\\`
 
+=== User management
+
+*/ignore* _<username>_::
+Ignore a user, invites from them are also rejected.
+
+*/unignore* _<username>_::
+Stops ignoring a user.
+
 === Advanced
 
 */clear-timeline*::
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index f66df94f99a2233d2c31230f477130e943835e30..0a25549174b1e3680f5470d9da3aa35a66c9ed7d 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -277,6 +277,16 @@ Item {
 
             onClicked: Rooms.declineInvite(roomPreview.roomid)
         }
+        FlatButton {
+            Layout.alignment: Qt.AlignHCenter
+            text: qsTr("decline invite and ignore user")
+            visible: roomPreview && roomPreview.isInvite
+
+            onClicked: {
+                var inviter = TimelineManager.getGlobalUserProfile(roomPreview.inviterUserId)
+                inviter.ignored = true
+            }
+        }
         FlatButton {
             Layout.alignment: Qt.AlignHCenter
             text: qsTr("leave")
diff --git a/src/CommandCompleter.cpp b/src/CommandCompleter.cpp
index e1b91a8c77b95265728f2dc777dcdb751a551e70..ee666559c290e5008ad9efc36908b732edddaade 100644
--- a/src/CommandCompleter.cpp
+++ b/src/CommandCompleter.cpp
@@ -99,6 +99,8 @@ CommandCompleter::data(const QModelIndex &index, int role) const
                 return QStringLiteral("/converttoroom");
             case Ignore:
                 return QStringLiteral("/ignore");
+            case Unignore:
+                return QStringLiteral("/unignore");
             default:
                 return {};
             }
@@ -174,6 +176,8 @@ CommandCompleter::data(const QModelIndex &index, int role) const
                 return QStringLiteral("/converttoroom");
             case Ignore:
                 return QStringLiteral("/ignore <@userid>");
+            case Unignore:
+                return QStringLiteral("/unignore <@userid>");
             default:
                 return {};
             }
@@ -248,7 +252,9 @@ CommandCompleter::data(const QModelIndex &index, int role) const
             case ConvertToRoom:
                 return tr("Convert this direct chat into a room.");
             case Ignore:
-                return tr("Ignores a user.");
+                return tr("Ignore a user.");
+            case Unignore:
+                return tr("Stop ignoring a user.");
             default:
                 return {};
             }
diff --git a/src/CommandCompleter.h b/src/CommandCompleter.h
index 325ebc6d817bfd33a5004e56324a618bdc774c1c..35ce2b36241b0da8a73bd0ba977bea098199eecf 100644
--- a/src/CommandCompleter.h
+++ b/src/CommandCompleter.h
@@ -52,6 +52,7 @@ public:
         ConvertToDm,
         ConvertToRoom,
         Ignore,
+        Unignore,
         COUNT,
     };
 
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index ca320d04a414d42c22d920c0fe7c35924aefb0dc..e93f279521968bda31ce23d52f50e82130aa47c6 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -241,7 +241,8 @@ InputBar::updateTextContentProperties(const QString &t)
                                              QStringLiteral("goto"),
                                              QStringLiteral("converttodm"),
                                              QStringLiteral("converttoroom"),
-                                             QStringLiteral("ignore")};
+                                             QStringLiteral("ignore"),
+                                             QStringLiteral("unignore")};
     bool hasInvalidCommand    = !commandName.isNull() && !validCommands.contains(commandName);
     bool hasIncompleteCommand = hasInvalidCommand && '/' + commandName == t;
 
@@ -940,15 +941,9 @@ InputBar::command(const QString &command, QString args)
     } else if (command == QLatin1String("converttoroom")) {
         utils::removeDirectFromRoom(this->room->roomId());
     } else if (command == QLatin1String("ignore")) {
-        QSharedPointer<UserProfile> user(
-          new UserProfile(QString(), args, TimelineViewManager::instance()));
-        connect(user.get(), &UserProfile::failedToFetchProfile, [args] {
-            MainWindow::instance()->showNotification(tr("Failed to fetch user %1").arg(args));
-        });
-        connect(user.get(), &UserProfile::globalUsernameRetrieved, [user](const QString &user_id) {
-            Q_UNUSED(user_id)
-            user->setIgnored(true);
-        });
+        this->setArgIgnored(args, true);
+    } else if (command == QLatin1String("unignore")) {
+        this->setArgIgnored(args, false);
     } else {
         return false;
     }
@@ -956,6 +951,23 @@ InputBar::command(const QString &command, QString args)
     return true;
 }
 
+void
+InputBar::setArgIgnored(const QString &user, const bool ignored)
+{
+    QSharedPointer<UserProfile> profile(
+      new UserProfile(QString(), user, TimelineViewManager::instance()));
+    connect(profile.get(), &UserProfile::failedToFetchProfile, [user] {
+        MainWindow::instance()->showNotification(tr("Failed to fetch user %1").arg(user));
+    });
+
+    connect(profile.get(),
+            &UserProfile::globalUsernameRetrieved,
+            [profile, ignored](const QString &user_id) {
+                Q_UNUSED(user_id)
+                profile->setIgnored(ignored);
+            });
+}
+
 MediaUpload::MediaUpload(std::unique_ptr<QIODevice> source_,
                          const QString &mimetype,
                          const QString &originalFilename,
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index b15377fcf660c9cb133fc4e5bf5c5f13fa36bc71..cbde0e07bb107df4371c2262c8c16a0a0b01fd47 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -283,6 +283,8 @@ private:
 
     void updateTextContentProperties(const QString &t);
 
+    void setArgIgnored(const QString &user, const bool ignored);
+
     QTimer typingRefresh_;
     QTimer typingTimeout_;
     TimelineModel *room;
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 34bf3f9a719446f9e8b37141bb3a90b327db0be5..2294864f414803f53c513fcf52c4dc2d4cd08b43 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -197,6 +197,8 @@ public:
         return instance_;
     }
 
+    static FilteredRoomlistModel *instance() { return instance_; }
+
     bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
     bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override;
 
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 5c63deb29d13c00863660ca1802d82a97753a525..ffb69aa42eddc2c3f67e47bc5fbc032b7ad1bd28 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -278,6 +278,14 @@ UserProfile::setIgnored(bool ignore)
                 .arg(userid, QString::fromStdString(e->matrix_error.error)));
         }
     });
+
+    if (ignore) {
+        const QHash<QString, RoomInfo> invites = cache::invites();
+
+        for (auto room = invites.keyBegin(), end = invites.keyEnd(); room != end; room++) {
+            FilteredRoomlistModel::instance()->declineInvite(*room);
+        }
+    }
 }
 
 void