diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index f01b2c7799b6f015b177f0e57f42473055547bd2..ca5c732a9beb089047fcb8084e2cf0621961a0df 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -104,12 +104,13 @@ interface Database<T> {
 	void addMessageToAck(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
-	 * Records a collection of sent messages as needing to be acknowledged.
+	 * Records the given messages as needing to be acknowledged by the given
+	 * expiry time.
 	 * <p>
 	 * Locking: contact read, message write.
 	 */
-	void addOutstandingMessages(T txn, ContactId c, Collection<MessageId> sent)
-			throws DbException;
+	void addOutstandingMessages(T txn, ContactId c, Collection<MessageId> sent,
+			long expiry) throws DbException;
 
 	/**
 	 * Stores the given message, or returns false if the message is already in
@@ -128,6 +129,15 @@ interface Database<T> {
 	void addSecrets(T txn, Collection<TemporarySecret> secrets)
 			throws DbException;
 
+	/**
+	 * Initialises the status (seen or unseen) of the given message with
+	 * respect to the given contact.
+	 * <p>
+	 * Locking: contact read, message write.
+	 */
+	void addStatus(T txn, ContactId c, MessageId m, boolean seen)
+			throws DbException;
+
 	/**
 	 * Subscribes to the given group.
 	 * <p>
@@ -614,14 +624,6 @@ interface Database<T> {
 	boolean setStarredFlag(T txn, MessageId m, boolean starred)
 			throws DbException;
 
-	/**
-	 * Sets the status of the given message with respect to the given contact.
-	 * <p>
-	 * Locking: contact read, message write.
-	 */
-	void setStatus(T txn, ContactId c, MessageId m, Status s)
-			throws DbException;
-
 	/**
 	 * If the database contains the given message and it belongs to a group
 	 * that is visible to the given contact, marks the message as seen by the
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 37c0c40809b942109dc80698dc7fe14f159a8732..4b8ff546e84ecd37ff4c24477457ef64272858f0 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -7,8 +7,6 @@ import static net.sf.briar.db.DatabaseConstants.CRITICAL_FREE_SPACE;
 import static net.sf.briar.db.DatabaseConstants.MAX_BYTES_BETWEEN_SPACE_CHECKS;
 import static net.sf.briar.db.DatabaseConstants.MAX_MS_BETWEEN_SPACE_CHECKS;
 import static net.sf.briar.db.DatabaseConstants.MIN_FREE_SPACE;
-import static net.sf.briar.db.Status.NEW;
-import static net.sf.briar.db.Status.SEEN;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -277,11 +275,11 @@ DatabaseCleaner.Callback {
 		boolean stored = db.addGroupMessage(txn, m);
 		// Mark the message as seen by the sender
 		MessageId id = m.getId();
-		if(sender != null) db.setStatus(txn, sender, id, SEEN);
+		if(sender != null) db.addStatus(txn, sender, id, true);
 		if(stored) {
 			// Mark the message as unseen by other contacts
 			for(ContactId c : db.getContacts(txn)) {
-				if(!c.equals(sender)) db.setStatus(txn, c, id, NEW);
+				if(!c.equals(sender)) db.addStatus(txn, c, id, false);
 			}
 			// Calculate and store the message's sendability
 			int sendability = calculateSendability(txn, m);
@@ -441,8 +439,8 @@ DatabaseCleaner.Callback {
 		if(m.getAuthor() != null) throw new IllegalArgumentException();
 		if(!db.addPrivateMessage(txn, m, c)) return false;
 		MessageId id = m.getId();
-		if(incoming) db.setStatus(txn, c, id, SEEN);
-		else db.setStatus(txn, c, id, NEW);
+		if(incoming) db.addStatus(txn, c, id, true);
+		else db.addStatus(txn, c, id, false);
 		// Count the bytes stored
 		synchronized(spaceLock) {
 			bytesStoredSinceLastCheck += m.getSerialised().length;
@@ -526,7 +524,8 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					db.addOutstandingMessages(txn, c, ids);
+					// FIXME: Calculate the expiry time
+					db.addOutstandingMessages(txn, c, ids, Long.MAX_VALUE);
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
@@ -585,7 +584,8 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					db.addOutstandingMessages(txn, c, ids);
+					// FIXME: Calculate the expiry time
+					db.addOutstandingMessages(txn, c, ids, Long.MAX_VALUE);
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
diff --git a/briar-core/src/net/sf/briar/db/DatabaseModule.java b/briar-core/src/net/sf/briar/db/DatabaseModule.java
index ce5cdcf6bc2a13815921eb30f95520721b5f6087..fccc1e44484377b595a6f6367c9112474fdab900 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseModule.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseModule.java
@@ -4,6 +4,7 @@ import java.sql.Connection;
 import java.util.concurrent.Executor;
 
 import net.sf.briar.api.clock.Clock;
+import net.sf.briar.api.clock.SystemClock;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DatabaseExecutor;
@@ -41,7 +42,7 @@ public class DatabaseModule extends AbstractModule {
 
 	@Provides
 	Database<Connection> getDatabase(DatabaseConfig config) {
-		return new H2Database(config);
+		return new H2Database(config, new SystemClock());
 	}
 
 	@Provides @Singleton
diff --git a/briar-core/src/net/sf/briar/db/H2Database.java b/briar-core/src/net/sf/briar/db/H2Database.java
index eeaf79069ad2bf42191360a61c268e80189d26fb..6a396bddd0575aebb9bd5ba56a9eefd28d07a804 100644
--- a/briar-core/src/net/sf/briar/db/H2Database.java
+++ b/briar-core/src/net/sf/briar/db/H2Database.java
@@ -8,6 +8,7 @@ import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Properties;
 
+import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.crypto.Password;
 import net.sf.briar.api.db.DatabaseConfig;
 import net.sf.briar.api.db.DbException;
@@ -29,8 +30,8 @@ class H2Database extends JdbcDatabase {
 	private final long maxSize;
 
 	@Inject
-	H2Database(DatabaseConfig config) {
-		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE);
+	H2Database(DatabaseConfig config, Clock clock) {
+		super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
 		home = new File(config.getDataDirectory(), "db");
 		url = "jdbc:h2:split:" + home.getPath()
 				+ ";CIPHER=AES;MULTI_THREADED=1;DB_CLOSE_ON_EXIT=false";
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index c2494b3e9500d26921684d56d28905e7904db25c..596b7a8632100047227a1fdba213be7fc2f7dbc7 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -5,9 +5,6 @@ import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static net.sf.briar.api.Rating.UNRATED;
 import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
-import static net.sf.briar.db.Status.NEW;
-import static net.sf.briar.db.Status.SEEN;
-import static net.sf.briar.db.Status.SENT;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -31,6 +28,7 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportProperties;
+import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.db.DbClosedException;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.MessageHeader;
@@ -157,7 +155,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			"CREATE TABLE statuses"
 					+ " (messageId HASH NOT NULL,"
 					+ " contactId INT NOT NULL,"
-					+ " status SMALLINT NOT NULL,"
+					+ " seen BOOLEAN NOT NULL,"
+					+ " transmissionCount INT NOT NULL,"
+					+ " expiry BIGINT NOT NULL,"
 					+ " PRIMARY KEY (messageId, contactId),"
 					+ " FOREIGN KEY (messageId)"
 					+ " REFERENCES messages (messageId)"
@@ -298,6 +298,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	// Different database libraries use different names for certain types
 	private final String hashType, binaryType, counterType, secretType;
+	private final Clock clock;
 
 	private final LinkedList<Connection> connections =
 			new LinkedList<Connection>(); // Locking: self
@@ -308,11 +309,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 	protected abstract Connection createConnection() throws SQLException;
 
 	JdbcDatabase(String hashType, String binaryType, String counterType,
-			String secretType) {
+			String secretType, Clock clock) {
 		this.hashType = hashType;
 		this.binaryType = binaryType;
 		this.counterType = counterType;
 		this.secretType = secretType;
+		this.clock = clock;
 	}
 
 	protected void open(boolean resume, File dir, String driverClass)
@@ -646,18 +648,19 @@ abstract class JdbcDatabase implements Database<Connection> {
 	}
 
 	public void addOutstandingMessages(Connection txn, ContactId c,
-			Collection<MessageId> sent) throws DbException {
+			Collection<MessageId> sent, long expiry) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			// Set the status of each message to SENT if it's currently NEW
-			String sql = "UPDATE statuses SET status = ?"
-					+ " WHERE messageId = ? AND contactId = ? AND status = ?";
+			// Update the transmission count and expiry time of each message
+			String sql = "UPDATE statuses SET expiry = ?,"
+					+ " transmissionCount = transmissionCount + ?"
+					+ " WHERE messageId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setShort(1, (short) SENT.ordinal());
-			ps.setInt(3, c.getInt());
-			ps.setShort(4, (short) NEW.ordinal());
+			ps.setLong(1, expiry);
+			ps.setInt(2, 1);
+			ps.setInt(4, c.getInt());
 			for(MessageId m : sent) {
-				ps.setBytes(2, m.getBytes());
+				ps.setBytes(3, m.getBytes());
 				ps.addBatch();
 			}
 			int[] batchAffected = ps.executeBatch();
@@ -705,6 +708,26 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void addStatus(Connection txn, ContactId c, MessageId m,
+			boolean seen) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "INSERT INTO statuses"
+					+ " (messageId, contactId, seen, transmissionCount, expiry)"
+					+ " VALUES (?, ?, ?, ZERO(), ZERO())";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, m.getBytes());
+			ps.setInt(2, c.getInt());
+			ps.setBoolean(3, seen);
+			int affected = ps.executeUpdate();
+			if(affected != 1) throw new DbStateException();
+			ps.close();
+		} catch(SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void addSubscription(Connection txn, Group g) throws DbException {
 		PreparedStatement ps = null;
 		try {
@@ -1220,6 +1243,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public Collection<MessageId> getMessagesToOffer(Connection txn,
 			ContactId c, int maxMessages) throws DbException {
+		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1227,11 +1251,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String sql = "SELECT m.messageId FROM messages AS m"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " WHERE m.contactId = ? AND status = ?"
+					+ " WHERE m.contactId = ? AND seen = FALSE AND expiry < ?"
 					+ " ORDER BY timestamp DESC LIMIT ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			ps.setInt(3, maxMessages);
 			rs = ps.executeQuery();
 			List<MessageId> ids = new ArrayList<MessageId>();
@@ -1254,12 +1278,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
 					+ " AND timestamp >= retention"
-					+ " AND status = ?"
+					+ " AND seen = FALSE AND expiry < ?"
 					+ " AND sendability > ZERO()"
 					+ " ORDER BY timestamp DESC LIMIT ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			ps.setInt(3, maxMessages - ids.size());
 			rs = ps.executeQuery();
 			while(rs.next()) ids.add(new MessageId(rs.getBytes(2)));
@@ -1383,6 +1407,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public byte[] getRawMessageIfSendable(Connection txn, ContactId c,
 			MessageId m) throws DbException {
+		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1391,11 +1416,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
 					+ " WHERE m.messageId = ? AND m.contactId = ?"
-					+ " AND status = ?";
+					+ " AND seen = FALSE AND expiry < ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			ps.setInt(2, c.getInt());
-			ps.setShort(3, (short) NEW.ordinal());
+			ps.setLong(3, now);
 			rs = ps.executeQuery();
 			byte[] raw = null;
 			if(rs.next()) {
@@ -1422,12 +1447,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " WHERE m.messageId = ?"
 					+ " AND cg.contactId = ?"
 					+ " AND timestamp >= retention"
-					+ " AND status = ?"
+					+ " AND seen = FALSE AND expiry < ?"
 					+ " AND sendability > ZERO()";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			ps.setInt(2, c.getInt());
-			ps.setShort(3, (short) NEW.ordinal());
+			ps.setLong(3, now);
 			rs = ps.executeQuery();
 			if(rs.next()) {
 				int length = rs.getInt(1);
@@ -1631,6 +1656,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public Collection<MessageId> getSendableMessages(Connection txn,
 			ContactId c, int maxLength) throws DbException {
+		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1638,11 +1664,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String sql = "SELECT length, m.messageId FROM messages AS m"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " WHERE m.contactId = ? AND status = ?"
+					+ " WHERE m.contactId = ? AND seen = FALSE AND expiry < ?"
 					+ " ORDER BY timestamp DESC";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			rs = ps.executeQuery();
 			List<MessageId> ids = new ArrayList<MessageId>();
 			int total = 0;
@@ -1669,12 +1695,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
 					+ " AND timestamp >= retention"
-					+ " AND status = ?"
+					+ " AND seen = FALSE AND expiry < ?"
 					+ " AND sendability > ZERO()"
 					+ " ORDER BY timestamp DESC";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			rs = ps.executeQuery();
 			while(rs.next()) {
 				int length = rs.getInt(1);
@@ -1988,6 +2014,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 
 	public boolean hasSendableMessages(Connection txn, ContactId c)
 			throws DbException {
+		long now = clock.currentTimeMillis();
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -1995,11 +2022,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String sql = "SELECT m.messageId FROM messages AS m"
 					+ " JOIN statuses AS s"
 					+ " ON m.messageId = s.messageId"
-					+ " WHERE m.contactId = ? AND status = ?"
+					+ " WHERE m.contactId = ? AND seen = FALSE AND expiry < ?"
 					+ " LIMIT ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			ps.setInt(3, 1);
 			rs = ps.executeQuery();
 			boolean found = rs.next();
@@ -2021,12 +2048,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " AND cg.contactId = s.contactId"
 					+ " WHERE cg.contactId = ?"
 					+ " AND timestamp >= retention"
-					+ " AND status = ?"
+					+ " AND seen = FALSE AND expiry < ?"
 					+ " AND sendability > ZERO()"
 					+ " LIMIT ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setShort(2, (short) NEW.ordinal());
+			ps.setLong(2, now);
 			ps.setInt(3, 1);
 			rs = ps.executeQuery();
 			found = rs.next();
@@ -2100,13 +2127,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			Collection<MessageId> acked) throws DbException {
 		PreparedStatement ps = null;
 		try {
-			// Set the status of each message to SEEN if it's currently SENT
-			String sql = "UPDATE statuses SET status = ?"
-					+ " WHERE messageId = ? AND contactId = ? AND status = ?";
+			// Set the status of each message to seen = true
+			String sql = "UPDATE statuses SET seen = TRUE"
+					+ " WHERE messageId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setShort(1, (short) SEEN.ordinal());
-			ps.setInt(3, c.getInt());
-			ps.setShort(4, (short) SENT.ordinal());
+			ps.setInt(1, c.getInt());
 			for(MessageId m : acked) {
 				ps.setBytes(2, m.getBytes());
 				ps.addBatch();
@@ -2612,55 +2637,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void setStatus(Connection txn, ContactId c, MessageId m, Status s)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT status FROM statuses"
-					+ " WHERE messageId = ? AND contactId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, m.getBytes());
-			ps.setInt(2, c.getInt());
-			rs = ps.executeQuery();
-			if(rs.next()) {
-				// A status row exists - update it if necessary
-				Status old = Status.values()[rs.getByte(1)];
-				if(rs.next()) throw new DbStateException();
-				rs.close();
-				ps.close();
-				if(old != SEEN && old != s) {
-					sql = "UPDATE statuses SET status = ?"
-							+ " WHERE messageId = ? AND contactId = ?";
-					ps = txn.prepareStatement(sql);
-					ps.setShort(1, (short) s.ordinal());
-					ps.setBytes(2, m.getBytes());
-					ps.setInt(3, c.getInt());
-					int affected = ps.executeUpdate();
-					if(affected != 1) throw new DbStateException();
-					ps.close();
-				}
-			} else {
-				// No status row exists - create one
-				rs.close();
-				ps.close();
-				sql = "INSERT INTO statuses (messageId, contactId, status)"
-						+ " VALUES (?, ?, ?)";
-				ps = txn.prepareStatement(sql);
-				ps.setBytes(1, m.getBytes());
-				ps.setInt(2, c.getInt());
-				ps.setShort(3, (short) s.ordinal());
-				int affected = ps.executeUpdate();
-				if(affected != 1) throw new DbStateException();
-				ps.close();
-			}
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public boolean setStatusSeenIfVisible(Connection txn, ContactId c,
 			MessageId m) throws DbException {
 		PreparedStatement ps = null;
@@ -2686,10 +2662,10 @@ abstract class JdbcDatabase implements Database<Connection> {
 			rs.close();
 			ps.close();
 			if(!found) return false;
-			sql = "UPDATE statuses SET status = ?"
+			sql = "UPDATE statuses SET seen = ?"
 					+ " WHERE messageId = ? AND contactId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setShort(1, (short) SEEN.ordinal());
+			ps.setBoolean(1, true);
 			ps.setBytes(2, m.getBytes());
 			ps.setInt(3, c.getInt());
 			int affected = ps.executeUpdate();
diff --git a/briar-core/src/net/sf/briar/db/Status.java b/briar-core/src/net/sf/briar/db/Status.java
deleted file mode 100644
index 8fcda6803bd2690d6b3cb3458da82c2dd0f89c93..0000000000000000000000000000000000000000
--- a/briar-core/src/net/sf/briar/db/Status.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.sf.briar.db;
-
-/** The status of a message with respect to a particular contact. */
-enum Status {
-	/** The message has not been sent, received, or acked. */
-	NEW,
-	/** The message has been sent, but not received or acked. */
-	SENT,
-	/** The message has been received or acked. */
-	SEEN
-}
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index d3b04d168ba7890768a6a78ce3c02a71a6672866..6099975be85d806a061b9ab5d5725f11b0cfa5ad 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -2,8 +2,6 @@ package net.sf.briar.db;
 
 import static net.sf.briar.api.Rating.GOOD;
 import static net.sf.briar.api.Rating.UNRATED;
-import static net.sf.briar.db.Status.NEW;
-import static net.sf.briar.db.Status.SEEN;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -364,7 +362,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contactId)));
-			oneOf(database).setStatus(txn, contactId, messageId, NEW);
+			oneOf(database).addStatus(txn, contactId, messageId, false);
 			// The author is unrated and there are no sendable children
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(UNRATED));
@@ -399,7 +397,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contactId)));
-			oneOf(database).setStatus(txn, contactId, messageId, NEW);
+			oneOf(database).addStatus(txn, contactId, messageId, false);
 			// The author is rated GOOD and there are two sendable children
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(GOOD));
@@ -460,7 +458,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// addLocalPrivateMessage(privateMessage, contactId)
 			oneOf(database).addPrivateMessage(txn, privateMessage, contactId);
 			will(returnValue(true));
