diff --git a/config/config.sample.yaml b/config/config.sample.yaml
index 10f6d2fb97e7f9ca24fff97f962683894245c11b..510195fdf0fe34bd91e1fc56ac3357d7d2bc9afd 100644
--- a/config/config.sample.yaml
+++ b/config/config.sample.yaml
@@ -85,10 +85,12 @@ channel:
 limits:
     # Delay in milliseconds between discord users joining a room.
     roomGhostJoinDelay: 6000
-    # Delay in milliseconds before sending messages to discord to avoid echos.
-    # (Copies of a sent message may arrive from discord before we've
+    # Lock timeout in milliseconds before seinding messages to discord to avoid
+    # echos. Default is rather high as the lock will most likely time out
+    # before anyways.
+    # echos = (Copies of a sent message may arrive from discord before we've
     # fininished handling it, causing us to echo it back to the room)
-    discordSendDelay: 750
+    discordSendDelay: 1500
 ghosts:
     # Pattern for the ghosts nick, available is :nick, :username, :tag and :id
     nickPattern: ":nick"
diff --git a/src/bot.ts b/src/bot.ts
index 22791a07b812ff8983d86de5863642d1b3def0ff..0990a3e23e05b4ea9ce19f5146bd47e9b083714f 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -38,6 +38,7 @@ import * as mime from "mime";
 import { IMatrixEvent, IMatrixMediaInfo } from "./matrixtypes";
 import { DiscordCommandHandler } from "./discordcommandhandler";
 import { MetricPeg } from "./metrics";
+import { Lock } from "./structures/lock";
 
 const log = new Log("DiscordBot");
 
