diff --git a/privatemsg_draft/privatemsg_draft.info b/privatemsg_draft/privatemsg_draft.info
new file mode 100644
index 0000000..7878172
--- /dev/null
+++ b/privatemsg_draft/privatemsg_draft.info
@@ -0,0 +1,5 @@
+name = Private Messages Drafts
+description = Allows users to save drafts of their private messages.
+package = Mail
+core = 6.x
+dependencies[] = privatemsg
diff --git a/privatemsg_draft/privatemsg_draft.install b/privatemsg_draft/privatemsg_draft.install
new file mode 100644
index 0000000..f4eb019
--- /dev/null
+++ b/privatemsg_draft/privatemsg_draft.install
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Install file for privatemsg_drafts.module
+ */
+
+/**
+ * Implementation of hook_schema().
+ */
+function privatemsg_draft_schema() {
+  $schema = array();
+
+  $schema['pm_draft'] = array(
+    'description' => '{pm_draft} stores the draft information',
+    'fields' => array(
+      'draft_id' => array(
+        'description'   => 'Private Message Draft ID',
+        'type'          => 'serial',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'thread_id' => array(
+        'description'   => 'Private Message thread ID',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'author' => array(
+        'description'   => 'UID of the author',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+      'subject' => array(
+        'description'   => 'Subject text of the message',
+        'type'          => 'varchar',
+        'length'        => 255,
+        'not null'      => TRUE,
+      ),
+      'recipients'     => array(
+        'description'   => 'Stores the saved recipients list',
+        'type'          => 'text',
+        'not null'      => TRUE,
+        'size'          => 'normal',
+      ),
+      'body' => array(
+        'description'   => 'Body of the message',
+        'type'          => 'text',
+        'not null'      => TRUE,
+        'size'          => 'big',
+      ),
+      'format' => array(
+        'type'          => 'int',
+        'size'          => 'small',
+        'not null'      => TRUE,
+        'default'       => FILTER_FORMAT_DEFAULT,
+        'description'   => 'The {filter_formats}.format of the message text.',
+      ),
+      'timestamp' => array(
+        'description'   => 'Time when the draft was saved',
+        'type'          => 'int',
+        'not null'      => TRUE,
+        'unsigned'      => TRUE,
+      ),
+    ),
+    'primary key' => array('draft_id'),
+    'indexes' => array(
+       'thread_id' => array('thread_id'),
+       'author' => array('author'),
+       'timestamp' => array('timestamp'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function privatemsg_draft_install() {
+  drupal_install_schema('privatemsg_draft');
+
+}
+
+/**
+ * Implementation hook_uninstall().
+ */
+function privatemsg_draft_uninstall() {
+  drupal_uninstall_schema('privatemsg_draft');
+}
+
diff --git a/privatemsg_draft/privatemsg_draft.module b/privatemsg_draft/privatemsg_draft.module
new file mode 100644
index 0000000..b503bd8
--- /dev/null
+++ b/privatemsg_draft/privatemsg_draft.module
@@ -0,0 +1,332 @@
+<?php
+
+/**
+ * @file
+ * Allows users to save/edit/send drafts of private messages.
+ */
+
+/**
+ * Implementation of hook_menu().
+ */
+function privatemsg_draft_menu() {
+  // The url prefix value may be changed per site, menus must be rebuilt to take effect.
+  $url_prefix = variable_get('privatemsg_url_prefix', 'messages');
+  // Find how many arguments are in the prefix.
+  $url_prefix_arg_count = substr_count($url_prefix, '/') + 1;
+
+  $items[$url_prefix . '/drafts'] = array(
+    'title' => 'My Drafts',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('privatemsg_draft_list'),
+    'access callback' => 'privatemsg_draft_list_access',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => -11,
+  );
+
+  $items[$url_prefix . '/drafts/%privatemsg_draft'] = array(
+    'title' => 'Edit or Send this Draft',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('privatemsg_draft_draft', $url_prefix_arg_count + 1),
+    'access callback'  => 'privatemsg_draft_access',
+    'access arguments' => array($url_prefix_arg_count + 1),
+    'type' => MENU_LOCAL_TASK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function privatemsg_draft_perm() {
+  return array('create drafts of private messages');
+}
+
+/**
+ * Implmentation of hook_theme().
+ */
+function privatemsg_draft_theme() {
+  return array(
+    'privatemsg_draft_list' => array(
+      'arguments' => array('form'),
+    ),
+  );
+}
+
+/**
+ * Loader function for the draft passed from the URL.
+ */
+function privatemsg_draft_load($draft_id) {
+  $result = db_fetch_object(db_query('SELECT pmd.draft_id, pmd.thread_id, pmd.author, pmd.subject, pmd.recipients, pmd.body, pmd.format, pmd.timestamp FROM {pm_draft} pmd WHERE pmd.draft_id = %d', $draft_id));
+  if ($result) {
+    return $result;
+  }
+}
+
+/**
+ * Custom access callback for viewing the My Drafts page.
+ */
+function privatemsg_draft_list_access() {
+  return user_access('write privatemsg') && user_access('read privatemsg') && user_access('create drafts of private messages');
+}
+
+/**
+ * Custom access callback for viewing individual drafts.
+ * @param $draft
+ *   Private message draft object
+ */
+function privatemsg_draft_access($draft) {
+  global $user;
+  return (user_access('create drafts of private messages') && $draft->author == $user->uid);
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function privatemsg_draft_form_alter($form, &$form_state, $form_id) {
+  global $user;
+  switch ($form_id) {
+    case 'privatemsg_new':
+    case 'privatemsg_form_reply':
+      // Check if user is allowed to create drafts.
+      if (user_access('create drafts of private messages')) {
+        // Alter recipients to be not required
+        $form['recipient']['#required'] = FALSE;
+        // Alter the current submit to allow custom validation on drafts
+        $form['submit']['#submit'] = $form['#submit'];
+        $form['submit']['#validate'] = $form['#validate'];
+        $form['#submit'] = array();
+        $form['#validate'] = array();
+        // Add the save draft submit button.
+        $form['save_draft'] = array(
+          '#type' => 'submit',
+          '#value' => t('Save draft'),
+          '#submit' => array('privatemsg_draft_save_draft'),
+          '#validate' => array('privatemsg_draft_save_draft_validate'),
+          '#weight' => $form['submit']['#weight'] + 1,
+        );
+      }
+    break;
+  }
+}
+
+/**
+ * Custom form for editing or sending drafts (messages or replies)
+ */
+function privatemsg_draft_draft($form, $draft) {
+  module_load_include('inc', 'privatemsg', 'privatemsg.pages');
+  $url_prefix = variable_get('privatemsg_url_prefix', 'messages');
+
+  if ($draft->thread_id != 0) { // this is a reply draft, get the reply form
+    $thread = privatemsg_thread_load($draft->thread_id);
+    $form = privatemsg_form_reply(&$form_state, $thread);
+
+    $form['thread_subject'] = array(
+      '#value' => '<h2 class="privatemsg-drafts-subject">' . t('Subject: ') . l($thread['subject'], $url_prefix . '/view/' . $thread['thread_id']) . '</h2>',
+      '#weight' => -105,
+    );
+
+    $to = _privatemsg_get_allowed_recipients($thread['participants'], $thread['thread_id']);
+    if (!empty($to)) {
+      $recipients = _privatemsg_format_participants($to, NULL, TRUE);
+    }
+    $form['thread_participants'] = array(
+      '#value' => '<h3 class="privatemsg-drafts-participants">' . t('Participants: ') . $recipients . '</h3>',
+      '#weight' => -103,
+    );
+
+    $form['thread_link'] = array(
+      '#value' => '<h4 class="privatemsg-drafts-thread-link">' . l(t('View this entire thread'), $url_prefix . '/view/' . $thread['thread_id']) . '</h4>',
+      '#weight' => -100,
+    );
+
+    $form['retain_draft'] = array( // Force draft deletion after send.
+      '#type' => 'value',
+      '#value' => 0,
+    );
+
+    $form['#redirect'] = $url_prefix . '/view/' . $thread['thread_id'];
+  }
+  else { // This is a new message draft, return the new message form.
+    $form = privatemsg_new(&$form_state);
+    // Alter recipients to be not required
+    $form['recipient']['#required'] = FALSE;
+    $form['recipient']['#default_value'] = $draft->recipients;
+    $form['subject']['#default_value'] = $draft->subject;
+    $form['retain_draft'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Keep this draft after sending.'),
+      '#description' => t('Optionally retain this draft after sending to allow for reuse later. '),
+    );
+  }
+
+  $form['body']['#default_value'] = $draft->body;
+
+  $form['draft_id'] = array(
+    '#type' => 'hidden',
+    '#value' => $draft->draft_id,
+  ); 
+
+  // Alter the current submit to allow custom validation on drafts
+  $form['submit']['#submit'] = $form['#submit'];
+  $form['submit']['#validate'] = $form['#validate'];
+  $form['#submit'] = array();
+  $form['#validate'] = array();
+
+  $form['save_draft'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save draft'),
+    '#submit' => array('privatemsg_draft_save_draft'),
+    '#validate' => array('privatemsg_draft_save_draft_validate'),
+    '#weight' => $form['submit']['#weight'] + 1,
+  );
+
+  return $form;
+}
+
+/**
+ * Build the draft list form
+ */
+function privatemsg_draft_list() {
+  global $user;
+  $url_prefix = variable_get('privatemsg_url_prefix', 'messages');
+
+  $query = _privatemsg_assemble_query(array('getdrafts', 'privatemsg_draft'), $user->uid);
+  $result = pager_query($query['query'], variable_get('privatemsg_per_page', 25), 0, $query['count']);
+
+  $drafts = array();
+  $form['#data'] = array();
+  while ($row = db_fetch_array($result)) {
+    // Store the raw row data.
+    $form['#data'][$row['draft_id']] = $row;
+    // Store thread id for the checkboxes array.
+    $drafts[$row['draft_id']] = '';
+
+    $form[$row['draft_id']]['subject'] = array(
+      '#value' => ($row['subject'] ? l($row['subject'], $url_prefix . '/drafts/' . $row['draft_id']) : l('Untitled Draft', $url_prefix . '/drafts/' . $row['draft_id'])),
+    );
+    $form[$row['draft_id']]['updated'] = array(
+      '#value' => format_date($row['timestamp'], 'small'),
+    );
+
+    $form[$row['draft_id']]['edit'] = array(
+      '#value' => l('Edit', $url_prefix . '/drafts/' . $row['draft_id']),
+    );
+  }
+
+  // Save the currently active account, used for actions.
+  $form['account'] = array('#type' => 'value', '#value' => $user);
+
+  // Define checkboxes, pager and theme.
+  $form['drafts'] = array('#type' => 'checkboxes', '#options' => $drafts);
+  $form['pager'] = array('#value' => theme('pager'), '#weight' => 20);
+  $form['#theme'] = 'privatemsg_draft_list';
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete selected drafts'),
+  );
+
+  return $form;
+}
+
+/**
+ * Delete selected drafts from db
+ */
+function privatemsg_draft_list_submit($form, &$form_state) {
+  $drafts = array_filter($form_state['values']['drafts']);
+  db_query('DELETE FROM {pm_draft} WHERE draft_id IN (' . db_placeholders($drafts, 'int') . ')', $drafts);
+  drupal_set_message(t('The selected drafts have been deleted.'));
+  return;
+}
+
+/**
+ * Theme to display the privatemsg list.
+ *
+ * This theme builds a table with paging based on the data which has been built
+ * by the header and field theme patterns.
+ */
+function theme_privatemsg_draft_list($form) {
+  $rows = array();
+  foreach (element_children($form['drafts']) as $draft_id) {
+    $row = array(); 
+    $row[] = drupal_render($form['drafts'][$draft_id]);
+    $row[] = drupal_render($form[$draft_id]['subject']);
+    $row[] = drupal_render($form[$draft_id]['updated']);
+    $row[] = drupal_render($form[$draft_id]['edit']);
+    $rows[] = $row;
+  }
+
+  if (count($rows)) {
+    $header = array(theme('table_select_header_cell'), 'subject' => array('data' => 'Subject', 'field' => 'subject'), 'updated' => array('data' => 'Updated', 'field' => 'timestamp'), 'edit' => array('data' => 'Edit')); 
+  }
+  else {
+    $header = array(t('Subject'), t('Updated'), t('Edit')); 
+    $row = array();
+    $row[] = array(
+      'data' => t('You do not have any saved drafts at this time.'),
+      'colspan' => count($header),
+    );
+    $rows[] = $row;
+  }
+
+  $output = theme('table', $header, $rows);
+  return $output . drupal_render($form);
+}
+
+/**
+ * Query builder function for listing drafts of messages.
+ */
+function privatemsg_draft_sql_getdrafts(&$fragments, $uid) {
+  // Set the primary table.
+  $fragments['primary_table'] = '{pm_draft} pd';
+  // Add a field.
+  $fragments['select'][] = 'pd.draft_id, pd.thread_id, pd.subject, pd.timestamp';
+  // And finally add a condition.
+  $fragments['where'][] = 'pd.author = %d';
+  $fragments['query_args']['where'][] = $uid;
+}
+
+/**
+ * Implementation of hook_privatemsg_message_insert().
+ */
+function privatemsg_draft_privatemsg_message_insert($message) {
+  if (isset($message['draft_id']) && $message['retain_draft'] == 0) {
+     // Delete sent draft from db.
+    db_query('DELETE FROM {pm_draft} WHERE draft_id = %d', $message['draft_id']);
+  }
+} 
+
+/**
+ * Save draft to db.
+ */
+function privatemsg_draft_save_draft($form, &$form_state) {
+  $args = array();
+  $args[] = (isset($form_state['values']['thread_id']) ? $form_state['values']['thread_id'] : 0);
+  $args[] = $form_state['values']['author']->uid;
+  $args[] = trim($form_state['values']['subject']);
+  $args[] = $form_state['values']['recipient'];
+  $args[] = $form_state['values']['body'];
+  $args[] = $form_state['values']['format'];
+  $args[] = time();
+  if (isset($form_state['values']['draft_id'])) {
+    $args[] = $form_state['values']['draft_id'];
+    $draft_sql = "UPDATE {pm_draft} SET thread_id = %d, author = %d, subject = '%s', recipients = '%s', body = '%s', format = %d, timestamp = %d WHERE draft_id = %d";
+  } 
+  else {
+    $draft_sql = "INSERT INTO {pm_draft} (thread_id, author, subject, recipients, body, format, timestamp) VALUES (%d, %d, '%s', '%s', '%s', %d, %d)";
+  }
+  db_query($draft_sql, $args);
+
+  $form_state['redirect'] = array(privatemsg_get_dynamic_url_prefix() . '/drafts');
+  drupal_set_message(t('Your draft has been saved.'));
+}
+
+/**
+ * Validate draft submissions.
+ */
+function privatemsg_draft_save_draft_validate($form, &$form_state) {
+  if ($form_state['values']['recipient'] == '' && $form_state['values']['subject'] == '' && $form_state['values']['body'] == '') {
+    form_set_error('', t('To save a draft, you must enter at least one value.'));
+  }
+}
diff --git a/privatemsg_draft/privatemsg_draft.test b/privatemsg_draft/privatemsg_draft.test
new file mode 100644
index 0000000..7640e98
--- /dev/null
+++ b/privatemsg_draft/privatemsg_draft.test
@@ -0,0 +1,118 @@
+<?php
+/**
+ * @file
+ * Test file for privatemsg_forward.module
+ */
+
+class PrivatemsgDraftsTestCase extends DrupalWebTestCase {
+
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Privatemsg drafts functionality.'),
+      'description' => t('Test save/edit/delete drafts features of the module.'),
+      'group' => t('Privatemsg'),
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    parent::setUp('privatemsg', 'privatemsg_draft');
+  }
+
+  /**
+  * Test user access to /drafts
+  * Create user without 'create drafts of private messages' permission. Try to 
+  * access drafts page and see if it gives access denied error.
+  * Create user with 'create drafts of private messages' permission. Try to 
+  * access drafts page and see if it gives allows access
+  * Test saving, sending, editing drafts.
+  */
+  function testPrivatemsgDrafts() {
+    $user_no_read_drafts = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg'));
+    $author = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg', 'create drafts of private messages'));
+    $authorB = $this->drupalCreateUser(array('write privatemsg', 'read privatemsg', 'create drafts of private messages'));
+
+    $this->drupalLogin($user_no_read_drafts);
+    $this->drupalGet('messages/drafts');
+    $this->assertResponse(403, t('HTTP Response 403: Access to drafts was blocked to user without "<em>create drafts</em>" permission.'));
+
+    $this->drupalLogin($author);
+    $this->drupalGet('messages/drafts');
+    $this->assertText('My Drafts', t('Access to thread for recipient allowed.'));
+
+    $edit = array(
+      'recipient'   => $user_no_read_drafts->name,
+      'subject'     => $this->randomName(20),
+      'body'        => $this->randomName(100),
+    );
+
+    // Test saving the draft
+    $this->drupalGet('messages/new');
+    $this->drupalPost('messages/new', $edit, t('Save draft'));
+    $this->assertText(t('Your draft has been saved.'), 'Draft saved confirmation displayed.');
+    $this->assertText($edit['subject'], 'Draft subject displayed on drafts page.');
+
+    // Test deleting the draft
+    $edit = array();
+    $edit['drafts[1]'] = '1';
+    $this->drupalPost('messages/drafts', $edit, t('Delete selected drafts'));
+    $this->assertNoText($edit['subject'], t('Draft deleted successfully.'));
+    $this->assertText(t('You do not have any saved drafts at this time.'), t('First and only draft deleted, empty message displayed.'));
+
+    $edit = array();
+    $edit = array(
+      'recipient'   => $user_no_read_drafts->name . ', ' . $authorB->name . ', ',
+      'subject'     => $this->randomName(20),
+      'body'        => $this->randomName(100),
+    );
+
+    // Create a new draft, edit, then save, assert the changes were saved
+    $this->drupalGet('messages/new');
+    $this->drupalPost('messages/new', $edit, t('Save draft'));
+    $this->assertText(t('Your draft has been saved.'), 'Draft saved confirmation displayed.');
+
+    $edit['retain_draft'] = FALSE; // Force the deletion of the draft.
+
+    $this->clickLink('Edit');
+    // Assert that the fields are properly filled.
+    $this->assertFieldById('edit-recipient', $edit['recipient'], 'The edit-recipient field is set correctly'); 
+    $this->assertFieldById('edit-subject', $edit['subject'], 'The edit-subject field is set correctly'); 
+    $this->assertFieldById('edit-body', $edit['body'], 'The edit-path-body field is set correctly'); 
+
+    $this->drupalPost('messages/drafts/2', NULL, t('Send message'));
+    $this->assertText(t('A message has been sent to ' . $user_no_read_drafts->name . ', ' . $authorB->name . '.'), t('Message sent confirmation displayed.'));
+
+    $this->drupalGet('messages/drafts');
+    $this->assertNoText($edit['subject'], t('This draft was deleted.'));
+
+    // Send new draft with "retain draft" selected, assert that it is not deleted from the db
+    $edit = array();
+    $edit = array(
+      'recipient'   => $user_no_read_drafts->name,
+      'subject'     => $this->randomName(20),
+      'body'        => $this->randomName(100),
+    );
+
+    $this->drupalGet('messages/new');
+    $this->drupalPost('messages/new', $edit, t('Save draft'));
+    $this->clickLink('Edit');
+    
+    $retain_draft = array();
+    $retain_draft['retain_draft'] = TRUE;
+    $this->drupalPost('messages/drafts/3', $retain_draft, t('Send message'));
+    $this->assertText(t('A message has been sent to ' . $user_no_read_drafts->name . '.'), t('Draft sent, confirmation displayed.'));
+    $this->drupalGet('messages/drafts');
+    $this->assertText($edit['subject'], t('Sent draft was retained.'));
+    
+    // Verify that $authorB cannot access $author's drafts
+    $this->drupalLogin($authorB);
+    $this->drupalGet('messages/drafts/3');
+    $this->assertResponse(403, t('HTTP Response 403: Access to draft deinied to user who is not the author, but has create drafts permission.'));
+
+  }
+}