-			oneOf(database).setStatus(txn, contactId, messageId, NEW);
+			oneOf(database).addStatus(txn, contactId, messageId, false);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
@@ -698,7 +696,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getRawMessage(txn, messageId1);
 			will(returnValue(raw1));
 			// Record the outstanding messages
-			oneOf(database).addOutstandingMessages(txn, contactId, sendable);
+			// FIXME: Calculate the expiry time
+			oneOf(database).addOutstandingMessages(txn, contactId, sendable,
+					Long.MAX_VALUE);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
@@ -734,8 +734,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getRawMessageIfSendable(txn, contactId, messageId2);
 			will(returnValue(null)); // Message is not sendable
 			// Record the outstanding messages
+			// FIXME: Calculate the expiry time
 			oneOf(database).addOutstandingMessages(txn, contactId,
-					Collections.singletonList(messageId1));
+					Collections.singletonList(messageId1), Long.MAX_VALUE);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
 				shutdown);
@@ -922,7 +923,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// The message is stored
 			oneOf(database).addPrivateMessage(txn, privateMessage, contactId);
 			will(returnValue(true));
-			oneOf(database).setStatus(txn, contactId, messageId, SEEN);
+			oneOf(database).addStatus(txn, contactId, messageId, true);
 			// The message must be acked
 			oneOf(database).addMessageToAck(txn, contactId, messageId);
 		}});