@@ -82,8 +83,7 @@ export class DiscordBot {
 
     /* Handles messages queued up to be sent to matrix from discord. */
     private discordMessageQueue: { [channelId: string]: Promise<void> };
-    private channelLocks: Map<string, {i: NodeJS.Timeout|null, r: (() => void)|null}>;
-    private channelLockPromises: Map<string, Promise<{}>>;
+    private channelLock: Lock<string>;
     constructor(
         private botUserId: string,
         private config: DiscordBridgeConfig,
@@ -107,8 +107,7 @@ export class DiscordBot {
         // init vars
         this.sentMessages = [];
         this.discordMessageQueue = {};
-        this.channelLocks = new Map();
-        this.channelLockPromises = new Map();
+        this.channelLock = new Lock(this.config.limits.discordSendDelay);
         this.lastEventIds = {};
     }
 
@@ -140,43 +139,6 @@ export class DiscordBot {
         return this.provisioner;
     }
 
-    public lockChannel(channel: Discord.Channel) {
-        if (this.channelLocks.has(channel.id)) {
-            return;
-        }
-
-        this.channelLocks[channel.id] = {i: null, r: null, p: null};
-        const p = new Promise<{}>((resolve) => {
-            const i = setTimeout(() => {
-                log.warn(`Lock on channel ${channel.id} expired. Discord is lagging behind?`);
-                this.unlockChannel(channel);
-            }, this.config.limits.discordSendDelay);
-            const o = Object.assign({r: resolve, i, p: null}, this.channelLocks.get(channel.id) || {});
-            this.channelLocks.set(channel.id, o);
-        });
-        this.channelLockPromises.set(channel.id, p);
-    }
-
-    public unlockChannel(channel: Discord.Channel) {
-        if (!this.channelLocks.has(channel.id)) {
-            return;
-        }
-        const lock = this.channelLocks.get(channel.id)!;
-        if (lock.i !== null) {
-            lock.r!();
-            clearTimeout(lock.i);
-        }
-        this.channelLocks.delete(channel.id);
-        this.channelLockPromises.delete(channel.id);
-    }
-
-    public async waitUnlock(channel: Discord.Channel) {
-        const promise = this.channelLockPromises.get(channel.id);
-        if (promise) {
-            await promise;
-        }
-    }
-
     public GetIntentFromDiscordMember(member: Discord.GuildMember | Discord.User, webhookID?: string): Intent {
         if (webhookID) {
             // webhookID and user IDs are the same, they are unique, so no need to prefix _webhook_
@@ -240,7 +202,7 @@ export class DiscordBot {
 
         client.on("messageDelete", async (msg: Discord.Message) => {
             try {
-                await this.waitUnlock(msg.channel);
+                await this.channelLock.wait(msg.channel.id);
                 this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
                 this.discordMessageQueue[msg.channel.id] = (async () => {
                     await (this.discordMessageQueue[msg.channel.id] || Promise.resolve());
@@ -261,7 +223,7 @@ export class DiscordBot {
                 msgs.forEach((msg) => {
                     promiseArr.push(async () => {
                         try {
-                            await this.waitUnlock(msg.channel);
+                            await this.channelLock.wait(msg.channel.id);
                             this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
                             await this.DeleteDiscordMessage(msg);
                         } catch (err) {
@@ -276,7 +238,7 @@ export class DiscordBot {
         });
         client.on("messageUpdate", async (oldMessage: Discord.Message, newMessage: Discord.Message) => {
             try {
-                await this.waitUnlock(newMessage.channel);
+                await this.channelLock.wait(newMessage.channel.id);
                 this.clientFactory.bindMetricsToChannel(newMessage.channel as Discord.TextChannel);
                 this.discordMessageQueue[newMessage.channel.id] = (async () => {
                     await (this.discordMessageQueue[newMessage.channel.id] || Promise.resolve());
@@ -293,7 +255,7 @@ export class DiscordBot {
         client.on("message", async (msg: Discord.Message) => {
             try {
                 MetricPeg.get.registerRequest(msg.id);
-                await this.waitUnlock(msg.channel);
+                await this.channelLock.wait(msg.channel.id);
                 this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
                 this.discordMessageQueue[msg.channel.id] = (async () => {
                     await (this.discordMessageQueue[msg.channel.id] || Promise.resolve());
@@ -417,10 +379,10 @@ export class DiscordBot {
         if (!msg) {
             return;
         }
-        this.lockChannel(channel);
+        this.channelLock.set(channel.id);
         const res = await channel.send(msg);
         await this.StoreMessagesSent(res, channel, event);
-        this.unlockChannel(channel);
+        this.channelLock.release(channel.id);
     }
 
     public async send(
@@ -451,7 +413,7 @@ export class DiscordBot {
             }
         }
         try {
-            this.lockChannel(chan);
+            this.channelLock.set(chan.id);
             if (!botUser) {
                 // NOTE: Don't send replies to discord if we are a puppet.
                 msg = await chan.send(embed.description, opts);
@@ -473,7 +435,7 @@ export class DiscordBot {
             }
             // Don't block on this.
             this.StoreMessagesSent(msg, chan, event).then(() => {
-                this.unlockChannel(chan);
+                this.channelLock.release(chan.id);
             }).catch(() => {
                 log.warn("Failed to store sent message for ", event.event_id);
             });
@@ -504,9 +466,9 @@ export class DiscordBot {
 
             const msg = await chan.fetchMessage(storeEvent.DiscordId);
             try {
-                this.lockChannel(msg.channel);
+                this.channelLock.set(msg.channel.id);
                 await msg.delete();
-                this.unlockChannel(msg.channel);
+                this.channelLock.release(msg.channel.id);
                 log.info(`Deleted message`);
             } catch (ex) {
                 log.warn(`Failed to delete message`, ex);
@@ -658,12 +620,12 @@ export class DiscordBot {
                   /* tslint:disable-next-line no-any */
               } as any, // XXX: Discord.js typings are wrong.
                 `Unbanned.`);
-            this.lockChannel(botChannel);
+            this.channelLock.set(botChannel.id);
             res = await botChannel.send(
                 `${kickee} was unbanned from this channel by ${kicker}.`,
             ) as Discord.Message;
             this.sentMessages.push(res.id);
-            this.unlockChannel(botChannel);
+            this.channelLock.release(botChannel.id);
             return;
         }
         const existingPerms = tchan.memberPermissions(kickee);
@@ -672,13 +634,13 @@ export class DiscordBot {
             return;
         }
         const word = `${kickban === "ban" ? "banned" : "kicked"}`;
-        this.lockChannel(botChannel);
+        this.channelLock.set(botChannel.id);
         res = await botChannel.send(
             `${kickee} was ${word} from this channel by ${kicker}.`
             + (reason ? ` Reason: ${reason}` : ""),
         ) as Discord.Message;
         this.sentMessages.push(res.id);
-        this.unlockChannel(botChannel);
+        this.channelLock.release(botChannel.id);
         log.info(`${word} ${kickee}`);
 
         await tchan.overwritePermissions(kickee,
diff --git a/src/config.ts b/src/config.ts
index 2da5b7734e721eca3d77126750cf16957349deb4..9d966b544b8f4dee85f80121e30da15c1bec477b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -133,7 +133,7 @@ export class DiscordBridgeConfigChannelDeleteOptions {
 
 class DiscordBridgeConfigLimits {
     public roomGhostJoinDelay: number = 6000;
-    public discordSendDelay: number = 750;
+    public discordSendDelay: number = 1500;
 }
 
 export class LoggingFile {
diff --git a/src/structures/lock.ts b/src/structures/lock.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f400aab092a3a6f49381d9c47b43112d0500be9d
--- /dev/null
+++ b/src/structures/lock.ts
@@ -0,0 +1,60 @@
+export class Lock<T> {
+    private locks: Map<T, {i: NodeJS.Timeout|null, r: (() => void)|null}>;
+    private lockPromises: Map<T, Promise<{}>>;
+    constructor(
+        private timeout: number,
+    ) {
+        this.locks = new Map();
+        this.lockPromises = new Map();
+    }
+
+    public set(key: T) {
+        // if there is a lock set.....we don't set a second one ontop
+        if (this.locks.has(key)) {
+            return;
+        }
+
+        // set a dummy lock so that if we re-set again before releasing it won't do anthing
+        this.locks.set(key, {i: null, r: null});
+
+        const p = new Promise<{}>((resolve) => {
+            // first we check if the lock has the key....if not, e.g. if it
+            // got released too quickly, we still want to resolve our promise
+            if (!this.locks.has(key)) {
+                resolve();
+                return;
+            }
+            // create the interval that will release our promise after the timeout
+            const i = setTimeout(() => {
+                this.release(key);
+            }, this.timeout);
+            // aaand store to our lock
+            this.locks.set(key, {r: resolve, i});
+        });
+        this.lockPromises.set(key, p);
+    }
+
+    public release(key: T) {
+        // if there is nothing to release then there is nothing to release
+        if (!this.locks.has(key)) {
+            return;
+        }
+        const lock = this.locks.get(key)!;
+        if (lock.r !== null) {
+            lock.r();
+        }
+        if (lock.i !== null) {
+            clearTimeout(lock.i);
+        }
+        this.locks.delete(key);
+        this.lockPromises.delete(key);
+    }
+
+    public async wait(key: T) {
+        // we wait for a lock release only if a promise is present
+        const promise = this.lockPromises.get(key);
+        if (promise) {
+            await promise;
+        }
+    }
+}
diff --git a/test/structures/test_lock.ts b/test/structures/test_lock.ts
new file mode 100644
index 0000000000000000000000000000000000000000..114ddb9065f0f11a9acb288b43f71c58227ba827
--- /dev/null
+++ b/test/structures/test_lock.ts
@@ -0,0 +1,45 @@
+/*
+Copyright 2019 matrix-appservice-discord
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { expect } from "chai";
+import { Lock } from "../../src/structures/lock";
+import { Util } from "../../src/util";
+
+const LOCKTIMEOUT = 300;
+
+describe("Lock", () => {
+    it("should lock and unlock", async () => {
+        const lock = new Lock<string>(LOCKTIMEOUT);
+        const t = Date.now();
+        lock.set("bunny");
+        await lock.wait("bunny");
+        const diff = Date.now() - t;
+        expect(diff).to.be.greaterThan(LOCKTIMEOUT - 1);
+    });
+    it("should lock and unlock early, if unlocked", async () => {
+        const SHORTDELAY = 100;
+        const DELAY_ACCURACY = 5;
+        const lock = new Lock<string>(LOCKTIMEOUT);
+        setTimeout(() => lock.release("fox"), SHORTDELAY);
+        const t = Date.now();
+        lock.set("fox");
+        await lock.wait("fox");
+        const diff = Date.now() - t;
+        // accuracy can be off by a few ms soemtimes
+        expect(diff).to.be.greaterThan(SHORTDELAY - DELAY_ACCURACY);
+        expect(diff).to.be.lessThan(SHORTDELAY + DELAY_ACCURACY);
+    });
+});
diff --git a/test/test_discordbot.ts b/test/test_discordbot.ts
index 918ff46863b19b6f70a106d96228576ec1cab580..2cfa4cacbe818f0a03577e83d292aba6f38cdf7c 100644
--- a/test/test_discordbot.ts
+++ b/test/test_discordbot.ts
@@ -416,48 +416,6 @@ describe("DiscordBot", () => {
             assert.equal(expected, ITERATIONS);
         });
     });
-    describe("locks", () => {
-        it("should lock and unlock a channel", async () => {
-            const bot = new modDiscordBot.DiscordBot(
-                "",
-                config,
-                mockBridge,
-                {},
-            ) as DiscordBot;
-            const chan = new MockChannel("123") as any;
-            const t = Date.now();
-            bot.lockChannel(chan);
-            await bot.waitUnlock(chan);
-            const diff = Date.now() - t;
-            expect(diff).to.be.greaterThan(config.limits.discordSendDelay - 1);
-        });
-        it("should lock and unlock a channel early, if unlocked", async () => {
-            const discordSendDelay = 500;
-            const SHORTDELAY = 100;
-            const MINEXPECTEDDELAY = 95;
-            const bot = new modDiscordBot.DiscordBot(
-                "",
-                {
-                    bridge: {
-                        domain: "localhost",
-                    },
-                    limits: {
-                        discordSendDelay,
-                    },
-                },
-                mockBridge,
-                {},
-            ) as DiscordBot;
-            const chan = new MockChannel("123") as any;
-            setTimeout(() => bot.unlockChannel(chan), SHORTDELAY);
-            const t = Date.now();
-            bot.lockChannel(chan);
-            await bot.waitUnlock(chan);
-            const diff = Date.now() - t;
-            // Date accuracy can be off by a few ms sometimes.
-            expect(diff).to.be.greaterThan(MINEXPECTEDDELAY);
-        });
-    });
   // });
     // describe("ProcessMatrixMsgEvent()", () => {
     //