diff --git a/pbx/pbx_spool.c b/pbx/pbx_spool.c
index b45ff6a5910d1e11451210d7758d57a8bfbe7f5d..327c995ce87a60884d5618c824cb3d3878ea6635 100644
--- a/pbx/pbx_spool.c
+++ b/pbx/pbx_spool.c
@@ -471,6 +471,7 @@ static AST_LIST_HEAD_STATIC(dirlist, direntry);
 #if defined(HAVE_INOTIFY)
 /* Only one thread is accessing this list, so no lock is necessary */
 static AST_LIST_HEAD_NOLOCK_STATIC(createlist, direntry);
+static AST_LIST_HEAD_NOLOCK_STATIC(openlist, direntry);
 #endif
 
 static void queue_file(const char *filename, time_t when)
@@ -551,14 +552,47 @@ static void queue_file_create(const char *filename)
 		return;
 	}
 	strcpy(cur->name, filename);
+	/* We'll handle this file unless an IN_OPEN event occurs within 2 seconds */
+	cur->mtime = time(NULL) + 2;
 	AST_LIST_INSERT_TAIL(&createlist, cur, list);
 }
 
+static void queue_file_open(const char *filename)
+{
+	struct direntry *cur;
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) {
+		if (!strcmp(cur->name, filename)) {
+			AST_LIST_REMOVE_CURRENT(list);
+			AST_LIST_INSERT_TAIL(&openlist, cur, list);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END
+}
+
+static void queue_created_files(void)
+{
+	struct direntry *cur;
+	time_t now = time(NULL);
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) {
+		if (cur->mtime > now) {
+			break;
+		}
+
+		AST_LIST_REMOVE_CURRENT(list);
+		queue_file(cur->name, 0);
+		ast_free(cur);
+	}
+	AST_LIST_TRAVERSE_SAFE_END
+}
+
 static void queue_file_write(const char *filename)
 {
 	struct direntry *cur;
 	/* Only queue entries where an IN_CREATE preceded the IN_CLOSE_WRITE */
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&createlist, cur, list) {
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&openlist, cur, list) {
 		if (!strcmp(cur->name, filename)) {
 			AST_LIST_REMOVE_CURRENT(list);
 			ast_free(cur);
@@ -605,7 +639,7 @@ static void *scan_thread(void *unused)
 	}
 
 #ifdef HAVE_INOTIFY
-	inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO);
+	inotify_add_watch(inotify_fd, qdir, IN_CREATE | IN_OPEN | IN_CLOSE_WRITE | IN_MOVED_TO);
 #endif
 
 	/* First, run through the directory and clear existing entries */
@@ -641,14 +675,35 @@ static void *scan_thread(void *unused)
 			/* Convert from seconds to milliseconds, unless there's nothing
 			 * in the queue already, in which case, we wait forever. */
 			int waittime = next == INT_MAX ? -1 : (next - now) * 1000;
+			if (!AST_LIST_EMPTY(&createlist)) {
+				waittime = 1000;
+			}
 			/* When a file arrives, add it to the queue, in mtime order. */
 			if ((res = poll(&pfd, 1, waittime)) > 0 && (stage = 1) &&
 				(res = read(inotify_fd, &buf, sizeof(buf))) >= sizeof(*iev)) {
 				ssize_t len = 0;
 				/* File(s) added to directory, add them to my list */
 				for (iev = (void *) buf; res >= sizeof(*iev); iev = (struct inotify_event *) (((char *) iev) + len)) {
+					/* For an IN_MOVED_TO event, simply process the file. However, if
+					 * we get an IN_CREATE event it *might* be an open(O_CREAT) or it
+					 * might be a hardlink (like smsq does, since rename() might
+					 * overwrite an existing file). So we have to see if we get a
+					 * subsequent IN_OPEN event on the same file. If we do, keep it
+					 * on the openlist and wait for the corresponding IN_CLOSE_WRITE.
+					 * If we *don't* see an IN_OPEN event, then it was a hard link so
+					 * it can be processed immediately.
+					 *
+					 * Unfortunately, although open(O_CREAT) is an atomic file system
+					 * operation, the inotify subsystem doesn't give it to us in a
+					 * single event with both IN_CREATE|IN_OPEN set. It's two separate
+					 * events, and the kernel doesn't even give them to us at the same
+					 * time. We can read() from inotify_fd after the IN_CREATE event,
+					 * and get *nothing* from it. The IN_OPEN arrives only later! So
+					 * we have a very short timeout of 2 seconds. */
 					if (iev->mask & IN_CREATE) {
 						queue_file_create(iev->name);
+					} else if (iev->mask & IN_OPEN) {
+						queue_file_open(iev->name);
 					} else if (iev->mask & IN_CLOSE_WRITE) {
 						queue_file_write(iev->name);
 					} else if (iev->mask & IN_MOVED_TO) {
@@ -679,6 +734,7 @@ static void *scan_thread(void *unused)
 			time(&now);
 		}
 
+		queue_created_files();
 		/* Empty the list of all entries ready to be processed */
 		AST_LIST_LOCK(&dirlist);
 		while (!AST_LIST_EMPTY(&dirlist) && AST_LIST_FIRST(&dirlist)->mtime <= now) {