@@ -1011,7 +1012,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// The message is stored, but it's a duplicate
 			oneOf(database).addGroupMessage(txn, message);
 			will(returnValue(false));
-			oneOf(database).setStatus(txn, contactId, messageId, SEEN);
+			oneOf(database).addStatus(txn, contactId, messageId, true);
 			// The message must be acked
 			oneOf(database).addMessageToAck(txn, contactId, messageId);
 		}});
@@ -1043,8 +1044,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// The message is stored, and it's not a duplicate
 			oneOf(database).addGroupMessage(txn, message);
 			will(returnValue(true));
-			oneOf(database).setStatus(txn, contactId, messageId, SEEN);
-			// Set the status to NEW for all other contacts (there are none)
+			oneOf(database).addStatus(txn, contactId, messageId, true);
+			// Set the status to seen = true for all other contacts (none)
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contactId)));
 			// Calculate the sendability - zero, so ancestors aren't updated
@@ -1085,8 +1086,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// The message is stored, and it's not a duplicate
 			oneOf(database).addGroupMessage(txn, message);
 			will(returnValue(true));
-			oneOf(database).setStatus(txn, contactId, messageId, SEEN);
-			// Set the status to NEW for all other contacts (there are none)
+			oneOf(database).addStatus(txn, contactId, messageId, true);
+			// Set the status to seen = true for all other contacts (none)
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contactId)));
 			// Calculate the sendability - ancestors are updated
