diff --git a/config/config.sample.yaml b/config/config.sample.yaml
index edb0b2e6994a21f6a89afdd6996173cfd7512a19..1baa13ea20cf4b8bc09e46122d94388af0719be8 100644
--- a/config/config.sample.yaml
+++ b/config/config.sample.yaml
@@ -38,6 +38,10 @@ bridge:
   disableInviteNotifications: false
   # Auto-determine the language of code blocks (this can be CPU-intensive)
   determineCodeLanguage: false
+  # MXID of an admin user that will be PMd if the bridge experiences problems. Optional
+  adminMxid: '@admin:localhost'
+  # The message to send to the bridge admin if the Discord token is not valid
+  invalidTokenMessage: 'Your Discord bot token seems to be invalid, and the bridge cannot function. Please update it in your bridge settings and restart the bridge'
 # Authentication configuration for the discord bot.
 auth:
   # This MUST be a string (wrapped in quotes)
@@ -113,4 +117,4 @@ ghosts:
 metrics:
     enable: false
     port: 9001
-    host: "127.0.0.1"
\ No newline at end of file
+    host: "127.0.0.1"
diff --git a/src/bot.ts b/src/bot.ts
index 58702274186002d05688938437b2c48fa895c904..73c0345ad87f8b733fe848901127159b6a7baed2 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -31,7 +31,7 @@ import { Log } from "./log";
 import * as Discord from "better-discord.js";
 import * as mime from "mime";
 import { IMatrixEvent, IMatrixMediaInfo, IMatrixMessage } from "./matrixtypes";
-import { Appservice, Intent } from "matrix-bot-sdk";
+import { Appservice, Intent, MatrixClient } from "matrix-bot-sdk";
 import { DiscordCommandHandler } from "./discordcommandhandler";
 import { MetricPeg } from "./metrics";
 import { Lock } from "./structures/lock";
@@ -44,6 +44,10 @@ const MIN_PRESENCE_UPDATE_DELAY = 250;
 const TYPING_TIMEOUT_MS = 30 * 1000;
 const CACHE_LIFETIME = 90000;
 
