Index: commands.c
===================================================================
--- commands.c	(revision a16d7e5b1281bf21e0fe52b6d9946fd58893da28)
+++ commands.c	(revision a16d7e5b1281bf21e0fe52b6d9946fd58893da28)
@@ -693,4 +693,13 @@
 	      "current view.\n"),
   OWLCMD_ALIAS("del", "delete"),
+
+  OWLCMD_ARGS("delete-and-expunge", owl_command_delete_and_expunge, OWL_CTX_INTERACTIVE,
+              "delete a message",
+              "delete-and-expunge [-id msgid] [-q | --quiet]",
+              "If no message id is specified the current message is deleted.\n"
+              "Otherwise the message with the given message id is deleted.\n"
+              "If --quiet is specified, then there is no message displayed on\n"
+              "success.\n"),
+  OWLCMD_ALIAS("delx", "delete-and-expunge"),
 
   OWLCMD_ARGS("undelete", owl_command_undelete, OWL_CTX_INTERACTIVE,
@@ -2354,4 +2363,31 @@
 }
 
+char *owl_command_delete_and_expunge(int argc, const char *const *argv, const char *buff)
+{
+  bool exclaim_success = true;
+
+  if (argc > 1 && (!strcmp(argv[1], "-q") || !strcmp(argv[1], "--quiet"))) {
+    exclaim_success = false;
+    argc--;
+    argv++;
+  } else if (!strcmp(argv[argc - 1], "-q") || !strcmp(argv[argc - 1], "--quiet")) {
+    exclaim_success = false;
+    argc--;
+  }
+
+  if (argc == 1) {
+    owl_function_delete_and_expunge_cur(exclaim_success);
+    return NULL;
+  }
+
+  if (argc == 3 && (!strcmp(argv[1], "-id") || !strcmp(argv[1], "--id"))) {
+    owl_function_delete_and_expunge_by_id(atoi(argv[2]), exclaim_success);
+    return NULL;
+  }
+
+  owl_function_makemsg("Unknown arguments to delete-and-expunge command");
+  return NULL;
+}
+
 char *owl_command_undelete(int argc, const char *const *argv, const char *buff)
 {
Index: functions.c
===================================================================
--- functions.c	(revision a16d7e5b1281bf21e0fe52b6d9946fd58893da28)
+++ functions.c	(revision a16d7e5b1281bf21e0fe52b6d9946fd58893da28)
@@ -662,4 +662,34 @@
 }
 
+void owl_function_delete_and_expunge_message(int n)
+{
+  owl_messagelist *ml = owl_global_get_msglist(&g);
+  owl_view *v = owl_global_get_current_view(&g);
+  int lastmsgid = owl_function_get_curmsg_id(v);
+
+  /* delete and expunge the message */
+  owl_messagelist_delete_and_expunge_element(ml, n);
+
+  owl_function_redisplay_to_nearest(lastmsgid, v);
+}
+
+void owl_function_delete_and_expunge_cur(bool exclaim_success)
+{
+  int curmsg;
+  const owl_view *v = owl_global_get_current_view(&g);
+
+  /* bail if there's no current message */
+  if (owl_view_get_size(v) < 1) {
+    owl_function_error("No current message to delete");
+    return;
+  }
+
+  /* delete the current message */
+  curmsg = owl_global_get_curmsg(&g);
+  owl_function_delete_and_expunge_message(curmsg);
+  if (exclaim_success)
+    owl_function_makemsg("Message deleted and expunged");
+}
+
 /* if move_after is 1, moves after the delete */
 void owl_function_deletecur(int move_after)
@@ -721,38 +751,60 @@
 }
 
+/* returns the current message id, if it exists.  Otherwise returns
+ * -1 if we are past the end of the message list, and 0 otherwise. */
+int owl_function_get_curmsg_id(const owl_view *v)
+{
+  int curmsg = owl_global_get_curmsg(&g);
+  const owl_message *m = owl_view_get_element(v, curmsg);
+  if (m)
+    return owl_message_get_id(m);
+  if (curmsg > 0) /* past the end of the message list (probably) */
+    return -1;
+  return 0;
+}
+
+/* redisplays the view to the nearest message to the id given.
+ * if msgid < 0, redisplay to past the end of the message list */
+void owl_function_redisplay_to_nearest(int msgid, owl_view *v)
+{
+  int curmsg;
+  /* update all views (we only have one right now) */
+  owl_view_recalculate(v);
+
+  /* find where the new position should be */
+  if (msgid < 0) {
+    /* If already at the end, blank the screen and move curmsg
+     * past the end of the messages. */
+    curmsg = owl_view_get_size(v);
+    owl_global_set_topmsg(&g, curmsg);
+    owl_global_set_curmsg(&g, curmsg);
+  } else {
+    curmsg = owl_view_get_nearest_to_msgid(v, msgid);
+    if (curmsg > owl_view_get_size(v) - 1)
+      curmsg = owl_view_get_size(v) - 1;
+    if (curmsg < 0)
+      curmsg = 0;
+    owl_global_set_curmsg(&g, curmsg);
+    owl_function_calculate_topmsg(OWL_DIRECTION_NONE);
+  }
+  /* if there are no messages set the direction to down in case we
+   * delete everything upwards */
+  owl_global_set_direction_downwards(&g);
+
+  owl_mainwin_redisplay(owl_global_get_mainwin(&g));
+}
+
 void owl_function_expunge(void)
 {
-  int curmsg;
-  const owl_message *m;
-  owl_messagelist *ml;
-  owl_view *v;
-  int lastmsgid=0;
-
-  curmsg=owl_global_get_curmsg(&g);
-  v=owl_global_get_current_view(&g);
-  ml=owl_global_get_msglist(&g);
-
-  m=owl_view_get_element(v, curmsg);
-  if (m) lastmsgid = owl_message_get_id(m);
+  owl_messagelist *ml = owl_global_get_msglist(&g);
+  owl_view *v = owl_global_get_current_view(&g);
+  int lastmsgid = owl_function_get_curmsg_id(v);
 
   /* expunge the message list */
   owl_messagelist_expunge(ml);
 
-  /* update all views (we only have one right now) */
-  owl_view_recalculate(v);
-
-  /* find where the new position should be
-     (as close as possible to where we last where) */
-  curmsg = owl_view_get_nearest_to_msgid(v, lastmsgid);
-  if (curmsg>owl_view_get_size(v)-1) curmsg = owl_view_get_size(v)-1;
-  if (curmsg<0) curmsg = 0;
-  owl_global_set_curmsg(&g, curmsg);
-  owl_function_calculate_topmsg(OWL_DIRECTION_NONE);
-  /* if there are no messages set the direction to down in case we
-     delete everything upwards */
-  owl_global_set_direction_downwards(&g);
+  owl_function_redisplay_to_nearest(lastmsgid, v);
   
   owl_function_makemsg("Messages expunged");
-  owl_mainwin_redisplay(owl_global_get_mainwin(&g));
 }
 