@@ -1214,7 +1215,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contactId)));
-			oneOf(database).setStatus(txn, contactId, messageId, NEW);
+			oneOf(database).addStatus(txn, contactId, messageId, false);
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(UNRATED));
 			oneOf(database).getNumberOfSendableChildren(txn, messageId);
@@ -1250,7 +1251,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// addLocalPrivateMessage(privateMessage, contactId)
 			oneOf(database).addPrivateMessage(txn, privateMessage, contactId);
 			will(returnValue(true));
-			oneOf(database).setStatus(txn, contactId, messageId, NEW);
+			oneOf(database).addStatus(txn, contactId, messageId, false);
 			// The message was added, so the listener should be called
 			oneOf(listener).eventOccurred(with(any(MessageAddedEvent.class)));
 		}});
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 15d2ec9b61d2051621bd9061575f6c10dbcac6a6..17546db32a11b47a94684ccbf7a4320b6fccae50 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -3,9 +3,6 @@ package net.sf.briar.db;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static net.sf.briar.api.Rating.GOOD;
 import static net.sf.briar.api.Rating.UNRATED;
-import static net.sf.briar.db.Status.NEW;
-import static net.sf.briar.db.Status.SEEN;
-import static net.sf.briar.db.Status.SENT;
 import static org.junit.Assert.assertArrayEquals;
 
 import java.io.File;