+// how often do we retry to connect on startup
+const INITIAL_FALLOFF_SECONDS = 5;
+const MAX_FALLOFF_SECONDS = 5 * 60; // 5 minutes
+
 // TODO: This is bad. We should be serving the icon from the own homeserver.
 const MATRIX_ICON_URL = "https://matrix.org/_matrix/media/r0/download/matrix.org/mlxoESwIsTbJrfXyAAogrNxA";
 class ChannelLookupResult {
@@ -128,6 +132,7 @@ export class DiscordBot {
         private config: DiscordBridgeConfig,
         private bridge: Appservice,
         private store: DiscordStore,
+        private adminNotifier?: AdminNotifier,
     ) {
 
         // create handlers
@@ -146,6 +151,12 @@ export class DiscordBot {
         this.discordMessageQueue = {};
         this.channelLock = new Lock(this.config.limits.discordSendDelay);
         this.lastEventIds = {};
+
+        if (!this.adminNotifier && config.bridge.adminMxid) {
+            this.adminNotifier = new AdminNotifier(
+                this.bridge.botClient, config.bridge.adminMxid
+            );
+        }
     }
 
     get ClientFactory(): DiscordClientFactory {
@@ -387,6 +398,31 @@ export class DiscordBot {
         }
     }
 
+    public async start(): Promise<void> {
+        return this._start(INITIAL_FALLOFF_SECONDS);
+    }
+
+    private async _start(falloffSeconds: number, isRetry = false): Promise<void> {
+        try {
+            await this.init();
+            await this.run();
+        } catch (err) {
+            if (err.code === 'TOKEN_INVALID' && !isRetry) {
+                await this.adminNotifier?.notify(this.config.bridge.invalidTokenMessage);
+            }
+
+            // no more than 5 minutes
+            const newFalloffSeconds = Math.min(falloffSeconds * 2, MAX_FALLOFF_SECONDS);
+            log.error(`Failed do start Discordbot: ${err.code}. Will try again in ${newFalloffSeconds} seconds`);
+            await new Promise((r, _) => setTimeout(r, newFalloffSeconds * 1000));
+            return this._start(newFalloffSeconds, true);
+        }
+
+        if (isRetry) {
+            await this.adminNotifier?.notify(`The token situation is now resolved and the bridge is running correctly`);
+        }
+    }
+
     public async stop(): Promise<void> {
         this._bot = undefined;
     }
@@ -1203,3 +1239,50 @@ export class DiscordBot {
         MetricPeg.get.setRemoteMonthlyActiveUsers(state.activeUsers);
     }
 }
+
+class AdminNotifier {
+    constructor(
+        private client:    MatrixClient,
+        private adminMxid: string,
+    ) {}
+
+    public async notify(message: string) {
+        const roomId = await this.ensureDMRoom(this.adminMxid);
+        await this.client.sendText(roomId, message)
+    }
+
+    private async findDMRoom(targetMxid: string): Promise<string|undefined> {
+        const rooms = await this.client.getJoinedRooms();
+        const roomsWithMembers = await Promise.all(rooms.map(async (id) => {
+            return {
+                id,
+                memberships: await this.client.getRoomMembers(id, undefined, ['join', 'invite']),
+            }
+        }));
+
+        return roomsWithMembers.find(
+            room => room.memberships.length == 2
+                 && !!room.memberships.find(member => member.stateKey === targetMxid)
+        )?.id;
+    }
+
+    private async ensureDMRoom(mxid: string): Promise<string> {
+        const existing = await this.findDMRoom(mxid);
+        if (existing) {
+            log.verbose(`Found existing DM room with ${mxid}: ${existing}`);
+            return existing;
+        }
+
+        const roomId = await this.client.createRoom();
+        try {
+            await this.client.inviteUser(mxid, roomId);
+        } catch (err) {
+            log.verbose(`Failed to invite ${mxid} to ${roomId}, cleaning up`);
+            this.client.leaveRoom(roomId); // no point awaiting it, nothing we can do if we fail
+            throw err;
+        }
+
+        log.verbose(`Created ${roomId} to DM with ${mxid}`);
+        return roomId;
+    }
+}
diff --git a/src/config.ts b/src/config.ts
index 216d531eaa23879c7c74a0d2bfe4e3376624f2af..96ebf97a003f4219e33822cee0dc4771c347967b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -100,6 +100,8 @@ class DiscordBridgeConfigBridge {
     public determineCodeLanguage: boolean = false;
     public activityTracker: UserActivityTrackerConfig = UserActivityTrackerConfig.DEFAULT;
     public userLimit: number|null = null;
+    public adminMxid: string|null = null;
+    public invalidTokenMessage: string = 'Your Discord token is invalid';
 }
 
 export class DiscordBridgeConfigDatabase {
diff --git a/src/discordas.ts b/src/discordas.ts
index c863c3e1e1605ce693e97f8649fdba973971b4c0..951b4fb9db8de3a5535143ba33eca5ce9182ca8a 100644
--- a/src/discordas.ts
+++ b/src/discordas.ts
@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
-import { Appservice, IAppserviceRegistration, LogService } from "matrix-bot-sdk";
+import { Appservice, IAppserviceRegistration, LogService, MatrixClient } from "matrix-bot-sdk";
 import * as yaml from "js-yaml";
 import * as fs from "fs";
 import { DiscordBridgeConfig } from "./config";
@@ -214,18 +214,18 @@ async function run(): Promise<void> {
 
     roomhandler.bindThirdparty();
 
-    await appservice.begin();
-    log.info(`Started listening on port ${port}`);
-
     try {
-        await discordbot.init();
-        await discordbot.run();
+        await discordbot.start();
         log.info("Discordbot started successfully");
     } catch (err) {
         log.error(err);
         log.error("Failure during startup. Exiting");
         process.exit(1);
     }
+
+    await appservice.begin();
+    log.info(`Started listening on port ${port}`);
+
 }
 
 run().catch((err) => {