@@ -1642,4 +1694,17 @@
 }
 
+void owl_function_delete_and_expunge_by_id(int id, bool exclaim_success)
+{
+  const owl_messagelist *ml = owl_global_get_msglist(&g);
+  int msg = owl_messagelist_get_index_by_id(ml, id);
+  if (msg < 0) {
+    owl_function_error("No message with id %d: unable to delete", id);
+  } else {
+    owl_function_delete_and_expunge_message(msg);
+    if (exclaim_success)
+      owl_function_makemsg("Message deleted and expunged");
+  }
+}
+
 /* note: this applies to global message list, not to view.
  * If flag is 1, deletes.  If flag is 0, undeletes. */
Index: messagelist.c
===================================================================
--- messagelist.c	(revision 901cee9b7ebc203aee860a9c5fc3849ed3527532)
+++ messagelist.c	(revision 901cee9b7ebc203aee860a9c5fc3849ed3527532)
@@ -30,7 +30,7 @@
 }
 
-owl_message *owl_messagelist_get_by_id(const owl_messagelist *ml, int target_id)
+int owl_messagelist_get_index_by_id(const owl_messagelist *ml, int target_id)
 {
-  /* return the message with id == 'id'.  If it doesn't exist return NULL. */
+  /* return the message index with id == 'id'.  If it doesn't exist return -1. */
   int first, last, mid, msg_id;
   owl_message *m;
@@ -43,5 +43,5 @@
     msg_id = owl_message_get_id(m);
     if (msg_id == target_id) {
-      return(m);
+      return mid;
     } else if (msg_id < target_id) {
       first = mid + 1;
@@ -50,5 +50,13 @@
     }
   }
-  return(NULL);
+  return -1;
+}
+
+owl_message *owl_messagelist_get_by_id(const owl_messagelist *ml, int target_id)
+{
+  /* return the message with id == 'id'.  If it doesn't exist return NULL. */
+  int n = owl_messagelist_get_index_by_id(ml, target_id);
+  if (n < 0) return NULL;
+  return ml->list->pdata[n];
 }
 
@@ -71,4 +79,9 @@
   owl_message_unmark_delete(ml->list->pdata[n]);
   return(0);
+}
+
+void owl_messagelist_delete_and_expunge_element(owl_messagelist *ml, int n)
+{
+  owl_message_delete(g_ptr_array_remove_index(ml->list, n));
 }
 
Index: perl/lib/BarnOwl.pm
===================================================================
--- perl/lib/BarnOwl.pm	(revision 785ee770a72bcdda39fd541a90d8c640fa5c76ca)
+++ perl/lib/BarnOwl.pm	(revision 785ee770a72bcdda39fd541a90d8c640fa5c76ca)
@@ -105,5 +105,6 @@
 Enqueue a message in the BarnOwl message list, logging it and
 processing it appropriately. C<MESSAGE> should be an instance of
-BarnOwl::Message or a subclass.
+BarnOwl::Message or a subclass.  Returns the queued message.  This
+is useful for, e.g., deleting a message from the message list.
 
 =head2 admin_message HEADER BODY
Index: perl/lib/BarnOwl/Message.pm
===================================================================
--- perl/lib/BarnOwl/Message.pm	(revision d1ae4a421ba5874c2899090ebdd7079e9bbddc25)
+++ perl/lib/BarnOwl/Message.pm	(revision caac19d40ffaea75410ce13da172590b04dae2b1)
@@ -81,4 +81,9 @@
 sub short_personal_context { return ""; }
 
+sub delete_and_expunge {
+    my ($m) = @_;
+    &BarnOwl::command("delete-and-expunge --quiet --id " . $m->id);
+}
+
 sub delete {
     my ($m) = @_;
Index: perlglue.xs
===================================================================
--- perlglue.xs	(revision ce68f235ae86ae86fa4398ae206bda1194d1fcda)
+++ perlglue.xs	(revision caac19d40ffaea75410ce13da172590b04dae2b1)
@@ -143,5 +143,5 @@
 		g_free(rv);
 
-void
+SV *
 queue_message(msg)
 	SV *msg
@@ -157,5 +157,9 @@
 
 		owl_global_messagequeue_addmsg(&g, m);
-	}
+
+		RETVAL = owl_perlconfig_message2hashref(m);
+	}
+	OUTPUT:
+		RETVAL
 
 void
