diff --git a/briar-android/res/values/roboguice.xml b/briar-android/res/values/roboguice.xml
index 7f33cf6d5cbccb03cc89a9b36e70c2c06d6a34c5..25a6e1ffada620c8e03b0d52eee8fab069a3cb5a 100644
--- a/briar-android/res/values/roboguice.xml
+++ b/briar-android/res/values/roboguice.xml
@@ -13,6 +13,7 @@
 		<item>org.briarproject.lifecycle.LifecycleModule</item>
 		<item>org.briarproject.messaging.MessagingModule</item>
 		<item>org.briarproject.plugins.AndroidPluginsModule</item>
+		<item>org.briarproject.property.PropertyModule</item>
 		<item>org.briarproject.sync.SyncModule</item>
 		<item>org.briarproject.system.AndroidSystemModule</item>
 		<item>org.briarproject.transport.TransportModule</item>
diff --git a/briar-android/src/org/briarproject/android/TestingActivity.java b/briar-android/src/org/briarproject/android/TestingActivity.java
index 9146f9759e29b5ca3e1161d4f01ccf37e75db0a1..fb9b7fb912532e0ed0487864010a1156375ed5a0 100644
--- a/briar-android/src/org/briarproject/android/TestingActivity.java
+++ b/briar-android/src/org/briarproject/android/TestingActivity.java
@@ -30,11 +30,11 @@ import org.briarproject.android.util.ListLoadingProgressBar;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.android.AndroidExecutor;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.util.StringUtils;
 
 import java.io.File;
