diff --git a/src/bot.ts b/src/bot.ts
index 3690168e4b7eb8f1b05fe991df2e88a9b8b227f9..1fff78e4b72df9f4af6e535efcb40575a5ba4d68 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -524,19 +524,27 @@ export class DiscordBot {
       if (msg.content !== null && msg.content !== "") {
         this.msgProcessor.FormatDiscordMessage(msg).then((result) => {
             rooms.forEach((room) => {
-              intent.sendMessage(room, {
+              const trySend = () => intent.sendMessage(room, {
                 body: result.body,
                 msgtype: "m.text",
                 formatted_body: result.formattedBody,
                 format: "org.matrix.custom.html",
-            }).then((res) => {
-                    const evt = new DbEvent();
-                    evt.MatrixId = res.event_id + ";" + room;
-                    evt.DiscordId = msg.id;
-                    evt.ChannelId = msg.channel.id;
-                    evt.GuildId = msg.guild.id;
-                    return this.store.Insert(evt);
-                });
+              });
+              const afterSend = (res) => {
+                const evt = new DbEvent();
+                evt.MatrixId = res.event_id + ";" + room;
+                evt.DiscordId = msg.id;
+                evt.ChannelId = msg.channel.id;
+                evt.GuildId = msg.guild.id;
+                return this.store.Insert(evt);
+              };
+              trySend().then(afterSend).catch((e) => {
+                if (e.errcode !== "M_FORBIDDEN") {
+                  log.error("DiscordBot", "Failed to send message into room.", e);
+                  return;
+                }
+                return this.userSync.EnsureJoin(msg.member, room).then(() => trySend()).then(afterSend);
+              });
             });
         });
       }
diff --git a/src/discordas.ts b/src/discordas.ts
index 632fa62b8e714454453de00421a9cc7e39b943ee..947c758db701c517bbb9e3040dee7da2d57d06ea 100644
--- a/src/discordas.ts
+++ b/src/discordas.ts
@@ -71,6 +71,11 @@ function run (port: number, fileConfig: DiscordBridgeConfig) {
         log.verbose("matrix-appservice-bridge", line);
       },
     },
+    intentOptions: {
+      clients: {
+        dontJoin: true, // handled manually
+      },
+    },
     domain: config.bridge.domain,
     homeserverUrl: config.bridge.homeserverUrl,
     registration,
diff --git a/src/usersyncroniser.ts b/src/usersyncroniser.ts
index 5e2747691356a1312f5ee4d7a08461696f5ca353..e50fd37bb97d7b93fe684600696e5e71bc6b2da5 100644
--- a/src/usersyncroniser.ts
+++ b/src/usersyncroniser.ts
@@ -126,6 +126,17 @@ export class UserSyncroniser {
         }
     }
 
+    public async EnsureJoin(member: GuildMember, roomId: string) {
+        const mxUserId = `@_discord_${member.id}:${this.config.bridge.domain}`;
+        log.info("UserSync", `Ensuring ${mxUserId} is joined to ${roomId}`);
+        const state = <IGuildMemberState> {
+            id: member.id,
+            mxUserId,
+            displayName: member.displayName,
+        };
+        await this.ApplyStateToRoom(state, roomId, member.guild.id);
+    }
+
     public async ApplyStateToRoom(memberState: IGuildMemberState, roomId: string, guildId: string) {
         log.info("UserSync", `Applying new room state for ${memberState.mxUserId} to ${roomId}`);
         if (memberState.displayName === null) {
@@ -137,14 +148,25 @@ export class UserSyncroniser {
         const intent = this.bridge.getIntent(memberState.mxUserId);
         /* The intent class tries to be smart and deny a state update for <PL50 users.
            Obviously a user can change their own state so we use the client instead. */
+        const tryState = () => intent.getClient().sendStateEvent(roomId, "m.room.member", {
+            membership: "join",
+            avatar_url: remoteUser.get("avatarurl_mxc"),
+            displayname: memberState.displayName,
+        }, memberState.mxUserId);
         try {
-            await intent.getClient().sendStateEvent(roomId, "m.room.member", {
-                membership: "join",
-                avatar_url: remoteUser.get("avatarurl_mxc"),
-                displayname: memberState.displayName,
-            }, memberState.mxUserId);
+            await tryState();
         } catch (e) {
-            log.warn("UserSync", `Failed to send state to ${roomId}`, e);
+            if (e.errorcode !== "M_FORBIDDEN") {
+                log.warn("UserSync", `Failed to send state to ${roomId}`, e);
+            } else {
+                log.warn("UserSync", `User not in room ${roomId}, inviting`);
+                try {
+                    await this.bridge.getIntent().invite(roomId, memberState.mxUserId);
+                    await tryState();
+                } catch (e) {
+                    log.warn("UserSync", `Failed to send state to ${roomId}`, e);
+                }
+            }
         }
 
         remoteUser.set(nickKey, memberState.displayName);