diff --git a/bootstrap.php b/bootstrap.php
index 056c5f01dcd4098d7f8649be2211249a7b9f20f8..6977d3385f9321c1865f3f41222cdbbaceb004d3 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -102,6 +102,7 @@ class Bootstrap {
         define('THREAD_COLLABORATOR_TABLE', $prefix.'thread_collaborator');
         define('TICKET_STATUS_TABLE', $prefix.'ticket_status');
         define('TICKET_PRIORITY_TABLE',$prefix.'ticket_priority');
+        define('EVENT_TABLE',$prefix.'event');
 
         define('TASK_TABLE', $prefix.'task');
         define('TASK_CDATA_TABLE', $prefix.'task__cdata');
diff --git a/include/class.i18n.php b/include/class.i18n.php
index 17709bee89b112527e2158910641d18608c64555..ab692eb6292cd2a4181249920eadc04006054eeb 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -64,6 +64,7 @@ class Internationalization {
             'ticket_status.yaml' => 'TicketStatus::__create',
             // Role
             'role.yaml' =>          'Role::__create',
+            'event.yaml' =>         'Event::__create',
             'file.yaml' =>          'AttachmentFile::__create',
             'sequence.yaml' =>      'Sequence::__create',
             'queue_column.yaml' =>  'QueueColumn::__create',
diff --git a/include/class.report.php b/include/class.report.php
index 12d7322d3dbc6665fed18b86d8b738d49eb091ba..dbff0ba38f68714692c091d9214b079223351360 100644
--- a/include/class.report.php
+++ b/include/class.report.php
@@ -77,26 +77,31 @@ class OverviewReport {
 
     function getPlotData() {
         list($start, $stop) = $this->getDateRange();
+        $states = array("created", "closed", "reopened", "assigned", "overdue", "transferred");
+        $event_ids = Event::getIds();
 
         # Fetch all types of events over the timeframe
-        $res = db_query('SELECT DISTINCT(state) FROM '.THREAD_EVENT_TABLE
+        $res = db_query('SELECT DISTINCT(E.name) FROM '.THREAD_EVENT_TABLE
+            .' T JOIN '.EVENT_TABLE . ' E ON E.id = T.event_id'
             .' WHERE timestamp BETWEEN '.$start.' AND '.$stop
-            .' AND state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
+            .' AND T.event_id IN ('.implode(",",$event_ids).')'
             .' ORDER BY 1');
         $events = array();
         while ($row = db_fetch_row($res)) $events[] = $row[0];
 
         # TODO: Handle user => db timezone offset
         # XXX: Implement annulled column from the %ticket_event table
-        $res = db_query('SELECT state, DATE_FORMAT(timestamp, \'%Y-%m-%d\'), '
+        $res = db_query('SELECT H.name, DATE_FORMAT(timestamp, \'%Y-%m-%d\'), '
                 .'COUNT(DISTINCT T.id)'
             .' FROM '.THREAD_EVENT_TABLE. ' E '
+            . ' LEFT JOIN '.EVENT_TABLE. ' H
+                ON (E.event_id = H.id)'
             .' JOIN '.THREAD_TABLE. ' T
                 ON (T.id = E.thread_id AND T.object_type = "T") '
             .' WHERE E.timestamp BETWEEN '.$start.' AND '.$stop
             .' AND NOT annulled'
-            .' AND E.state IN ("created", "closed", "reopened", "assigned", "overdue", "transferred")'
-            .' GROUP BY E.state, DATE_FORMAT(E.timestamp, \'%Y-%m-%d\')'
+            .' AND E.event_id IN ('.implode(",",$event_ids).')'
+            .' GROUP BY E.event_id, DATE_FORMAT(E.timestamp, \'%Y-%m-%d\')'
             .' ORDER BY 2, 1');
         # Initialize array of plot values
         $plots = array();
@@ -139,6 +144,11 @@ class OverviewReport {
     function getTabularData($group='dept') {
         global $thisstaff;
 
+        $event_ids = Event::getIds();
+        $event = function ($name) use ($event_ids) {
+            return $event_ids[$name];
+        };
+
         list($start, $stop) = $this->getDateRange();
         $times = ThreadEvent::objects()
             ->constrain(array(
@@ -148,8 +158,8 @@ class OverviewReport {
                ))
             ->constrain(array(
                 'thread__events' => array(
-                    'thread__events__state' => 'created',
-                    'state' => 'closed',
+                    'thread__events__event_id' => $event('created'),
+                    'event_id' => $event('closed'),
                     'annulled' => 0,
                     ),
                 ))
@@ -174,27 +184,27 @@ class OverviewReport {
                 ->aggregate(array(
                     'Opened' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'created')), 1)
+                            ->when(new Q(array('event_id' => $event('created'))), 1)
                     ),
                     'Assigned' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'assigned')), 1)
+                            ->when(new Q(array('event_id' => $event('assigned'))), 1)
                     ),
                     'Overdue' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'overdue')), 1)
+                            ->when(new Q(array('event_id' => $event('overdue'))), 1)
                     ),
                     'Closed' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'closed')), 1)
