<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://rt-wiki.bestpractical.com/index.php?action=history&amp;feed=atom&amp;title=Rt2Mbox</id>
	<title>Rt2Mbox - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://rt-wiki.bestpractical.com/index.php?action=history&amp;feed=atom&amp;title=Rt2Mbox"/>
	<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Rt2Mbox&amp;action=history"/>
	<updated>2026-08-22T06:48:30Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Rt2Mbox&amp;diff=3272&amp;oldid=prev</id>
		<title>Admin: 2 revisions imported</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Rt2Mbox&amp;diff=3272&amp;oldid=prev"/>
		<updated>2016-04-06T20:36:21Z</updated>

		<summary type="html">&lt;p&gt;2 revisions imported&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Rt2Mbox =&lt;br /&gt;
&lt;br /&gt;
This script connects to the RT database and exports all of the mail into mbox format. An mbox file is created for each queue in the current directory. It does a little bit of subject tweaking to try to encourage good threading habits, and the results imported into Mail.app very nicely. This script also extremely fast, since it doesn&amp;#039;t use the RT API.&lt;br /&gt;
&lt;br /&gt;
A to how to use it, well just run it and it will tell you how. :-)&lt;br /&gt;
&lt;br /&gt;
Prerequisites:&lt;br /&gt;
&lt;br /&gt;
* DBI&lt;br /&gt;
* Your DBD (DBD::mysql, DBD::Pg, etc.).&lt;br /&gt;
* Mail::Box perl-module&lt;br /&gt;
* POSIX (comes with Perl)&lt;br /&gt;
* Getopt::Long (comes with Perl)&lt;br /&gt;
&lt;br /&gt;
Caveat:&lt;br /&gt;
&lt;br /&gt;
* I&amp;#039;ve tested this with my [[PostgreSQL]] database and added code so that it should also work with [[MySQL]], but all other databases might have to tweak the &amp;lt;code&amp;gt;$epoch&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;#!/usr/bin/perl -w&lt;br /&gt;
 &lt;br /&gt;
 use strict;&lt;br /&gt;
 use DBI;&lt;br /&gt;
 use Getopt::Long qw(:config auto_help auto_version);&lt;br /&gt;
 use POSIX qw(asctime);&lt;br /&gt;
 our $VERSION = &amp;#039;0.01&amp;#039;;&lt;br /&gt;
 &lt;br /&gt;
 GetOptions(&lt;br /&gt;
     &amp;#039;dsn|d=s&amp;#039;          =&amp;amp;gt; \my $dsn,&lt;br /&gt;
     &amp;#039;username|u=s&amp;#039;     =&amp;amp;gt; \my $username,&lt;br /&gt;
     &amp;#039;password|p=s&amp;#039;     =&amp;amp;gt; \my $password,&lt;br /&gt;
     &amp;#039;rtname|n=s&amp;#039;       =&amp;amp;gt; \my $rtname,&lt;br /&gt;
     &amp;#039;no-autoreplies|x&amp;#039; =&amp;amp;gt; \my $noauto,&lt;br /&gt;
 )  or require Pod::Usage &amp;amp;amp;&amp;amp;amp; Pod::Usage::pod2usage(2);&lt;br /&gt;
 &lt;br /&gt;
 require Pod::Usage &amp;amp;amp;&amp;amp;amp; Pod::Usage::pod2usage(1) unless $dsn &amp;amp;amp;&amp;amp;amp; $rtname;&lt;br /&gt;
 &lt;br /&gt;
 my $dbh = DBI-&amp;amp;gt;connect(&lt;br /&gt;
     $dsn,&lt;br /&gt;
     $username,&lt;br /&gt;
     $password,&lt;br /&gt;
     { RaiseError =&amp;amp;gt; 1 }&lt;br /&gt;
 );&lt;br /&gt;
 &lt;br /&gt;
 END {&lt;br /&gt;
     $dbh-&amp;amp;gt;disconnect if $dbh;&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 my $qsel = $dbh-&amp;amp;gt;prepare(q{&lt;br /&gt;
     SELECT id, name, correspondaddress, commentaddress&lt;br /&gt;
       FROM queues&lt;br /&gt;
      ORDER BY name&lt;br /&gt;
 });&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 my $exclude = $noauto ? &amp;quot;\n       AND users.id &amp;amp;lt;&amp;amp;gt; 1&amp;quot; : &amp;#039;&amp;#039;;&lt;br /&gt;
 my $epoch   = $dsn =~ /^dbi:Pg/&lt;br /&gt;
     ? &amp;#039;EXTRACT( EPOCH FROM attachments.created )&amp;#039;&lt;br /&gt;
     : &amp;#039;UNIX_TIMESTAMP( attachments.created )&amp;#039;;&lt;br /&gt;
 &lt;br /&gt;
 my $msel = $dbh-&amp;amp;gt;prepare(qq{&lt;br /&gt;
     SELECT tickets.id, transactions.id, transactions.type, attachments.id,&lt;br /&gt;
            parent, users.emailaddress,&lt;br /&gt;
            contenttype, COALESCE( contentencoding, &amp;#039;&amp;#039; ),&lt;br /&gt;
            $epoch,&lt;br /&gt;
            headers, content, tickets.subject&lt;br /&gt;
       FROM tickets, transactions, attachments, users&lt;br /&gt;
      WHERE tickets.queue = ?&lt;br /&gt;
        AND tickets.id = transactions.objectid&lt;br /&gt;
        AND transactions.objecttype = ?&lt;br /&gt;
        AND transactions.id = attachments.transactionid&lt;br /&gt;
        AND transactions.creator = users.id$exclude&lt;br /&gt;
      ORDER BY transactions.id, attachments.id, parent&lt;br /&gt;
 });&lt;br /&gt;
 &lt;br /&gt;
 $qsel-&amp;amp;gt;execute;&lt;br /&gt;
 $qsel-&amp;amp;gt;bind_columns( \my ( $qid, $qname, $correspond, $comment ) );&lt;br /&gt;
 &lt;br /&gt;
 while ($qsel-&amp;amp;gt;fetch) {&lt;br /&gt;
     # Normalize the name.&lt;br /&gt;
     $qname =~ s{[/.]}{}g;&lt;br /&gt;
 &lt;br /&gt;
     # Open the mbox file.&lt;br /&gt;
     open my $mbox, &amp;#039;&amp;amp;gt;&amp;#039;, &amp;quot;$qname.mbox&amp;quot; or die &amp;quot;Cannot open $qname.mbox: $!\n&amp;quot;;&lt;br /&gt;
     $msel-&amp;amp;gt;execute( $qid, &amp;#039;RT::Ticket&amp;#039; );&lt;br /&gt;
     $msel-&amp;amp;gt;bind_columns(\my ( $tid, $txid, $ttype, $aid, $parent, $email, $type, $encoding, $time, $headers, $body, $subject));&lt;br /&gt;
 &lt;br /&gt;
     my @parents;&lt;br /&gt;
     my @bounds;&lt;br /&gt;
     while ($msel-&amp;amp;gt;fetch) {&lt;br /&gt;
         if (!$parent) {&lt;br /&gt;
             # Top level of a message. Close the previous message.&lt;br /&gt;
             @parents = ();&lt;br /&gt;
             while (my $bound = pop @bounds) {&lt;br /&gt;
                 print $mbox &amp;quot;--$bound--\n\n&amp;quot;;&lt;br /&gt;
             }&lt;br /&gt;
 &lt;br /&gt;
             # Start the new message.&lt;br /&gt;
             ( my $asctime = asctime( gmtime $time ) ) =~ s/\s+$//ms;&lt;br /&gt;
             $email ||= $ttype =~ /comment/i ? $comment : $correspond;&lt;br /&gt;
             print $mbox &amp;quot;From $email $asctime\n&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
             # Make sure that the body ends in a blank line.&lt;br /&gt;
             $body =~ s/(\n{0,2})?$/\n\n/ if $body;&lt;br /&gt;
 &lt;br /&gt;
             # Adjust headers. They always end with a single newline.&lt;br /&gt;
             $headers .= &amp;quot;RT-Transaction-Id: $txid\n&amp;quot;;&lt;br /&gt;
             if ( $headers !~ /^From:/mi ) {&lt;br /&gt;
                 $headers .= &amp;quot;From: $email\n&amp;quot;;&lt;br /&gt;
             }&lt;br /&gt;
             if ( $headers !~ /^Subject:/mi ) {&lt;br /&gt;
                 $headers .= &amp;quot;Subject: [$rtname #$tid] $subject\n&amp;quot;;&lt;br /&gt;
             } else {&lt;br /&gt;
                 $headers =~ s/^(Subject:\s*(?:(?:Re|Fwd):\s*)?)/$1\[$rtname #$tid] /mi&lt;br /&gt;
                     unless $headers =~ /^Subject:\s*(?:(?:Re|Fwd):\s*)?[[]\Q$rtname\E/mi&lt;br /&gt;
             }&lt;br /&gt;
 &lt;br /&gt;
         } else {&lt;br /&gt;
             # Close any parents.&lt;br /&gt;
             while (@parents &amp;amp;amp;&amp;amp;amp; $parents[-1] != $parent) {&lt;br /&gt;
                 pop @parents;&lt;br /&gt;
                 my $bound = pop @bounds;&lt;br /&gt;
                 print $mbox &amp;quot;--$bound--\n\n&amp;quot;;&lt;br /&gt;
             }&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         # Add the Attachment ID to the headers, so simplify backtracking.&lt;br /&gt;
         $headers .= &amp;quot;RT-Attachment-Id: $aid\n&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
         # Handle this part.&lt;br /&gt;
         if ($type =~ m{^multipart/}) {&lt;br /&gt;
             # Part is multipart. Push the parent ID onto the stack.&lt;br /&gt;
             push @parents, $aid;&lt;br /&gt;
 &lt;br /&gt;
             # Determine the bounary.&lt;br /&gt;
             my $bound;&lt;br /&gt;
             if ($headers =~ qr{\bboundary=(.+)}mi) {&lt;br /&gt;
                 # Retain the original boundary.&lt;br /&gt;
                 ($bound = $1) =~ s/^&amp;quot;//;&lt;br /&gt;
                 $bound =~ s/&amp;quot;$//;&lt;br /&gt;
             } else {&lt;br /&gt;
                 # Just invent a boundary.&lt;br /&gt;
                 $bound = join &amp;#039;&amp;#039;, (&amp;#039;a&amp;#039;..&amp;#039;z&amp;#039;, &amp;#039;A&amp;#039;..&amp;#039;Z&amp;#039;, 0..9)[ map { rand 62 } 0..10];&lt;br /&gt;
             }&lt;br /&gt;
 &lt;br /&gt;
             # Output the part.&lt;br /&gt;
             print $mbox &amp;quot;--$bounds[-1]\n&amp;quot; if @bounds;&lt;br /&gt;
             print $mbox &amp;quot;$headers\n&amp;quot;;&lt;br /&gt;
             print $mbox $body if $body;&lt;br /&gt;
 &lt;br /&gt;
             # Push the boundary onto the stack.&lt;br /&gt;
             push @bounds, $bound;&lt;br /&gt;
         } else {&lt;br /&gt;
             # Is a standalone part.&lt;br /&gt;
             print $mbox &amp;quot;--$bounds[-1]\n&amp;quot; if @bounds;&lt;br /&gt;
             print $mbox &amp;quot;$headers\n&amp;quot;;&lt;br /&gt;
             print $mbox $body if $body;&lt;br /&gt;
         }&lt;br /&gt;
     }&lt;br /&gt;
     close $mbox or die &amp;quot;Cannot close $qname.mbox: $!\n&amp;quot;;&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 1;&lt;br /&gt;
 __END__&lt;br /&gt;
 &lt;br /&gt;
 =head1 Name&lt;br /&gt;
 &lt;br /&gt;
 rt2mbox - Export RT messages to mbox files, one for each queue&lt;br /&gt;
 &lt;br /&gt;
 =head1 Synopsis&lt;br /&gt;
 &lt;br /&gt;
 =begin comment&lt;br /&gt;
 &lt;br /&gt;
 Fake-out Pod::Usage&lt;br /&gt;
 &lt;br /&gt;
 =head1 SYNOPSIS&lt;br /&gt;
 &lt;br /&gt;
 =end comment&lt;br /&gt;
 &lt;br /&gt;
   rt2mbox --dsn dbi:Pgdbname=rt3 --username rt_user --password secret&lt;br /&gt;
 &lt;br /&gt;
 =head1 Options&lt;br /&gt;
 &lt;br /&gt;
   -d --dsn DSN            RT database DSN to which DBI can connect. Required.&lt;br /&gt;
   -n --rtname   NAME      RT name, usually domain name. Required.&lt;br /&gt;
   -u --username USERNAME  RT database username.&lt;br /&gt;
   -p --password PASSWORD  RT database password.&lt;br /&gt;
   -x --no-autoreplies     Do not export autoreply messages.&lt;br /&gt;
 &lt;br /&gt;
 =head1 Author&lt;br /&gt;
 &lt;br /&gt;
 David E. Wheeler &amp;amp;lt;david@kineticode.com&amp;amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 =head1 Copyright and License&lt;br /&gt;
 &lt;br /&gt;
 Copyright (c) 2008 David E. Wheeler. All Rights Reserved.&lt;br /&gt;
 &lt;br /&gt;
 This module is free software; you can redistribute it and/or modify it under&lt;br /&gt;
 the same terms as Perl itself.&lt;br /&gt;
 &lt;br /&gt;
 =cut&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
User Notes To get this to work for mysql in rt 3.8.2 I had to change all the references to tables in the script to start with capitals eg &amp;quot;From queues&amp;quot; changed to From Queues otherwise I got errors saying the tables didn&amp;#039;t exist.&lt;/div&gt;</summary>
		<author><name>Admin</name></author>
	</entry>
</feed>