diff --git a/pm_email_notify/pm_email_notify.module b/pm_email_notify/pm_email_notify.module
index b36f735..fcaad6d 100644
--- a/pm_email_notify/pm_email_notify.module
+++ b/pm_email_notify/pm_email_notify.module
@@ -44,7 +44,11 @@ function pm_email_notify_permission() {
     'change privatemsg e-mail notification for indirect messages' => array(
       'title' => t('Change privatemsg e-mail notification for indirect messages'),
       'description' => t('Users with this permission may override the default setting.'),
-    )
+    ),
+    'resend edited privatemsg messages' => array(
+      'title' => t('May resend edited messages'),
+      'description' => t('Allows users who are allowed to edit private message to choose to send the edited message to the recipient(s). (There is also a default setting that goes with this permission.)'),
+    ),
   );
 }
 
@@ -181,8 +185,10 @@ function _pm_email_notify_send_check($uid, $message) {
  * Implements hook_privatemsg_message_insert().
  */
 function pm_email_notify_privatemsg_message_insert($message) {
-   foreach ($message->recipients as $recipient) {
-    pm_email_notify_send_mail($recipient, $message);
+  if ($message->op != 'Save message' || ($message->op == 'Save message' && $message->resend_edited_message)) {
+    foreach ($message->recipients as $recipient) {
+      pm_email_notify_send_mail($recipient, $message);
+    }
   }
 }
 
@@ -396,6 +402,20 @@ function pm_email_notify_user_delete($account) {
 }
 
 /**
+ * Implements hook_form_FORM_ID_alter() for the privatemsg_message_edit form.
+ */
+function pm_email_notify_form_privatemsg_message_edit_form_alter(&$form, &$form_state) {
+  if (user_access('resend edited privatemsg messages')) {
+    $form['resend_edited_message'] = array(
+      '#type'          => 'checkbox',
+      '#title'         => t('Send edited message'),
+      '#default_value' => variable_get('privatemsg_resend_edited_messages_default', 0),
+      '#weight'        => $form['actions']['submit']['#weight'] - 1,
+    );
+  }
+}
+
+/**
  * Implements hook_form_FORM_ID_alter().
  */
 function pm_email_notify_form_privatemsg_admin_settings_alter(&$form, &$form_state) {
@@ -492,6 +512,13 @@ function pm_email_notify_form_privatemsg_admin_settings_alter(&$form, &$form_sta
   if (module_exists('i18n_string')) {
     $form['#submit'][] = 'pm_email_notify_admin_settings_update_strings';
   }
+
+  $form['editing_messages']['privatemsg_resend_edited_messages_default'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable re-sending edited messages by default.'),
+    '#description' => t('When users with the "May resend edited messages" permission edit a message and this checkbox is checked, the default setting will be for the edited message to be re-sent. Those users will have the option of not re-sending a message, when it is being edited.'),
+    '#default_value' => variable_get('privatemsg_resend_edited_messages_default', 0),
+  );
 }
 
 /**
diff --git a/privatemsg.admin.inc b/privatemsg.admin.inc
index 841ebd1..e004a32 100644
--- a/privatemsg.admin.inc
+++ b/privatemsg.admin.inc
@@ -221,6 +221,23 @@ function privatemsg_admin_settings() {
     '#default_value' => variable_get('privatemsg_display_on_comments', 0),
   );
 
+  $form['editing_messages'] = array(
+    '#type' => 'fieldset',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#title' => t('Editing messages'),
+    '#description' => t('These settings specify how to deal with editing messages. In addition to these settings, there are five permissions for specifying who may edit which messages and for re-sending edited messages.'),
+    '#weight' => 15,
+    '#group' => 'settings',
+  );
+  $form['editing_messages']['privatemsg_edited_message_display_text'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Edited message display text'),
+    '#default_value' => variable_get('privatemsg_edited_message_display_text', 'This message was modified after sending.'),
+    '#description' => t('Specifies a text string to display at the top of an edited message. If there is text in this field and a message has been edited after sending, then the text in this field will be displayed at the top of the message. Leave the field empty to not display any text.'),
+    '#weight' => 0,
+  );
+
   $form['#submit'][] = 'privatemsg_admin_settings_submit';
   return system_settings_form($form);
 }
diff --git a/privatemsg.module b/privatemsg.module
index bf4e5e3..3feb2b3 100755
--- a/privatemsg.module
+++ b/privatemsg.module
@@ -60,9 +60,25 @@ function privatemsg_permission() {
       'description' => t('Allows to choose the text format when sending private messages. Otherwise, the default is used.'),
     ),
     'report private messages to mollom' => array(
-      'title' => t('Reporte private messages to mollom'),
+      'title' => t('Report private messages to mollom'),
       'description' => t('Allows users to report messages as spam or unwanted content when they delete then, when Mollom is set up to check private messages.'),
     ),
+    'edit own unread privatemsg' => array(
+      'title' => t('Edit own unread private messages'),
+      'description' => t('Allows users to edit private messages they sent and that have not been read by the recipient(s).'),
+    ),
+    'edit own read privatemsg' => array(
+      'title' => t('Edit own read private messages'),
+      'description' => t('Allows users to edit private messages they sent that have been read by the recipient(s).'),
+    ),
+    'edit any unread privatemsg' => array(
+      'title' => t('Edit any unread private messages'),
+      'description' => t('Allows users to edit any private message that has not been read by the recipient(s).'),
+    ),
+    'edit any read privatemsg' => array(
+      'title' => t('Edit any read private messages'),
+      'description' => t('Allows users to edit any private message that has been read by the recipient(s).'),
+    ),
   );
 }
 
@@ -229,6 +245,17 @@ function privatemsg_menu() {
     'weight'           => -3,
     'menu_name'        => 'user-menu',
   );
+  $items['messages/edit/%/%'] = array(
+    'title'            => 'Edit message',
+    'page callback'    => 'drupal_get_form',
+    'page arguments'   => array('privatemsg_message_edit_form', 2, 3),
+    'file'             => 'privatemsg.pages.inc',
+    'access callback'  => 'privatemsg_user_access',
+    'access arguments' => array(3),
+    'type'             => MENU_CALLBACK,
+    'weight'           => -11,
+    'menu_name'        => 'user-menu',
+  );
   // Auto-completes available user names & removes duplicates.
   $items['messages/autocomplete'] = array(
     'page callback'    => 'privatemsg_autocomplete',
@@ -337,20 +364,55 @@ function privatemsg_user_access($permission = 'read privatemsg', $account = NULL
   if (!$account->uid) { // Disallow anonymous access, regardless of permissions
     return FALSE;
   }
-  if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
-    if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
-      $disabled_displayed = TRUE;
-      drupal_set_message(t('You have disabled Privatemsg and are not allowed to write messages. Go to your <a href="@settings_url">Account settings</a> to enable it again.', array('@settings_url' => url('user/' . $account->uid . '/edit'))), 'warning');
-    }
-    return FALSE;
+  // Checks for message edit permission.
+  if (is_numeric($permission)) {
+    return privatemsg_user_access_edit_message($permission);
   }
-  if (!user_access($permission, $account)) {
-    return FALSE;
+  else {
+    if (privatemsg_is_disabled($account) && ($permission == 'write privatemsg') ) {
+      if (arg(0) == 'messages' && variable_get('privatemsg_display_disabled_message', TRUE) && !$disabled_displayed) {
+        $disabled_displayed = TRUE;
+        drupal_set_message(t('You have disabled Privatemsg and are not allowed to write messages. Go to your <a href="@settings_url">Account settings</a> to enable it again.', array('@settings_url' => url('user/' . $account->uid . '/edit'))), 'warning');
+      }
+      return FALSE;
+    }
+    if (!user_access($permission, $account)) {
+      return FALSE;
+    }
   }
   return TRUE;
 }
 
 /**
+ * Access callback for the edit private message functionality.
+ */
+function privatemsg_user_access_edit_message($message_id) {
+  global $user;
+  $message = privatemsg_message_load($message_id);
+
+  // Can the current user edit any unread message?
+  if ($message->is_new && user_access('edit any unread privatemsg')) {
+    return TRUE;
+  }
+  // Can the current user edit any read message?
+  elseif (!$message->is_new && user_access('edit any read privatemsg')) {
+    return TRUE;
+  }
+  // Is the current user the author of the message?
+  elseif ($message->author->uid == $user->uid) {
+    // Is the message unread?
+    if ($message->is_new) {
+      return user_access('edit own unread privatemsg');
+    }
+    else {
+      return user_access('edit own read privatemsg');
+    }
+  }
+
+  return FALSE;
+}
+
+/**
  * Check access to the view messages page.
  *
  * Function to restrict the access of the view messages page to just the
@@ -772,6 +834,9 @@ function template_preprocess_privatemsg_view(&$vars) {
 
   // Render message body.
   $vars['message_body'] =  drupal_render($message->content);
+  if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access($vars['mid'])) {
+    $vars['message_actions'][] = array('title' => t('Edit'), 'href' => 'messages/edit/' . $vars['thread_id'] . '/' . $vars['mid']);
+  }
   if (isset($vars['mid']) && isset($vars['thread_id']) && privatemsg_user_access('delete privatemsg')) {
     $vars['message_actions'][] = array('title' => t('Delete'), 'href' => 'messages/delete/' . $vars['thread_id'] . '/' . $vars['mid']);
   }
@@ -1886,20 +1951,22 @@ function _privatemsg_send($message) {
     drupal_alter('privatemsg_message_presave', $message);
     field_attach_presave('privatemsg_message', $message);
 
-    $query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'recipient', 'type', 'is_new', 'deleted'));
-    if (isset($message->read_all) && $message->read_all && isset($message->thread_id)) {
-      // The message was sent in read all mode, add the author as recipient to all
-      // existing messages.
-      $query_messages = _privatemsg_assemble_query('messages', array($message->thread_id), NULL);
-      foreach ($query_messages->execute()->fetchCol() as $mid) {
-        $query->values(array(
-          'mid' => $mid,
-          'thread_id' => $message->thread_id,
-          'recipient' => $message->author->uid,
-          'type' => 'user',
-          'is_new' => 0,
-          'deleted' => 0,
-        ));
+    if (empty($message->op) || $message->op != 'Save message') {
+      $query = db_insert('pm_index')->fields(array('mid', 'thread_id', 'recipient', 'type', 'is_new', 'deleted'));
+      if (isset($message->read_all) && $message->read_all && isset($message->thread_id)) {
+        // The message was sent in read all mode, add the author as recipient to all
+        // existing messages.
+        $query_messages = _privatemsg_assemble_query('messages', array($message->thread_id), NULL);
+        foreach ($query_messages->execute()->fetchCol() as $mid) {
+          $query->values(array(
+            'mid' => $mid,
+            'thread_id' => $message->thread_id,
+            'recipient' => $message->author->uid,
+            'type' => 'user',
+            'is_new' => 0,
+            'deleted' => 0,
+          ));
+        }
       }
     }
 
@@ -1915,10 +1982,18 @@ function _privatemsg_send($message) {
       $args['reply_to_mid'] = $message->reply_to_mid;
     }
 
-    $mid = db_insert('pm_message')
-      ->fields($args)
-      ->execute();
-    $message->mid = $mid;
+    if (empty($message->op) || $message->op != 'Save message') {
+      $mid = db_insert('pm_message')
+        ->fields($args)
+        ->execute();
+      $message->mid = $mid;
+    }
+    else {
+      db_update('pm_message')
+        ->fields($args)
+        ->condition('mid', $message->mid)
+        ->execute();
+    }
 
     // Thread ID is the same as the mid if it's the first message in the thread.
     if (!isset($message->thread_id)) {
@@ -1927,33 +2002,40 @@ function _privatemsg_send($message) {
 
     // 2) Save message to recipients.
     // Each recipient gets a record in the pm_index table.
-    foreach ($message->recipients as $recipient) {
-      $query->values(array(
-        'mid' => $mid,
-        'thread_id' => $message->thread_id,
-        'recipient' => $recipient->recipient,
-        'type' => $recipient->type,
-        'is_new' => 1,
-        'deleted' => 0,
-      ));
-    }
-
-    // We only want to add the author to the pm_index table, if the message has
-    // not been sent directly to him.
-    if (!isset($message->recipients['user_' . $message->author->uid])) {
-      $query->values(array(
-        'mid' => $mid,
-        'thread_id' => $message->thread_id,
-        'recipient' => $message->author->uid,
-        'type' => 'user',
-        'is_new' => 0,
-        'deleted' => 0,
-      ));
-    }
-    $query->execute();
+    if (empty($message->op) || $message->op != 'Save message') {
+      foreach ($message->recipients as $recipient) {
+        $query->values(array(
+          'mid' => $mid,
+          'thread_id' => $message->thread_id,
+          'recipient' => $recipient->recipient,
+          'type' => $recipient->type,
+          'is_new' => 1,
+          'deleted' => 0,
+        ));
+      }
+
+      // We only want to add the author to the pm_index table, if the message has
+      // not been sent directly to him.
+      if (!isset($message->recipients['user_' . $message->author->uid])) {
+        $query->values(array(
+          'mid' => $mid,
+          'thread_id' => $message->thread_id,
+          'recipient' => $message->author->uid,
+          'type' => 'user',
+          'is_new' => 0,
+          'deleted' => 0,
+        ));
+      }
+      $query->execute();
+    }
 
     module_invoke_all('privatemsg_message_insert', $message);
-    field_attach_insert('privatemsg_message', $message);
+    if (empty($message->op) || $message->op != 'Save message') {
+      field_attach_insert('privatemsg_message', $message);
+    }
+    else {
+      field_attach_update('privatemsg_message', $message);
+    }
 
   } catch (Exception $exception) {
     $transaction->rollback();
diff --git a/privatemsg.pages.inc b/privatemsg.pages.inc
index 0dbd4aa..9c11ff9 100644
--- a/privatemsg.pages.inc
+++ b/privatemsg.pages.inc
@@ -375,6 +375,145 @@ function privatemsg_new($form, &$form_state, $recipients = '', $subject = '') {
 }
 
 /**
+ * Form builder function to edit an existing private message.
+ */
+function privatemsg_message_edit_form($form, $form_state, $thread_id, $message_id) {
+  $thread = privatemsg_thread_load($thread_id);
+  $message = privatemsg_message_load($message_id);
+  $recipients = _privatemsg_get_allowed_recipients($thread['participants'], $thread['thread_id']);
+
+  // Subject has / encoded twice if clean urls are enabled to get it through
+  // mod_rewrite and the menu system. Decode it once more.
+  $subject = str_replace('%2F', '/', $message->subject);
+
+  if (isset($form_state['values'])) {
+    if (isset($form_state['values']['recipient'])) {
+      $recipients_plain = $form_state['values']['recipient'];
+    }
+    $subject   = $form_state['values']['subject'];
+  }
+  else {
+    $to = _privatemsg_get_allowed_recipients($recipients);
+
+    $recipients_plain = '';
+    if (!empty($to)) {
+      $to_plain = array();
+      $to_title = array();
+      foreach ($to as $recipient) {
+        // Load user(s) with that name
+        $to_user = _privatemsg_parse_userstring($recipient->name);
+        // If the count of duplicated is more than 0 that means the recipient name is used by the other recipient type,
+        // so we should use unique.
+        $to_plain[] = privatemsg_recipient_format($recipient, array('plain' => TRUE, 'unique' => (count($to_user[2]) > 0)));
+        $to_title[] = privatemsg_recipient_format($recipient, array('plain' => TRUE));
+      }
+      $recipients_plain = implode(', ', $to_plain);
+      $recipients_title = implode(', ', $to_title);
+    }
+  }
+
+  // Add in the base Privatemsg form elements.
+  $form += _privatemsg_form_base_fields($form, $form_state, $message);
+
+  if ($message->is_new) {
+    if (!empty($recipients_title)) {
+      drupal_set_title(t('Edit existing unread message to %recipient', array('%recipient' => $recipients_title)), PASS_THROUGH);
+    }
+    else {
+      drupal_set_title(t('Edit existing unread message'));
+    }
+  }
+  else {
+    if (!empty($recipients_title)) {
+      drupal_set_title(t('Edit existing read message to %recipient', array('%recipient' => $recipients_title)), PASS_THROUGH);
+    }
+    else {
+      drupal_set_title(t('Edit existing read message'));
+    }
+
+    // Add a warning message to be displayed.
+    $markup_message =
+        '<br /><em>'
+        . t('You are editing a message that has been read by one or more participants.')
+        . '<br />';
+    if (!empty(variable_get('privatemsg_edited_message_display_text', 'This message was modified after sending.'))) {
+      $markup_message .= t('Based on administrative configuration settings, a warning indicating that the message was edited will be displayed at the top of the message when viewed.');
+    }
+    $markup_message .= '</em><br /><br />';
+    $form['edit_read_message_warning'] = array(
+      '#markup' => $markup_message,
+      '#weight' => -5,
+    );
+  }
+
+  // The author gets changed, if a user other than the original author is
+  // editing the message, so it needs to be set back to what it originally was.
+  $form['author']['#value'] = $message->author;
+
+  $description_array = array();
+  foreach (privatemsg_recipient_get_types() as $name => $type) {
+    if (privatemsg_recipient_access($name, 'write')) {
+      $description_array[] = $type['description'];
+    }
+  }
+  $description = t('Enter the recipient, separate recipients with commas.');
+  $description .= theme('item_list', array('items' => $description_array));
+
+  $form['recipient'] = array(
+    '#type'               => 'textfield',
+    '#title'              => t('To'),
+    '#description'        => $description,
+    '#default_value'      => $recipients_plain,
+    '#required'           => TRUE,
+    '#weight'             => -10,
+    '#size'               => 50,
+    '#autocomplete_path'  => 'messages/autocomplete',
+    // Disable #maxlength, make it configurable by number of recipients, not
+    // their name length.
+    '#after_build'        => array('privatemsg_disable_maxlength'),
+  );
+  $form['subject'] = array(
+    '#type'               => 'textfield',
+    '#title'              => t('Subject'),
+    '#size'               => 50,
+    '#maxlength'          => 255,
+    '#default_value'      => $subject,
+    '#weight'             => -5,
+  );
+
+  $url = 'messages';
+  if (isset($_REQUEST['destination'])) {
+    $url = $_REQUEST['destination'];
+  }
+
+  $form['actions']['cancel'] = array(
+    '#value'  => l(t('Cancel'), $url, array('attributes' => array('id' => 'edit-cancel'))),
+    '#weight' => 20,
+  );
+
+  if (!empty($message->thread_id)) {
+    $form['thread_id'] = array(
+      '#type'          => 'hidden',
+      '#default_value' => $message->thread_id,
+    );
+  }
+  if (!empty($message->mid)) {
+    $form['mid'] = array(
+      '#type'          => 'hidden',
+      '#default_value' => $message->mid,
+    );
+  }
+  if (!empty($message->reply_to_mid)) {
+    $form['reply_to_mid'] = array(
+      '#type'          => 'hidden',
+      '#default_value' => $message->reply_to_mid,
+    );
+  }
+
+  return $form;
+}
+
+/**
  * Remove the maxlength attribute from a field.
  */
 function privatemsg_disable_maxlength($element) {
@@ -444,7 +583,7 @@ function privatemsg_form_reply($form, &$form_state, $thread) {
 /**
  * Returns the common fields of the reply and new form.
  */
-function _privatemsg_form_base_fields($form, &$form_state) {
+function _privatemsg_form_base_fields($form, &$form_state, $message = array()) {
   global $user;
 
   if (isset($form_state['privatemsg_preview'])) {
@@ -486,6 +625,9 @@ function _privatemsg_form_base_fields($form, &$form_state) {
     '#format'             => isset($format) ? $format : NULL,
     '#after_build'        => array('privatemsg_check_format_access'),
   );
+  if (!empty($message) && !empty($message->body)) {
+      $form['body']['#default_value'] = $message->body;
+  }
 
   if (privatemsg_user_access('use tokens in privatemsg') && module_exists('token')) {
     $form['token'] = array(
@@ -519,9 +661,14 @@ function _privatemsg_form_base_fields($form, &$form_state) {
     '#validate' => array('privatemsg_new_validate'),
     '#submit'   => array('privatemsg_new_submit'),
   );
+  if (!empty($message)) {
+    $form['actions']['submit']['#value'] = t('Save message');
+  }
 
   // Attach field widgets.
-  $message = (object) array();
+  if (empty($message)) {
+    $message = (object) array();
+  }
   if (isset($form_state['validate_built_message'])) {
     $message = $form_state['validate_built_message'];
   }
@@ -611,6 +758,20 @@ function privatemsg_new_validate($form, &$form_state) {
   $message->format    = $message->body['format'];
   $message->body      = $message->body['value'];
   $message->timestamp = REQUEST_TIME;
+  $message = (object) $form_state['values'];
+  if (empty($form_state['values']['mid'])) {
+    $message->mid       = 0;
+    $message->timestamp = REQUEST_TIME;
+  }
+  else {
+    $message->mid       = $form_state['values']['mid'];
+    $old_message        = privatemsg_message_load($form_state['values']['mid']);
+    $message->timestamp = $old_message->timestamp;
+  }
+  $message->format  = $message->body['format'];
+  $message->body    = $message->body['value'];
+  $message->subject = trim($message->subject);
+
   // Avoid subjects which only consist of a space as these can not be clicked.
   $message->subject = trim($message->subject);
 
@@ -624,7 +785,7 @@ function privatemsg_new_validate($form, &$form_state) {
   }
   else {
     // Load participants. Limit recipients to visible unless read_all is TRUE.
-    $message->recipients = _privatemsg_load_thread_participants($message->thread_id, $message->read_all ? FALSE : $message->author);
+    $message->recipients = _privatemsg_load_thread_participants($message->thread_id, (isset($message->read_all) && $message->read_all) ? FALSE : $message->author);
   }
 
   if (!empty($invalid)) {
@@ -701,8 +862,10 @@ function privatemsg_new_submit($form, &$form_state) {
   }
   try {
     $message = _privatemsg_send($message);
-    _privatemsg_handle_recipients($message->mid, $message->recipients);
-    drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
+    if (!$message->op == 'Save message') {
+      _privatemsg_handle_recipients($message->mid, $message->recipients);
+      drupal_set_message(t('A message has been sent to !recipients.', array('!recipients' => implode(', ', $recipient_names))));
+    }
     // Only redirect on new threads.
     if ($message->mid == $message->thread_id || variable_get('privatemsg_default_redirect_reply', FALSE)) {
       $redirect = variable_get('privatemsg_default_redirect', '<new-message>');