+                            ->when(new Q(array('event_id' => $event('closed'))), 1)
                     ),
                     'Reopened' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'reopened')), 1)
+                            ->when(new Q(array('event_id' => $event('reopened'))), 1)
                     ),
                     'Deleted' => SqlAggregate::COUNT(
                         SqlCase::N()
-                            ->when(new Q(array('state' => 'deleted')), 1)
+                            ->when(new Q(array('event_id' => $event('deleted'))), 1)
                     ),
                 ));
 
diff --git a/include/class.thread.php b/include/class.thread.php
index 3d367a902f496b69e54d4bc5fc3fd710a4ef3e0a..467ed6c0c46b2c981d662291a4bee88571b191de 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -2052,16 +2052,80 @@ class ThreadEvent extends VerySimpleModel {
                     $subclasses[$class::$state] = $class;
             }
         }
+        $this->state = Event::getNameById($this->event_id);
         if (!($class = $subclasses[$this->state]))
             return $this;
         return new $class($this->ht);
     }
 }
 
+class Event extends VerySimpleModel {
+    static $meta = array(
+        'table' => EVENT_TABLE,
+        'pk' => array('id'),
+    );
+
+    function getInfo() {
+        return $this->ht;
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function getName() {
+        return $this->name;
+    }
+
+    function getNameById($id) {
+        return array_search($id, self::getIds());
+    }
+
+    function getIdByName($name) {
+         $ids =  self::getIds();
+         return $ids[$name] ?: 0;
+    }
+
+    function getDescription() {
+        return $this->description;
+    }
+
+    static function getIds() {
+        static $ids;
+
+        if (!isset($ids)) {
+            $ids = array();
+            $events = static::objects()->values_flat('id', 'name');
+            foreach ($events as $row) {
+                list($id, $name) = $row;
+                $ids[$name] = $id;
+            }
+        }
+
+        return $ids;
+    }
+
+    static function create($vars=false, &$errors=array()) {
+        $event = new static($vars);
+        return $event;
+    }
+
+    static function __create($vars, &$errors=array()) {
+        $event = self::create($vars);
+        $event->save();
+        return $event;
+    }
+
+    function save($refetch=false) {
+        return parent::save($refetch);
+    }
+}
+
 class ThreadEvents extends InstrumentedList {
     function annul($event) {
+        $event_id = Event::getIdByName($event);
         $this->queryset
-            ->filter(array('state' => $event))
+            ->filter(array('event_id' => $event_id))
             ->update(array('annulled' => 1));
     }
 
@@ -2116,7 +2180,7 @@ class ThreadEvents extends InstrumentedList {
             }
         }
         $event->username = $username;
-        $event->state = $state;
+        $event->event_id = Event::getIdByName($state);
 
         if ($data) {
             if (is_array($data))
diff --git a/include/class.user.php b/include/class.user.php
index 0fa35e4a14d62514632bcf163f80fc25e216b49c..f2d3e2fd0e8c9e4cff961248ce1abe51a062a646 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -635,7 +635,8 @@ implements TemplateVariable, Searchable {
     }
 
     function deleteAllTickets() {
-        $deleted = TicketStatus::lookup(array('state' => 'deleted'));
+        $event_id = Event::getIdByName('deleted');
+        $deleted = TicketStatus::lookup(array('event_id' => $event_id));
         foreach($this->tickets as $ticket) {
             if (!$T = Ticket::lookup($ticket->getId()))
                 continue;
diff --git a/include/client/templates/thread-entries.tmpl.php b/include/client/templates/thread-entries.tmpl.php
index 93843210969e4e3d79aea05b4ad692805dc5f95f..8457698caf820605482ee839492d4001fc6fbb55 100644
--- a/include/client/templates/thread-entries.tmpl.php
+++ b/include/client/templates/thread-entries.tmpl.php
@@ -1,6 +1,12 @@
 <?php
+$states = array('created', 'closed', 'reopened', 'edited', 'collab');
+$event_ids = array();
+foreach ($states as $state) {
+    $eid = Event::getIdByName($state);
+    $event_ids[] = $eid;
+}
 $events = $events
-    ->filter(array('state__in' => array('created', 'closed', 'reopened', 'edited', 'collab')))
+    ->filter(array('event_id__in' => $event_ids))
     ->order_by('id');
 $events = new IteratorIterator($events->getIterator());
 $events->rewind();
diff --git a/include/i18n/en_US/event.yaml b/include/i18n/en_US/event.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7c9c8ed5bb93d531a50a21f6b84f6a4caf0f07ee
--- /dev/null
+++ b/include/i18n/en_US/event.yaml
@@ -0,0 +1,61 @@
+#
+# event.yaml
+#
+# Events initially inserted into the system.
+#
+---
+- id: 1
+  name: created
+  description:
+
+- id: 2
+  name: closed
+  description:
+
+- id: 3
+  name: reopened
+  description:
+
+- id: 4
+  name: assigned
+  description:
+
+- id: 5
+  name: released
+  description:
+
+- id: 6
+  name: transferred
+  description:
+
+- id: 7
+  name: referred
+  description:
+
+- id: 8
+  name: overdue
+  description:
+
+- id: 9
+  name: edited
+  description:
+
+- id: 10
+  name: viewed
+  description:
+
+- id: 11
+  name: error
+  description:
+
+- id: 12
+  name: collab
+  description:
+
+- id: 13
+  name: resent
+  description:
+
+- id: 14
+  name: deleted
+  description:
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index fbebb45e63ee1f85f4388d1ead81fa6b7403dd4f..00b20916567f6418d4c640ae0895c26b3499d242 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-26fd79dc5443f37779f9d2c4108058f4
+00c949a623b82848baaf3480b51307e3
diff --git a/include/upgrader/streams/core/0ca85857-86707325.patch.sql b/include/upgrader/streams/core/0ca85857-86707325.patch.sql
index 2962d23e40f284614bd877ee0317e1183f7a21bf..2daaf3b7cdab96ac80265205de51dbe6fc0926ef 100644
--- a/include/upgrader/streams/core/0ca85857-86707325.patch.sql
+++ b/include/upgrader/streams/core/0ca85857-86707325.patch.sql
@@ -17,10 +17,6 @@ CREATE TABLE `%TABLE_PREFIX%thread_referral` (
   KEY `thread_id` (`thread_id`)
 ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
 
-ALTER TABLE `%TABLE_PREFIX%thread_event`
-  CHANGE `state` `state` enum('created','closed','reopened','assigned','transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL;
-
-
  -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
     SET `value` = '86707325fc571e56242fccc46fd24466'
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql b/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql
new file mode 100644
index 0000000000000000000000000000000000000000..50cad9d04f5cb4a2ee7b773c2841637a6659a2de
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.cleanup.sql
@@ -0,0 +1,3 @@
+-- Drop the state field from thread_events
+ALTER TABLE `%TABLE_PREFIX%thread_event`
+    DROP COLUMN `state`;
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql b/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..774e248b961eee5cf30ee8459e3ca76496e27059
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.patch.sql
@@ -0,0 +1,45 @@
+/**
+* @signature 00c949a623b82848baaf3480b51307e3
+* @version v1.11.0
+* @title Database Optimization
+*
+* This patch is for optimizing our database to handle large amounts of data
+* more smoothly.
+*
+* 1. remove states in thread_event table and add them to their own event table
+*/
+
+-- Create a new table to store events
+CREATE TABLE `%TABLE_PREFIX%event` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL,
+  `description` varchar(60) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+
+INSERT INTO `%TABLE_PREFIX%event` (`id`, `name`, `description`)
+VALUES
+	(1,'created',''),
+	(2,'closed',''),
+	(3,'reopened',''),
+	(4,'assigned',''),
+	(5,'released',''),
+	(6,'transferred',''),
+	(7,'referred',''),
+	(8,'overdue',''),
+	(9,'edited',''),
+	(10,'viewed',''),
+	(11,'error',''),
+	(12,'collab',''),
+	(13,'resent',''),
+	(14,'deleted','');
+
+-- Add event_id column to thread_events
+ALTER TABLE `%TABLE_PREFIX%thread_event`
+    ADD `event_id` int(11) unsigned AFTER `thread_id`;
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+   SET `value` = '00c949a623b82848baaf3480b51307e3', `updated` = NOW()
+   WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/26fd79dc-00c949a6.task.php b/include/upgrader/streams/core/26fd79dc-00c949a6.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..d3d6a2084ac31d8cab76465794c8992a3d6e69e3
--- /dev/null
+++ b/include/upgrader/streams/core/26fd79dc-00c949a6.task.php
@@ -0,0 +1,143 @@
+<?php
+
+class EventEnumRemoval extends MigrationTask {
+    var $description = "Remove the Enum 'state' field from ThreadEvents";
+    var $queue;
+    var $skipList;
+    var $errorList = array();
+
+    function sleep() {
+        return array('queue'=>$this->queue, 'skipList'=>$this->skipList);
+    }
+    function wakeup($stuff) {
+        $this->queue = $stuff['queue'];
+        $this->skipList = $stuff['skipList'];
+        while (!$this->isFinished())
+            $this->do_batch(30, 5000);
+    }
+
+    function run($max_time) {
+        $this->do_batch($max_time * 0.9, 5000);
+    }
+
+    function isFinished() {
+        return $this->getQueueLength() == 0;
+    }
+
+    function do_batch($time=30, $max=0) {
+        if(!$this->queueEvents($max) || !$this->getQueueLength())
+            return 0;
+
+        $this->setStatus("{$this->getQueueLength()} events remaining");
+
+        $count = 0;
+        $start = Misc::micro_time();
+        while ($this->getQueueLength() && (Misc::micro_time()-$start) < $time) {
+            if($this->next() && $max && ++$count>=$max) {
+                break;
+            }
+        }
+
+        return $this->queueEvents($max);
+    }
+
+    function queueEvents($limit=0){
+        global $cfg, $ost;
+
+        # Since the queue is persistent - we want to make sure we get to empty
+        # before we find more events.
+        if(($qc=$this->getQueueLength()))
+            return $qc;
+
+        $sql = "SELECT this.id, this.state, that.id, that.name FROM ".THREAD_EVENT_TABLE. " this
+            INNER JOIN (
+            SELECT id, name
+            FROM ". EVENT_TABLE. ") that
+            WHERE this.state = that.name
+            AND event_id IS NULL";
+
+        if(($skipList=$this->getSkipList()))
+            $sql.= ' AND this.id NOT IN('.implode(',', db_input($skipList)).')';
+
+        if($limit && is_numeric($limit))
+            $sql.=' LIMIT '.$limit;
+
+        //XXX: Do a hard fail or error querying the database?
+        if(!($res=db_query($sql)))
+            return $this->error('Unable to query DB for Thread Event migration!');
+
+        // Force the log message to the database
+        $ost->logDebug("Thread Event Migration", 'Found '.db_num_rows($res)
+            .' events to migrate', true);
+
+        if(!db_num_rows($res))
+            return 0;  //Nothing else to do!!
+
+        $this->queue = array();
+        while (list($id, $state, $eventId, $eventName)=db_fetch_row($res)) {
+            $info=array(
+                'id'        => $id,
+                'state'     => $state,
+                'eventId'   => $eventId,
+                'eventName' => $eventName,
+            );
+            $this->enqueue($info);
+        }
+
+        return $this->getQueueLength();
+    }
+
+    function skip($eventId, $error) {
+        $this->skipList[] = $eventId;
+
+        return $this->error($error." (ID #$eventId)");
+    }
+
+    function error($what) {
+        global $ost;
+
+        $this->errors++;
+        $this->errorList[] = $what;
+        // Log the error but don't send the alert email
+        $ost->logError('Upgrader: Thread Event Migrater', $what, false);
+        # Assist in returning FALSE for inline returns with this method
+        return false;
+    }
+
+    function getErrors() {
+        return $this->errorList;
+    }
+
+    function getSkipList() {
+        return $this->skipList;
+    }
+
+    function enqueue($info) {
+        $this->queue[] = $info;
+    }
+
+    function getQueueLength() {
+        return count($this->queue);
+    }
+
+    function next() {
+        # Fetch next item -- use the last item so the array indices don't
+        # need to be recalculated for every shift() operation.
+        $info = array_pop($this->queue);
+
+        if (!$info['state']) {
+            # Continue with next thread event
+            return $this->skip($info['eventId'],
+            sprintf('Thread Event ID %s: State is blank', $info['eventId']));
+        }
+
+        db_query('update '.THREAD_EVENT_TABLE
+            .' set event_id='.db_input($info['eventId'])
+            .' where state='.db_input($info['eventName'])
+            .' and id='.db_input($info['id']));
+
+        return true;
+    }
+}
+return 'EventEnumRemoval';
+?>
diff --git a/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql b/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
index f3bc7e0b201ea76bb59c6ff4de8f1ed272740d82..fd5953f5b6870f0cd53ffd5e42f8e36ea0019670 100644
--- a/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
+++ b/include/upgrader/streams/core/70921d5c-26fd79dc.patch.sql
@@ -5,10 +5,6 @@
 *
 * This patch is for final revisions needed for v1.11
 */
-
-ALTER TABLE `%TABLE_PREFIX%thread_event`
-    CHANGE `state` `state` enum('created','closed','reopened','assigned', 'released', 'transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL;
-
 ALTER TABLE `%TABLE_PREFIX%attachment`
     ADD INDEX `file_object` (`file_id`,`object_id`);
 
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index fbbb6e45050facca4ad2c6390657073d14b23c96..d023e83ca506ac49e41578256a0a7ead0cb41766 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -711,15 +711,24 @@ CREATE TABLE `%TABLE_PREFIX%lock` (
   KEY `staff_id` (`staff_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%event`;
+CREATE TABLE `%TABLE_PREFIX%event` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL,
+  `description` varchar(60) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_event`;
 CREATE TABLE `%TABLE_PREFIX%thread_event` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `thread_id` int(11) unsigned NOT NULL default '0',
+  `event_id` int(11) unsigned DEFAULT NULL,
   `staff_id` int(11) unsigned NOT NULL,
   `team_id` int(11) unsigned NOT NULL,
   `dept_id` int(11) unsigned NOT NULL,
   `topic_id` int(11) unsigned NOT NULL,
-  `state` enum('created','closed','reopened','assigned','released','transferred', 'referred', 'overdue','edited','viewed','error','collab','resent', 'deleted') NOT NULL,
   `data` varchar(1024) DEFAULT NULL COMMENT 'Encoded differences',
   `username` varchar(128) NOT NULL default 'SYSTEM',
   `uid` int(11) unsigned DEFAULT NULL,
@@ -727,8 +736,8 @@ CREATE TABLE `%TABLE_PREFIX%thread_event` (
   `annulled` tinyint(1) unsigned NOT NULL default '0',
   `timestamp` datetime NOT NULL,
   PRIMARY KEY (`id`),
-  KEY `ticket_state` (`thread_id`, `state`, `timestamp`),
-  KEY `ticket_stats` (`timestamp`, `state`)
+  KEY `ticket_state` (`thread_id`, `event_id`, `timestamp`),
+  KEY `ticket_stats` (`timestamp`, `event_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_referral`;