Problem: You have a site where users are sending each other private messages. These messages are accumulating and you want a way to automatically delete messages that are older than, e.g., 3 months, since they are no longer needed and are just bloating your site.

Solution:

There are 2 stages to permanently deleting old private messages from your DB.

(A)

(1) Go to "Site configuration > Private messages > Private messages"
(2) Expand "Flush deleted messages"
(3) Select the checkbox "Flush deleted messages" and configure other settings as required.

These settings means that any messages which have been deleted by users will be "flushed" - permanently erased - from the DB when cron runs. However, what about messages that are old but haven't yet been selected by a user for deletion? These old messages are just filling up users' private message inboxes and we want to force deletion of these too. So...

(B)

We need to run the following script with cron (you can find out how to create scripts to run with cron elsewhere on drupal.org). This uses a function in private message module that sets the pm_index.deleted field for a message to "1" meaning its status is "deleted". Once its status is "deleted", it will get erased when "Flush deleted messages" runs (see above).

$sql = 'SELECT mid FROM {pm_message} WHERE timestamp < %d';

// Messages older than $timestamp are deleted, example, 90 days ago.
$timestamp = time() - (90 * 86400); 

$result = db_query($sql, $timestamp);

// Fetch over all found messages and mark them as deleted
while ($row = db_fetch_object($result)) {
  privatemsg_message_change_delete($row->mid, TRUE);
}

One final thing, if your site is large and you have lots of old private messages piled up that need deleting, it's probably a good idea to limit the number of messages that are affected the first few times that the above script runs. To do this use "db_query_range" instead of "db_query". For example:

$result = db_query_range($sql, $timestamp, 0, 500); // limit to 500 per run

--
And thanks to Berdir for a great module and for generous support with questions :)

--------Addition / Edit to work with Drupal 7---------------------

Although this may not be to Drupal Coding standards, this edited code works with Drupal 7 as a cron job.

$sql = 'SELECT mid FROM {pm_message} WHERE timestamp < :date';

// Messages older than $timestamp are deleted, example, 90 days ago.
$timestamp = time() - (90 * 86400); 

$result = db_query_range($sql, 0, 650, array(':date' => $timestamp));

// Fetch over all found messages and mark them as deleted
foreach ($result as $results) {
  privatemsg_message_change_delete($results->mid, TRUE);
}