@@ -84,7 +84,7 @@ public class TestingActivity extends BriarActivity implements OnClickListener {
 	@Inject private AndroidExecutor androidExecutor;
 	@Inject private PluginManager pluginManager;
 	@Inject private LifecycleManager lifecycleManager;
-	@Inject private DatabaseComponent db;
+	@Inject private TransportPropertyManager transportPropertyManager;
 	private ScrollView scroll = null;
 	private ListLoadingProgressBar progress = null;
 	private LinearLayout status = null;
@@ -364,7 +364,7 @@ public class TestingActivity extends BriarActivity implements OnClickListener {
 		Map<TransportId, TransportProperties> props = Collections.emptyMap();
 		try {
 			lifecycleManager.waitForDatabase();
-			props = db.getLocalProperties();
+			props = transportPropertyManager.getLocalProperties();
 		} catch (InterruptedException e) {
 			LOG.info("Interrupted while waiting for database");
 			Thread.currentThread().interrupt();
diff --git a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
index 960bdd639341cf5b09f7fc9ee695fcf5c218ae93..ade6ab4c745d72044feb0f4592efd621d4c2b9ba 100644
--- a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
+++ b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
@@ -1,6 +1,7 @@
 package org.briarproject.api.messaging;
 
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
@@ -10,6 +11,12 @@ import java.util.Collection;
 
 public interface MessagingManager {
 
+	/**
+	 * Informs the messaging manager that a new contact has been added.
+	 * Creates a private conversation with the contact.
+	 */
+	void addContact(ContactId c, SecretKey master) throws DbException;
+
 	/** Stores a local private message. */
 	void addLocalMessage(Message m) throws DbException;
 
diff --git a/briar-api/src/org/briarproject/api/property/TransportPropertyManager.java b/briar-api/src/org/briarproject/api/property/TransportPropertyManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce7bc96e1814c9ee95ae3e1869efe0c5cec51c5a
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/property/TransportPropertyManager.java
@@ -0,0 +1,36 @@
+package org.briarproject.api.property;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.TransportProperties;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+
+import java.util.Map;
+
+public interface TransportPropertyManager {
+
+	/** Returns the local transport properties for all transports. */
+	Map<TransportId, TransportProperties> getLocalProperties()
+			throws DbException;
+
+	/** Returns the local transport properties for the given transport. */
+	TransportProperties getLocalProperties(TransportId t) throws DbException;
+
+	/** Returns all remote transport properties for the given transport. */
+	Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
+			throws DbException;
+
+	/**
+	 * Merges the given properties with the existing local properties for the
+	 * given transport.
+	 */
+	void mergeLocalProperties(TransportId t, TransportProperties p)
+			throws DbException;
+
+	/**
+	 * Sets the remote transport properties for the given contact, replacing
+	 * any existing properties.
+	 */
+	void setRemoteProperties(ContactId c,
+			Map<TransportId, TransportProperties> p) throws DbException;
+}
diff --git a/briar-api/src/org/briarproject/api/sync/Ack.java b/briar-api/src/org/briarproject/api/sync/Ack.java
index 70417fc1617c39dfba5076426af1193c7e8cb28e..21486a4ae23cf75d132d7f06f1d03400bd1089da 100644
--- a/briar-api/src/org/briarproject/api/sync/Ack.java
+++ b/briar-api/src/org/briarproject/api/sync/Ack.java
@@ -2,7 +2,7 @@ package org.briarproject.api.sync;
 
 import java.util.Collection;
 
-/** A packet acknowledging receipt of one or more {@link Message}s. */
+/** A packet acknowledging receipt of one or more {@link Message Messages}. */
 public class Ack {
 
 	private final Collection<MessageId> acked;
diff --git a/briar-api/src/org/briarproject/api/sync/Offer.java b/briar-api/src/org/briarproject/api/sync/Offer.java
index 6611fda66b236c4312b8dc3e3c612e2062acdd41..dc107ed4ab00b3532f0e4edf60068b81ef8de7cb 100644
--- a/briar-api/src/org/briarproject/api/sync/Offer.java
+++ b/briar-api/src/org/briarproject/api/sync/Offer.java
@@ -2,7 +2,7 @@ package org.briarproject.api.sync;
 
 import java.util.Collection;
 
-/** A packet offering the recipient one or more {@link Message}s. */
+/** A packet offering the recipient one or more {@link Message Messages}. */
 public class Offer {
 
 	private final Collection<MessageId> offered;
diff --git a/briar-api/src/org/briarproject/api/sync/Request.java b/briar-api/src/org/briarproject/api/sync/Request.java
index 3b562802dffbaae1756d8bbfdf3f5d27298f48a0..af6d2321f8b1f7dcafe8c77dfc54197adc32209d 100644
--- a/briar-api/src/org/briarproject/api/sync/Request.java
+++ b/briar-api/src/org/briarproject/api/sync/Request.java
@@ -2,7 +2,9 @@ package org.briarproject.api.sync;
 
 import java.util.Collection;
 
-/** A packet requesting one or more {@link Message}s from the recipient. */
+/**
+ * A packet requesting one or more {@link Message Messages} from the recipient.
+ */
 public class Request {
 
 	private final Collection<org.briarproject.api.sync.MessageId> requested;
diff --git a/briar-api/src/org/briarproject/api/transport/KeyManager.java b/briar-api/src/org/briarproject/api/transport/KeyManager.java
index 4602584c17afd13a56c42535dee70b78d3ef92ba..e2f76f081b89d057eed77a873add5149e85afa41 100644
--- a/briar-api/src/org/briarproject/api/transport/KeyManager.java
+++ b/briar-api/src/org/briarproject/api/transport/KeyManager.java
@@ -2,7 +2,7 @@ package org.briarproject.api.transport;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DbException;
+import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.lifecycle.Service;
 
 import java.util.Collection;
@@ -14,11 +14,13 @@ import java.util.Collection;
 public interface KeyManager extends Service {
 
 	/**
-	 * Informs the key manager that a new contact has been added.
+	 * Informs the key manager that a new contact has been added. Derives and
+	 * stores transport keys for communicating with the contact.
 	 * {@link StreamContext StreamContexts} for the contact can be created
 	 * after this method has returned.
 	 */
-	void contactAdded(ContactId c, Collection<TransportKeys> keys);
+	void addContact(ContactId c, Collection<TransportId> transports,
+			SecretKey master, long timestamp, boolean alice);
 
 	/**
 	 * Returns a {@link StreamContext} for sending a stream to the given
@@ -29,8 +31,8 @@ public interface KeyManager extends Service {
 
 	/**
 	 * Looks up the given tag and returns a {@link StreamContext} for reading
-	 * from the corresponding stream if the tag was expected, or null if the
-	 * tag was unexpected.
+	 * from the corresponding stream, or null if an error occurs or the tag was
+	 * unexpected.
 	 */
-	StreamContext recogniseTag(TransportId t, byte[] tag) throws DbException;
+	StreamContext getStreamContext(TransportId t, byte[] tag);
 }
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index 963aa10ff734321d61a33092f3b0e2ec5218955a..84de4fc5472296e847e66cedee8d2e868c90f4f3 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -2,6 +2,7 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
@@ -9,14 +10,15 @@ import org.briarproject.api.data.Reader;
 import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.data.Writer;
 import org.briarproject.api.data.WriterFactory;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -39,20 +41,24 @@ class AliceConnector extends Connector {
 	private static final Logger LOG =
 			Logger.getLogger(AliceConnector.class.getName());
 
-	AliceConnector(CryptoComponent crypto, DatabaseComponent db,
+	AliceConnector(CryptoComponent crypto,
 			ReaderFactory readerFactory, WriterFactory writerFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			Clock clock, boolean reuseConnection, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor,
+			ContactManager contactManager, MessagingManager messagingManager,
+			TransportPropertyManager transportPropertyManager, Clock clock,
+			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
+			LocalAuthor localAuthor,
 			Map<TransportId, TransportProperties> localProps,
 			PseudoRandom random) {
-		super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
+		super(crypto, readerFactory, writerFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
-				keyManager, connectionManager, clock, reuseConnection, group,
-				plugin, localAuthor, localProps, random);
+				keyManager, connectionManager, contactManager,
+				messagingManager, transportPropertyManager, clock,
+				reuseConnection, group, plugin, localAuthor, localProps,
+				random);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java
index 826659fddf7cdfcefae541e66dafd924f18f99e7..f598e4a0034558ea79caaf5f8c7e3d2e4f67fdcb 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -2,6 +2,7 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.crypto.SecretKey;
@@ -9,14 +10,15 @@ import org.briarproject.api.data.Reader;
 import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.data.Writer;
 import org.briarproject.api.data.WriterFactory;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -39,20 +41,24 @@ class BobConnector extends Connector {
 	private static final Logger LOG =
 			Logger.getLogger(BobConnector.class.getName());
 
-	BobConnector(CryptoComponent crypto, DatabaseComponent db,
+	BobConnector(CryptoComponent crypto,
 			ReaderFactory readerFactory, WriterFactory writerFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			Clock clock, boolean reuseConnection, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor,
+			ContactManager contactManager, MessagingManager messagingManager,
+			TransportPropertyManager transportPropertyManager, Clock clock,
+			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
+			LocalAuthor localAuthor,
 			Map<TransportId, TransportProperties> localProps,
 			PseudoRandom random) {
-		super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
+		super(crypto, readerFactory, writerFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
-				keyManager, connectionManager, clock, reuseConnection, group,
-				plugin, localAuthor, localProps, random);
+				keyManager, connectionManager, contactManager,
+				messagingManager, transportPropertyManager, clock,
+				reuseConnection, group, plugin, localAuthor, localProps,
+				random);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java
index ed654fbd1e93f87ba28474ab9344eafd119f1835..1e22f0492605399947c79d12fa7e81a87b311a84 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -4,6 +4,7 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyPair;
 import org.briarproject.api.crypto.KeyParser;
@@ -15,28 +16,25 @@ import org.briarproject.api.data.Reader;
 import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.data.Writer;
 import org.briarproject.api.data.WriterFactory;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-import org.briarproject.api.sync.Group;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamReaderFactory;
 import org.briarproject.api.transport.StreamWriterFactory;
-import org.briarproject.api.transport.TransportKeys;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Logger;
@@ -50,7 +48,6 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENG
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
-import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 
 // FIXME: This class has way too many dependencies
 abstract class Connector extends Thread {
@@ -59,7 +56,6 @@ abstract class Connector extends Thread {
 			Logger.getLogger(Connector.class.getName());
 
 	protected final CryptoComponent crypto;
-	protected final DatabaseComponent db;
 	protected final ReaderFactory readerFactory;
 	protected final WriterFactory writerFactory;
 	protected final StreamReaderFactory streamReaderFactory;
@@ -68,6 +64,9 @@ abstract class Connector extends Thread {
 	protected final GroupFactory groupFactory;
 	protected final KeyManager keyManager;
 	protected final ConnectionManager connectionManager;
+	protected final ContactManager contactManager;
+	protected final MessagingManager messagingManager;
+	protected final TransportPropertyManager transportPropertyManager;
 	protected final Clock clock;
 	protected final boolean reuseConnection;
 	protected final ConnectorGroup group;
@@ -83,19 +82,20 @@ abstract class Connector extends Thread {
 
 	private volatile ContactId contactId = null;
 
-	Connector(CryptoComponent crypto, DatabaseComponent db,
+	Connector(CryptoComponent crypto,
 			ReaderFactory readerFactory, WriterFactory writerFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			Clock clock, boolean reuseConnection, ConnectorGroup group,
-			DuplexPlugin plugin, LocalAuthor localAuthor,
+			ContactManager contactManager, MessagingManager messagingManager,
+			TransportPropertyManager transportPropertyManager, Clock clock,
+			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
+			LocalAuthor localAuthor,
 			Map<TransportId, TransportProperties> localProps,
 			PseudoRandom random) {
 		super("Connector");
 		this.crypto = crypto;
-		this.db = db;
 		this.readerFactory = readerFactory;
 		this.writerFactory = writerFactory;
 		this.streamReaderFactory = streamReaderFactory;
@@ -104,6 +104,9 @@ abstract class Connector extends Thread {
 		this.groupFactory = groupFactory;
 		this.keyManager = keyManager;
 		this.connectionManager = connectionManager;
+		this.contactManager = contactManager;
+		this.messagingManager = messagingManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.reuseConnection = reuseConnection;
 		this.group = group;
@@ -274,31 +277,15 @@ abstract class Connector extends Thread {
 			Map<TransportId, TransportProperties> remoteProps, SecretKey master,
 			long timestamp, boolean alice) throws DbException {
 		// Add the contact to the database
-		contactId = db.addContact(remoteAuthor, localAuthor.getId());
-		// Create and store the inbox group
-		byte[] salt = crypto.deriveGroupSalt(master);
-		Group inbox = groupFactory.createGroup("Inbox", salt);
-		db.addGroup(inbox);
-		db.setInboxGroup(contactId, inbox);
+		contactId = contactManager.addContact(remoteAuthor,
+				localAuthor.getId());
+		// Create a private messaging conversation
+		messagingManager.addContact(contactId, master);
 		// Store the remote transport properties
-		db.setRemoteProperties(contactId, remoteProps);
+		transportPropertyManager.setRemoteProperties(contactId, remoteProps);
 		// Derive transport keys for each transport shared with the contact
-		Map<TransportId, Integer> latencies = db.getTransportLatencies();
-		List<TransportKeys> keys = new ArrayList<TransportKeys>();
-		for (TransportId t : localProps.keySet()) {
-			if (remoteProps.containsKey(t) && latencies.containsKey(t)) {
-				// Work out what rotation period the timestamp belongs to
-				long latency = latencies.get(t);
-				long rotationPeriodLength = latency + MAX_CLOCK_DIFFERENCE;
-				long rotationPeriod = timestamp / rotationPeriodLength;
-				// Derive the transport keys
-				TransportKeys k = crypto.deriveTransportKeys(t, master,
-						rotationPeriod, alice);
-				db.addTransportKeys(contactId, k);
-				keys.add(k);
-			}
-		}
-		keyManager.contactAdded(contactId, keys);
+		keyManager.addContact(contactId, remoteProps.keySet(), master,
+				timestamp, alice);
 	}
 
 	protected void tryToClose(DuplexTransportConnection conn,
diff --git a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
index 564b86fa82668238a6350392b3ab4b2c6ff8038d..f4ed123527e3b3f479a0235f751d4780ed1ded83 100644
--- a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
+++ b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
@@ -2,22 +2,25 @@ package org.briarproject.invitation;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.PseudoRandom;
 import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.data.WriterFactory;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.invitation.InvitationListener;
 import org.briarproject.api.invitation.InvitationState;
 import org.briarproject.api.invitation.InvitationTask;
+import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.PluginManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -45,7 +48,6 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Logger.getLogger(ConnectorGroup.class.getName());
 
 	private final CryptoComponent crypto;
-	private final DatabaseComponent db;
 	private final ReaderFactory readerFactory;
 	private final WriterFactory writerFactory;
 	private final StreamReaderFactory streamReaderFactory;
@@ -54,6 +56,10 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private final GroupFactory groupFactory;
 	private final KeyManager keyManager;
 	private final ConnectionManager connectionManager;
+	private final IdentityManager identityManager;
+	private final ContactManager contactManager;
+	private final MessagingManager messagingManager;
+	private final TransportPropertyManager transportPropertyManager;
 	private final Clock clock;
 	private final PluginManager pluginManager;
 	private final AuthorId localAuthorId;
@@ -71,18 +77,20 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	private boolean localMatched = false, remoteMatched = false;
 	private String remoteName = null;
 
-	ConnectorGroup(CryptoComponent crypto, DatabaseComponent db,
+	ConnectorGroup(CryptoComponent crypto,
 			ReaderFactory readerFactory, WriterFactory writerFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
+			IdentityManager identityManager, ContactManager contactManager,
+			MessagingManager messagingManager,
+			TransportPropertyManager transportPropertyManager, Clock clock,
+			PluginManager pluginManager, AuthorId localAuthorId,
 			int localInvitationCode, int remoteInvitationCode,
 			boolean reuseConnection) {
 		super("ConnectorGroup");
 		this.crypto = crypto;
-		this.db = db;
 		this.readerFactory = readerFactory;
 		this.writerFactory = writerFactory;
 		this.streamReaderFactory = streamReaderFactory;
@@ -91,6 +99,10 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		this.groupFactory = groupFactory;
 		this.keyManager = keyManager;
 		this.connectionManager = connectionManager;
+		this.identityManager = identityManager;
+		this.contactManager = contactManager;
+		this.messagingManager = messagingManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
 		this.localAuthorId = localAuthorId;
@@ -130,8 +142,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		Map<TransportId, TransportProperties> localProps;
 		// Load the local pseudonym and transport properties
 		try {
-			localAuthor = db.getLocalAuthor(localAuthorId);
-			localProps = db.getLocalProperties();
+			localAuthor = identityManager.getLocalAuthor(localAuthorId);
+			localProps = transportPropertyManager.getLocalProperties();
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			lock.lock();
@@ -185,9 +197,10 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Map<TransportId, TransportProperties> localProps) {
 		PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
 				remoteInvitationCode);
-		return new AliceConnector(crypto, db, readerFactory, writerFactory,
+		return new AliceConnector(crypto, readerFactory, writerFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
-				groupFactory, keyManager, connectionManager, clock,
+				groupFactory, keyManager, connectionManager, contactManager,
+				messagingManager, transportPropertyManager, clock,
 				reuseConnection, this, plugin, localAuthor, localProps, random);
 	}
 
@@ -196,9 +209,10 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			Map<TransportId, TransportProperties> localProps) {
 		PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
 				localInvitationCode);
-		return new BobConnector(crypto, db, readerFactory, writerFactory,
+		return new BobConnector(crypto, readerFactory, writerFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
-				groupFactory, keyManager, connectionManager, clock,
+				groupFactory, keyManager, connectionManager, contactManager,
+				messagingManager, transportPropertyManager, clock,
 				reuseConnection, this, plugin, localAuthor, localProps, random);
 	}
 
diff --git a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
index 25cb8b8069f69afdbf519be83d1c541de4ebf88a..899b8c558dcdd3004902abfb435bd4cc8a81f53a 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
@@ -1,15 +1,18 @@
 package org.briarproject.invitation;
 
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.data.ReaderFactory;
 import org.briarproject.api.data.WriterFactory;
-import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.invitation.InvitationTask;
 import org.briarproject.api.invitation.InvitationTaskFactory;
+import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.transport.KeyManager;
@@ -21,7 +24,6 @@ import javax.inject.Inject;
 class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 
 	private final CryptoComponent crypto;
-	private final DatabaseComponent db;
 	private final ReaderFactory readerFactory;
 	private final WriterFactory writerFactory;
 	private final StreamReaderFactory streamReaderFactory;
@@ -30,19 +32,25 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 	private final GroupFactory groupFactory;
 	private final KeyManager keyManager;
 	private final ConnectionManager connectionManager;
+	private final IdentityManager identityManager;
+	private final ContactManager contactManager;
+	private final MessagingManager messagingManager;
+	private final TransportPropertyManager transportPropertyManager;
 	private final Clock clock;
 	private final PluginManager pluginManager;
 
 	@Inject
-	InvitationTaskFactoryImpl(CryptoComponent crypto, DatabaseComponent db,
+	InvitationTaskFactoryImpl(CryptoComponent crypto,
 			ReaderFactory readerFactory, WriterFactory writerFactory,
 			StreamReaderFactory streamReaderFactory,
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
+			IdentityManager identityManager, ContactManager contactManager,
+			MessagingManager messagingManager,
+			TransportPropertyManager transportPropertyManager,
 			Clock clock, PluginManager pluginManager) {
 		this.crypto = crypto;
-		this.db = db;
 		this.readerFactory = readerFactory;
 		this.writerFactory = writerFactory;
 		this.streamReaderFactory = streamReaderFactory;
@@ -51,16 +59,21 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 		this.groupFactory = groupFactory;
 		this.keyManager = keyManager;
 		this.connectionManager = connectionManager;
+		this.identityManager = identityManager;
+		this.contactManager = contactManager;
+		this.messagingManager = messagingManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
 	}
 
 	public InvitationTask createTask(AuthorId localAuthorId, int localCode,
 			int remoteCode, boolean reuseConnection) {
-		return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
+		return new ConnectorGroup(crypto, readerFactory, writerFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
-				groupFactory, keyManager, connectionManager, clock,
-				pluginManager, localAuthorId, localCode, remoteCode,
+				groupFactory, keyManager, connectionManager, identityManager,
+				contactManager, messagingManager, transportPropertyManager,
+				clock, pluginManager, localAuthorId, localCode, remoteCode,
 				reuseConnection);
 	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index eca57c25dc514d3729866ebb10970aebbf374b46..712254c206f6039a01196c2af71bab0bc58153b0 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -3,11 +3,15 @@ package org.briarproject.messaging;
 import com.google.inject.Inject;
 
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateConversation;
 import org.briarproject.api.messaging.PrivateMessageHeader;
+import org.briarproject.api.sync.Group;
+import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageHeader;
@@ -22,10 +26,23 @@ import java.util.List;
 class MessagingManagerImpl implements MessagingManager {
 
 	private final DatabaseComponent db;
+	private final CryptoComponent crypto;
+	private final GroupFactory groupFactory;
 
 	@Inject
-	MessagingManagerImpl(DatabaseComponent db) {
+	MessagingManagerImpl(DatabaseComponent db, CryptoComponent crypto,
+			GroupFactory groupFactory) {
 		this.db = db;
+		this.crypto = crypto;
+		this.groupFactory = groupFactory;
+	}
+
+	@Override
+	public void addContact(ContactId c, SecretKey master) throws DbException {
+		byte[] salt = crypto.deriveGroupSalt(master);
+		Group inbox = groupFactory.createGroup("Inbox", salt);
+		db.addGroup(inbox);
+		db.setInboxGroup(c, inbox);
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
index f3a31c83bc7cd6d1af19a6493710d9836a68bae7..76bf5e8c48552f8ed803f081135df2862334b7d5 100644
--- a/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/ConnectionManagerImpl.java
@@ -2,7 +2,6 @@ package org.briarproject.plugins;
 
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.db.DbException;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.ConnectionRegistry;
@@ -130,15 +129,11 @@ class ConnectionManagerImpl implements ConnectionManager {
 			StreamContext ctx;
 			try {
 				byte[] tag = readTag(transportId, reader);
-				ctx = keyManager.recogniseTag(transportId, tag);
+				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, false);
 				return;
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				disposeReader(true, false);
-				return;
 			}
 			if (ctx == null) {
 				LOG.info("Unrecognised tag");
@@ -234,15 +229,11 @@ class ConnectionManagerImpl implements ConnectionManager {
 			StreamContext ctx;
 			try {
 				byte[] tag = readTag(transportId, reader);
-				ctx = keyManager.recogniseTag(transportId, tag);
+				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, false);
 				return;
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				disposeReader(true, false);
-				return;
 			}
 			if (ctx == null) {
 				LOG.info("Unrecognised tag");
@@ -363,15 +354,11 @@ class ConnectionManagerImpl implements ConnectionManager {
 			StreamContext ctx;
 			try {
 				byte[] tag = readTag(transportId, reader);
-				ctx = keyManager.recogniseTag(transportId, tag);
+				ctx = keyManager.getStreamContext(transportId, tag);
 			} catch (IOException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				disposeReader(true, true);
 				return;
-			} catch (DbException e) {
-				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-				disposeReader(true, true);
-				return;
 			}
 			// Unrecognised tags are suspicious in this case
 			if (ctx == null) {
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index e96254b04a4c127b24aa7840a6f54450004c4a3f..a780b887dc3a076baf76ad97bf2f797f4d2b292d 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -25,6 +25,7 @@ import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
 
@@ -58,6 +59,7 @@ class PluginManagerImpl implements PluginManager {
 	private final DatabaseComponent db;
 	private final Poller poller;
 	private final ConnectionManager connectionManager;
+	private final TransportPropertyManager transportPropertyManager;
 	private final UiCallback uiCallback;
 	private final Map<TransportId, Plugin> plugins;
 	private final List<SimplexPlugin> simplexPlugins;
@@ -68,7 +70,9 @@ class PluginManagerImpl implements PluginManager {
 			SimplexPluginConfig simplexPluginConfig,
 			DuplexPluginConfig duplexPluginConfig, Clock clock,
 			DatabaseComponent db, Poller poller,
-			ConnectionManager connectionManager, UiCallback uiCallback) {
+			ConnectionManager connectionManager,
+			TransportPropertyManager transportPropertyManager,
+			UiCallback uiCallback) {
 		this.ioExecutor = ioExecutor;
 		this.eventBus = eventBus;
 		this.simplexPluginConfig = simplexPluginConfig;
@@ -77,6 +81,7 @@ class PluginManagerImpl implements PluginManager {
 		this.db = db;
 		this.poller = poller;
 		this.connectionManager = connectionManager;
+		this.transportPropertyManager = transportPropertyManager;
 		this.uiCallback = uiCallback;
 		plugins = new ConcurrentHashMap<TransportId, Plugin>();
 		simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
@@ -320,7 +325,8 @@ class PluginManagerImpl implements PluginManager {
 
 		public TransportProperties getLocalProperties() {
 			try {
-				TransportProperties p = db.getLocalProperties(id);
+				TransportProperties p =
+						transportPropertyManager.getLocalProperties(id);
 				return p == null ? new TransportProperties() : p;
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -330,7 +336,7 @@ class PluginManagerImpl implements PluginManager {
 
 		public Map<ContactId, TransportProperties> getRemoteProperties() {
 			try {
-				return db.getRemoteProperties(id);
+				return transportPropertyManager.getRemoteProperties(id);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 				return Collections.emptyMap();
@@ -347,7 +353,7 @@ class PluginManagerImpl implements PluginManager {
 
 		public void mergeLocalProperties(TransportProperties p) {
 			try {
-				db.mergeLocalProperties(id, p);
+				transportPropertyManager.mergeLocalProperties(id, p);
 			} catch (DbException e) {
 				if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 			}
diff --git a/briar-core/src/org/briarproject/property/PropertyModule.java b/briar-core/src/org/briarproject/property/PropertyModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..23c3499538aaee749c7050b21fb1ddf41a7fb5a2
--- /dev/null
+++ b/briar-core/src/org/briarproject/property/PropertyModule.java
@@ -0,0 +1,14 @@
+package org.briarproject.property;
+
+import com.google.inject.AbstractModule;
+
+import org.briarproject.api.property.TransportPropertyManager;
+
+public class PropertyModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+		bind(TransportPropertyManager.class).to(
+				TransportPropertyManagerImpl.class);
+	}
+}
diff --git a/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..355ee55f18e8f689e5b67c7f5a95383cb8793354
--- /dev/null
+++ b/briar-core/src/org/briarproject/property/TransportPropertyManagerImpl.java
@@ -0,0 +1,53 @@
+package org.briarproject.property;
+
+import com.google.inject.Inject;
+
+import org.briarproject.api.TransportId;
+import org.briarproject.api.TransportProperties;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.property.TransportPropertyManager;
+
+import java.util.Map;
+
+// Temporary facade during sync protocol refactoring
+class TransportPropertyManagerImpl implements TransportPropertyManager {
+
+	private final DatabaseComponent db;
+
+	@Inject
+	TransportPropertyManagerImpl(DatabaseComponent db) {
+		this.db = db;
+	}
+
+	@Override
+	public Map<TransportId, TransportProperties> getLocalProperties()
+			throws DbException {
+		return db.getLocalProperties();
+	}
+
+	@Override
+	public TransportProperties getLocalProperties(TransportId t)
+			throws DbException {
+		return db.getLocalProperties(t);
+	}
+
+	@Override
+	public Map<ContactId, TransportProperties> getRemoteProperties(
+			TransportId t) throws DbException {
+		return db.getRemoteProperties(t);
+	}
+
+	@Override
+	public void mergeLocalProperties(TransportId t, TransportProperties p)
+			throws DbException {
+		db.mergeLocalProperties(t, p);
+	}
+
+	@Override
+	public void setRemoteProperties(ContactId c,
+			Map<TransportId, TransportProperties> p) throws DbException {
+		db.setRemoteProperties(c, p);
+	}
+}
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index 7c7123637ab254f49620f7150ab753ae0d6a3fb5..c36e844d2efddcc5cb25fd42c7e3afced68321a5 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -3,6 +3,7 @@ package org.briarproject.transport;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
@@ -16,7 +17,6 @@ import org.briarproject.api.system.Clock;
 import org.briarproject.api.system.Timer;
 import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamContext;
-import org.briarproject.api.transport.TransportKeys;
 
 import java.util.Collection;
 import java.util.Map;
@@ -73,10 +73,11 @@ class KeyManagerImpl implements KeyManager, EventListener {
 		return true;
 	}
 
-	public void contactAdded(ContactId c, Collection<TransportKeys> keys) {
-		for (TransportKeys k : keys) {
-			TransportKeyManager m = managers.get(k.getTransportId());
-			if (m != null) m.addContact(c, k);
+	public void addContact(ContactId c, Collection<TransportId> transports,
+			SecretKey master, long timestamp, boolean alice) {
+		for (TransportId t : transports) {
+			TransportKeyManager m = managers.get(t);
+			if (m != null) m.addContact(c, master, timestamp, alice);
 		}
 	}
 
@@ -85,8 +86,7 @@ class KeyManagerImpl implements KeyManager, EventListener {
 		return m == null ? null : m.getStreamContext(c);
 	}
 
-	public StreamContext recogniseTag(TransportId t, byte[] tag)
-			throws DbException {
+	public StreamContext getStreamContext(TransportId t, byte[] tag) {
 		TransportKeyManager m = managers.get(t);
 		return m == null ? null : m.recogniseTag(tag);
 	}
diff --git a/briar-core/src/org/briarproject/transport/TransportKeyManager.java b/briar-core/src/org/briarproject/transport/TransportKeyManager.java
index 7b6b5011c078651a283c332e9f55b341016a9c76..7fd3c42bfcb1a77a27d7117a04c4a409d0e13917 100644
--- a/briar-core/src/org/briarproject/transport/TransportKeyManager.java
+++ b/briar-core/src/org/briarproject/transport/TransportKeyManager.java
@@ -4,6 +4,7 @@ import org.briarproject.api.Bytes;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.system.Clock;
@@ -98,9 +99,9 @@ class TransportKeyManager extends TimerTask {
 		} finally {
 			lock.unlock();
 		}
-		// Schedule a periodic task to rotate the keys
+		// Schedule the next key rotation
 		long delay = rotationPeriodLength - now % rotationPeriodLength;
-		timer.scheduleAtFixedRate(this, delay, rotationPeriodLength);
+		timer.schedule(this, delay);
 	}
 
 	// Locking: lock
@@ -136,16 +137,40 @@ class TransportKeyManager extends TimerTask {
 		});
 	}
 
-	void addContact(ContactId c, TransportKeys k) {
+	void addContact(ContactId c, SecretKey master, long timestamp,
+			boolean alice) {
+		// Work out what rotation period the timestamp belongs to
+		long rotationPeriod = timestamp / rotationPeriodLength;
+		// Derive the transport keys
+		TransportKeys k = crypto.deriveTransportKeys(transportId, master,
+				rotationPeriod, alice);
+		// Rotate the keys to the current rotation period if necessary
+		rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
+		k = crypto.rotateTransportKeys(k, rotationPeriod);
 		lock.lock();
 		try {
 			// Initialise mutable state for the contact
 			addKeys(c, new MutableTransportKeys(k));
+			// Write the keys back to the DB
+			saveTransportKeys(c, k);
 		} finally {
 			lock.unlock();
 		}
 	}
 
+	private void saveTransportKeys(final ContactId c, final TransportKeys k) {
+		dbExecutor.execute(new Runnable() {
+			public void run() {
+				try {
+					db.addTransportKeys(c, k);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
 	void removeContact(ContactId c) {
 		lock.lock();
 		try {
@@ -308,6 +333,10 @@ class TransportKeyManager extends TimerTask {
 		} finally {
 			lock.unlock();
 		}
+		// Schedule the next key rotation
+		long now = clock.currentTimeMillis();
+		long delay = rotationPeriodLength - now % rotationPeriodLength;
+		timer.schedule(this, delay);
 	}
 
 	private static class TagContext {
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 1394a998b4a3f4f85d3993b3ca4d6e034d93ff2c..66a174dd61d63ab6a002fd309d0765c3382dab57 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -4,7 +4,6 @@ import org.briarproject.BriarTestCase;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -14,6 +13,7 @@ import org.briarproject.api.plugins.simplex.SimplexPlugin;
 import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
 import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
+import org.briarproject.api.property.TransportPropertyManager;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.ui.UiCallback;
 import org.briarproject.system.SystemClock;
@@ -41,8 +41,10 @@ public class PluginManagerImplTest extends BriarTestCase {
 				context.mock(DuplexPluginConfig.class);
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
 		final Poller poller = context.mock(Poller.class);
-		final ConnectionManager dispatcher =
+		final ConnectionManager connectionManager =
 				context.mock(ConnectionManager.class);
+		final TransportPropertyManager transportPropertyManager =
+				context.mock(TransportPropertyManager.class);
 		final UiCallback uiCallback = context.mock(UiCallback.class);
 		// Two simplex plugin factories: both create plugins, one fails to start
 		final SimplexPluginFactory simplexFactory =
@@ -126,7 +128,7 @@ public class PluginManagerImplTest extends BriarTestCase {
 		}});
 		PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
 				simplexPluginConfig, duplexPluginConfig, clock, db, poller,
-				dispatcher, uiCallback);
+				connectionManager, transportPropertyManager, uiCallback);
 
 		// Two plugins should be started and stopped
 		assertTrue(p.start());
diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
index 7cb2ed9b351a2f0be7db32c5cddc6457cb7ecf0c..88ae60a383c9c83b4ccfce212c127b58d4269444 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
@@ -10,7 +10,7 @@ import org.briarproject.TestSystemModule;
 import org.briarproject.TestUtils;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
-import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.Event;
@@ -19,11 +19,13 @@ import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
-import org.briarproject.api.sync.Group;
-import org.briarproject.api.sync.GroupFactory;
+import org.briarproject.api.messaging.MessagingManager;
+import org.briarproject.api.messaging.PrivateConversation;
+import org.briarproject.api.messaging.PrivateMessageFactory;
+import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
-import org.briarproject.api.sync.MessageFactory;
 import org.briarproject.api.sync.MessageVerifier;
 import org.briarproject.api.sync.MessagingSession;
 import org.briarproject.api.sync.PacketReader;
@@ -34,11 +36,13 @@ import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamContext;
 import org.briarproject.api.transport.StreamReaderFactory;
 import org.briarproject.api.transport.StreamWriterFactory;
-import org.briarproject.api.transport.TransportKeys;
+import org.briarproject.contact.ContactModule;
 import org.briarproject.crypto.CryptoModule;
 import org.briarproject.data.DataModule;
 import org.briarproject.db.DatabaseModule;
 import org.briarproject.event.EventModule;
+import org.briarproject.identity.IdentityModule;
+import org.briarproject.messaging.MessagingModule;
 import org.briarproject.plugins.ImmediateExecutor;
 import org.briarproject.transport.TransportModule;
 import org.junit.After;
@@ -53,8 +57,6 @@ import java.io.OutputStream;
 import java.util.Collections;
 
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.api.sync.MessagingConstants.GROUP_SALT_LENGTH;
-import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -64,8 +66,6 @@ import static org.junit.Assert.assertTrue;
 public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
-	private static final long ROTATION_PERIOD_LENGTH =
-			MAX_LATENCY + MAX_CLOCK_DIFFERENCE;
 
 	private final File testDir = TestUtils.getTestDirectory();
 	private final File aliceDir = new File(testDir, "alice");
@@ -86,9 +86,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	private Injector createInjector(File dir) {
 		return Guice.createInjector(new TestDatabaseModule(dir),
 				new TestLifecycleModule(), new TestSystemModule(),
-				new CryptoModule(), new DatabaseModule(), new EventModule(),
-				new SyncModule(), new DataModule(),
-				new TransportModule());
+				new ContactModule(), new CryptoModule(), new DatabaseModule(),
+				new DataModule(), new EventModule(), new IdentityModule(),
+				new SyncModule(), new MessagingModule(), new TransportModule());
 	}
 
 	@Test
@@ -100,40 +100,41 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Open Alice's database
 		DatabaseComponent db = alice.getInstance(DatabaseComponent.class);
 		assertFalse(db.open());
+		// Add the transport
+		db.addTransport(transportId, MAX_LATENCY);
 		// Start Alice's key manager
 		KeyManager keyManager = alice.getInstance(KeyManager.class);
 		keyManager.start();
-		// Add a local pseudonym for Alice
+		// Add an identity for Alice
 		AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
 		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
 				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
-		db.addLocalAuthor(aliceAuthor);
+		IdentityManager identityManager =
+				alice.getInstance(IdentityManager.class);
+		identityManager.addLocalAuthor(aliceAuthor);
 		// Add Bob as a contact
 		AuthorId bobId = new AuthorId(TestUtils.getRandomId());
 		Author bobAuthor = new Author(bobId, "Bob",
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		ContactId contactId = db.addContact(bobAuthor, aliceId);
-		// Add the inbox group
-		GroupFactory gf = alice.getInstance(GroupFactory.class);
-		Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
-		db.addGroup(group);
-		db.setInboxGroup(contactId, group);
-		// Add the transport
-		db.addTransport(transportId, MAX_LATENCY);
+		ContactManager contactManager = alice.getInstance(ContactManager.class);
+		ContactId contactId = contactManager.addContact(bobAuthor, aliceId);
+		// Create the private conversation
+		MessagingManager messagingManager =
+				alice.getInstance(MessagingManager.class);
+		messagingManager.addContact(contactId, master);
 		// Derive and store the transport keys
-		long rotationPeriod = timestamp / ROTATION_PERIOD_LENGTH;
-		CryptoComponent crypto = alice.getInstance(CryptoComponent.class);
-		TransportKeys keys = crypto.deriveTransportKeys(transportId, master,
-				rotationPeriod, true);
-		db.addTransportKeys(contactId, keys);
-		keyManager.contactAdded(contactId, Collections.singletonList(keys));
+		keyManager.addContact(contactId, Collections.singletonList(transportId),
+				master, timestamp, true);
 		// Send Bob a message
-		String contentType = "text/plain";
 		byte[] body = "Hi Bob!".getBytes("UTF-8");
-		MessageFactory messageFactory = alice.getInstance(MessageFactory.class);
-		Message message = messageFactory.createAnonymousMessage(null, group,
-				contentType, timestamp, body);
-		db.addLocalMessage(message);
+		PrivateMessageFactory messageFactory =
+				alice.getInstance(PrivateMessageFactory.class);
+		GroupId groupId = messagingManager.getConversationId(contactId);
+		PrivateConversation conversation =
+				messagingManager.getConversation(groupId);
+		Message message = messageFactory.createPrivateMessage(null,
+				conversation, "text/plain", timestamp, body);
+		messagingManager.addLocalMessage(message);
 		// Get a stream context
 		StreamContext ctx = keyManager.getStreamContext(contactId, transportId);
 		assertNotNull(ctx);
@@ -143,13 +144,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				alice.getInstance(StreamWriterFactory.class);
 		OutputStream streamWriter =
 				streamWriterFactory.createStreamWriter(out, ctx);
-		// Create an outgoing messaging session
+		// Create an outgoing sync session
 		EventBus eventBus = alice.getInstance(EventBus.class);
 		PacketWriterFactory packetWriterFactory =
 				alice.getInstance(PacketWriterFactory.class);
 		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
 				streamWriter);
-		MessagingSession session = new org.briarproject.sync.SimplexOutgoingSession(db,
+		MessagingSession session = new SimplexOutgoingSession(db,
 				new ImmediateExecutor(), eventBus, contactId, transportId,
 				MAX_LATENCY, packetWriter);
 		// Write whatever needs to be written
@@ -166,33 +167,31 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Open Bob's database
 		DatabaseComponent db = bob.getInstance(DatabaseComponent.class);
 		assertFalse(db.open());
+		// Add the transport
+		db.addTransport(transportId, MAX_LATENCY);
 		// Start Bob's key manager
 		KeyManager keyManager = bob.getInstance(KeyManager.class);
 		keyManager.start();
-		// Add a local pseudonym for Bob
+		// Add an identity for Bob
 		AuthorId bobId = new AuthorId(TestUtils.getRandomId());
 		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
 				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
-		db.addLocalAuthor(bobAuthor);
+		IdentityManager identityManager =
+				bob.getInstance(IdentityManager.class);
+		identityManager.addLocalAuthor(bobAuthor);
 		// Add Alice as a contact
 		AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
 		Author aliceAuthor = new Author(aliceId, "Alice",
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		ContactId contactId = db.addContact(aliceAuthor, bobId);
-		// Add the inbox group
-		GroupFactory gf = bob.getInstance(GroupFactory.class);
-		Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
-		db.addGroup(group);
-		db.setInboxGroup(contactId, group);
-		// Add the transport
-		db.addTransport(transportId, MAX_LATENCY);
+		ContactManager contactManager = bob.getInstance(ContactManager.class);
+		ContactId contactId = contactManager.addContact(aliceAuthor, bobId);
+		// Create the private conversation
+		MessagingManager messagingManager =
+				bob.getInstance(MessagingManager.class);
+		messagingManager.addContact(contactId, master);
 		// Derive and store the transport keys
-		long rotationPeriod = timestamp / ROTATION_PERIOD_LENGTH;
-		CryptoComponent crypto = bob.getInstance(CryptoComponent.class);
-		TransportKeys keys = crypto.deriveTransportKeys(transportId, master,
-				rotationPeriod, false);
-		db.addTransportKeys(contactId, keys);
-		keyManager.contactAdded(contactId, Collections.singletonList(keys));
+		keyManager.addContact(contactId, Collections.singletonList(transportId),
+				master, timestamp, false);
 		// Set up an event listener
 		MessageListener listener = new MessageListener();
 		bob.getInstance(EventBus.class).addListener(listener);
@@ -201,14 +200,14 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		byte[] tag = new byte[TAG_LENGTH];
 		int read = in.read(tag);
 		assertEquals(tag.length, read);
-		StreamContext ctx = keyManager.recogniseTag(transportId, tag);
+		StreamContext ctx = keyManager.getStreamContext(transportId, tag);
 		assertNotNull(ctx);
 		// Create a stream reader
 		StreamReaderFactory streamReaderFactory =
 				bob.getInstance(StreamReaderFactory.class);
 		InputStream streamReader =
 				streamReaderFactory.createStreamReader(in, ctx);
-		// Create an incoming messaging session
+		// Create an incoming sync session
 		EventBus eventBus = bob.getInstance(EventBus.class);
 		MessageVerifier messageVerifier =
 				bob.getInstance(MessageVerifier.class);
@@ -216,7 +215,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				bob.getInstance(PacketReaderFactory.class);
 		PacketReader packetReader = packetReaderFactory.createPacketReader(
 				streamReader);
-		MessagingSession session = new org.briarproject.sync.IncomingSession(db,
+		MessagingSession session = new IncomingSession(db,
 				new ImmediateExecutor(), new ImmediateExecutor(), eventBus,
 				messageVerifier, contactId, transportId, packetReader);
 		// No messages should have been added yet