@@ -29,6 +26,7 @@ import net.sf.briar.TestUtils;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.TransportConfig;
 import net.sf.briar.api.TransportProperties;
+import net.sf.briar.api.clock.SystemClock;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.MessageHeader;
 import net.sf.briar.api.messaging.AuthorId;
@@ -224,7 +222,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testSendablePrivateMessagesMustHaveStatusNew()
+	public void testSendablePrivateMessagesMustHaveSeenFlagFalse()
 			throws Exception {
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -239,25 +237,14 @@ public class H2DatabaseTest extends BriarTestCase {
 				db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
 
-		// Changing the status to NEW should make the message sendable
-		db.setStatus(txn, contactId, messageId1, NEW);
+		// Adding a status with seen = false should make the message sendable
+		db.addStatus(txn, contactId, messageId1, false);
 		assertTrue(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
 		assertEquals(messageId1, it.next());
 		assertFalse(it.hasNext());
 
-		// Changing the status to SENT should make the message unsendable
-		db.setStatus(txn, contactId, messageId1, SENT);
-		assertFalse(db.hasSendableMessages(txn, contactId));
-		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
-		assertFalse(it.hasNext());
-
-		// Changing the status to SEEN should also make the message unsendable
-		db.setStatus(txn, contactId, messageId1, SEEN);
-		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
-		assertFalse(it.hasNext());
-
 		db.commitTransaction(txn);
 		db.close();
 	}
@@ -271,7 +258,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Add a contact and store a private message
 		assertEquals(contactId, db.addContact(txn));
 		db.addPrivateMessage(txn, privateMessage, contactId);
-		db.setStatus(txn, contactId, messageId1, NEW);
+		db.addStatus(txn, contactId, messageId1, false);
 
 		// The message is sendable, but too large to send
 		assertTrue(db.hasSendableMessages(txn, contactId));
@@ -302,7 +289,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addVisibility(txn, contactId, groupId);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The message should not be sendable
 		assertFalse(db.hasSendableMessages(txn, contactId));
@@ -329,7 +316,7 @@ public class H2DatabaseTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testSendableGroupMessagesMustHaveStatusNew()
+	public void testSendableGroupMessagesMustHaveSeenFlagFalse()
 			throws Exception {
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -348,25 +335,20 @@ public class H2DatabaseTest extends BriarTestCase {
 				db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
 
-		// Changing the status to NEW should make the message sendable
-		db.setStatus(txn, contactId, messageId, NEW);
+		// Adding a status with seen = false should make the message sendable
+		db.addStatus(txn, contactId, messageId, false);
 		assertTrue(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 		assertFalse(it.hasNext());
 
-		// Changing the status to SENT should make the message unsendable
-		db.setStatus(txn, contactId, messageId, SENT);
+		// Changing the status to seen = true should make the message unsendable
+		db.setStatusSeenIfVisible(txn, contactId, messageId);
 		assertFalse(db.hasSendableMessages(txn, contactId));
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
 		assertFalse(it.hasNext());
 
-		// Changing the status to SEEN should also make the message unsendable
-		db.setStatus(txn, contactId, messageId, SEEN);
-		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
-		assertFalse(it.hasNext());
-
 		db.commitTransaction(txn);
 		db.close();
 	}
@@ -382,7 +364,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addVisibility(txn, contactId, groupId);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The contact is not subscribed, so the message should not be sendable
 		assertFalse(db.hasSendableMessages(txn, contactId));
@@ -420,7 +402,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The message is sendable, but too large to send
 		assertTrue(db.hasSendableMessages(txn, contactId));
@@ -450,7 +432,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The subscription is not visible to the contact, so the message
 		// should not be sendable
@@ -534,7 +516,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// Retrieve the message from the database and mark it as sent
 		Iterator<MessageId> it =
@@ -542,9 +524,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 		assertFalse(it.hasNext());
-		db.setStatus(txn, contactId, messageId, SENT);
+		// FIXME: Calculate the expiry time
 		db.addOutstandingMessages(txn, contactId,
-				Collections.singletonList(messageId));
+				Collections.singletonList(messageId), Long.MAX_VALUE);
 
 		// The message should no longer be sendable
 		it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
@@ -913,11 +895,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 
-		// Set the sendability to > 0 and the status to SEEN
+		// Set the sendability to > 0 and the status to seen = true
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, SEEN);
+		db.addStatus(txn, contactId, messageId, true);
 
-		// The message is not sendable because its status is SEEN
+		// The message is not sendable because its status is seen = true
 		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
 
 		db.commitTransaction(txn);
@@ -936,9 +918,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 
-		// Set the sendability to 0 and the status to NEW
+		// Set the sendability to 0 and the status to seen = false
 		db.setSendability(txn, messageId, 0);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The message is not sendable because its sendability is 0
 		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
@@ -961,9 +943,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setRetentionTime(txn, contactId, timestamp + 1, 1);
 		db.addGroupMessage(txn, message);
 
-		// Set the sendability to > 0 and the status to NEW
+		// Set the sendability to > 0 and the status to seen = false
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The message is not sendable because it's too old
 		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
@@ -984,9 +966,9 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
 		db.addGroupMessage(txn, message);
 
-		// Set the sendability to > 0 and the status to NEW
+		// Set the sendability to > 0 and the status to seen = false
 		db.setSendability(txn, messageId, 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The message is sendable so it should be returned
 		byte[] b = db.getRawMessageIfSendable(txn, contactId, messageId);
@@ -1042,7 +1024,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// There's no contact subscription for the group
 		assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1062,7 +1044,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message);
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1);
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		// The subscription is not visible
 		assertFalse(db.setStatusSeenIfVisible(txn, contactId, messageId));
@@ -1085,7 +1067,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addGroupMessage(txn, message);
 
 		// The message has already been seen by the contact
-		db.setStatus(txn, contactId, messageId, SEEN);
+		db.addStatus(txn, contactId, messageId, true);
 
 		assertTrue(db.setStatusSeenIfVisible(txn, contactId, messageId));
 
@@ -1107,7 +1089,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addGroupMessage(txn, message);
 
 		// The message has not been seen by the contact
-		db.setStatus(txn, contactId, messageId, NEW);
+		db.addStatus(txn, contactId, messageId, false);
 
 		assertTrue(db.setStatusSeenIfVisible(txn, contactId, messageId));
 
@@ -1828,7 +1810,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	private Database<Connection> open(boolean resume) throws Exception {
 		Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
-				MAX_SIZE));
+				MAX_SIZE), new SystemClock());
 		db.open(resume);
 		return db;
 	}