diff --git a/briar-android/src/org/briarproject/android/AndroidModule.java b/briar-android/src/org/briarproject/android/AndroidModule.java
index d3a7d8a4d53d10897cf3b2b61c64c948e71f6318..9dc8fcd884407be47ab952554fb917fbd062251a 100644
--- a/briar-android/src/org/briarproject/android/AndroidModule.java
+++ b/briar-android/src/org/briarproject/android/AndroidModule.java
@@ -93,10 +93,8 @@ public class AndroidModule {
 	AndroidNotificationManager provideAndroidNotificationManager(
 			LifecycleManager lifecycleManager, EventBus eventBus,
 			AndroidNotificationManagerImpl notificationManager) {
-		lifecycleManager.register(notificationManager);
+		lifecycleManager.registerService(notificationManager);
 		eventBus.addListener(notificationManager);
-
 		return notificationManager;
 	}
-
 }
diff --git a/briar-api/src/org/briarproject/api/clients/Client.java b/briar-api/src/org/briarproject/api/clients/Client.java
new file mode 100644
index 0000000000000000000000000000000000000000..28954c238a7768e0c2b8109d1c5f5fa0f5036703
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/clients/Client.java
@@ -0,0 +1,12 @@
+package org.briarproject.api.clients;
+
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
+
+public interface Client {
+
+	/**
+	 * Called at startup to create any local state needed by the client.
+	 */
+	void createLocalState(Transaction txn) throws DbException;
+}
diff --git a/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java b/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java
index 3a87775724028aade1cf68ed3ffa732099c042ec..01e26801721428e8b75c7f9d53512590bbbf9f56 100644
--- a/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java
+++ b/briar-api/src/org/briarproject/api/clients/PrivateGroupFactory.java
@@ -6,6 +6,9 @@ import org.briarproject.api.sync.Group;
 
 public interface PrivateGroupFactory {
 
+	/** Creates a group that is not shared with any contacts. */
+	Group createLocalGroup(ClientId clientId);
+
 	/** Creates a group for the given client to share with the given contact. */
 	Group createPrivateGroup(ClientId clientId, Contact contact);
 }
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index a360fa98b61bb104e754a7bd42c6463a8e0918e0..2a627fb8a222329fc173529653c151aad6616927 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -44,6 +44,7 @@ public interface DatabaseComponent {
 	 * <p/>
 	 * This method acquires locks, so it must not be called while holding a
 	 * lock.
+	 *
 	 * @param readOnly true if the transaction will only be used for reading.
 	 */
 	Transaction startTransaction(boolean readOnly) throws DbException;
@@ -90,13 +91,27 @@ public interface DatabaseComponent {
 	void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
 			throws DbException;
 
+	/**
+	 * Returns true if the database contains the given contact for the given
+	 * local pseudonym.
+	 */
+	boolean containsContact(Transaction txn, AuthorId remote, AuthorId local)
+			throws DbException;
+
+	/**
+	 * Returns true if the database contains the given group.
+	 */
+	boolean containsGroup(Transaction txn, GroupId g) throws DbException;
+
 	/**
 	 * Deletes the message with the given ID. The message ID and any other
 	 * associated data are not deleted.
 	 */
 	void deleteMessage(Transaction txn, MessageId m) throws DbException;
 
-	/** Deletes any metadata associated with the given message. */
+	/**
+	 * Deletes any metadata associated with the given message.
+	 */
 	void deleteMessageMetadata(Transaction txn, MessageId m) throws DbException;
 
 	/**
@@ -162,13 +177,6 @@ public interface DatabaseComponent {
 	Collection<ContactId> getContacts(Transaction txn, AuthorId a)
 			throws DbException;
 
-	/**
-	 * Returns true if the database contains the given contact for the given
-	 * local pseudonym.
-	 */
-	boolean containsContact(Transaction txn, AuthorId remote, AuthorId local)
-			throws DbException;
-
 	/**
 	 * Returns the unique ID for this device.
 	 * <p/>
@@ -359,7 +367,7 @@ public interface DatabaseComponent {
 	 * Marks the given contact as active or inactive.
 	 */
 	void setContactActive(Transaction txn, ContactId c, boolean active)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Marks the given message as shared or unshared.
diff --git a/briar-api/src/org/briarproject/api/lifecycle/LifecycleManager.java b/briar-api/src/org/briarproject/api/lifecycle/LifecycleManager.java
index 5f75237c25e080ed40cdfc0614cdb4107d345fe9..536c9c30937266cc17409218217b0ff4ed3bbff9 100644
--- a/briar-api/src/org/briarproject/api/lifecycle/LifecycleManager.java
+++ b/briar-api/src/org/briarproject/api/lifecycle/LifecycleManager.java
@@ -1,32 +1,49 @@
 package org.briarproject.api.lifecycle;
 
+import org.briarproject.api.clients.Client;
+
 import java.util.concurrent.ExecutorService;
 
 /**
- * Manages the lifecycle of the app, starting and stopping {@link Service
- * Services}, shutting down {@link java.util.concurrent.ExecutorService
+ * Manages the lifecycle of the app, starting {@link
+ * org.briarproject.api.clients.Client Clients}, starting and stopping {@link
+ * Service Services}, shutting down {@link java.util.concurrent.ExecutorService
  * ExecutorServices}, and opening and closing the {@link
  * org.briarproject.api.db.DatabaseComponent DatabaseComponent}.
  */
 public interface LifecycleManager {
 
-	/** The result of calling {@link LifecycleManager#startServices()}. */
-	enum StartResult { ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS }
+	/**
+	 * The result of calling {@link LifecycleManager#startServices()}.
+	 */
+	enum StartResult {
+		ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
+	}
+
+	/**
+	 * Registers a {@link Service} to be started and stopped.
+	 */
+	void registerService(Service s);
 
-	/** Registers a {@link Service} to be started and stopped. */
-	public void register(Service s);
+	/**
+	 * Registers a {@link org.briarproject.api.clients.Client Client} to be
+	 * started.
+	 */
+	void registerClient(Client c);
 
 	/**
 	 * Registers an {@link java.util.concurrent.ExecutorService ExecutorService}
 	 * to be shut down.
 	 */
-	public void registerForShutdown(ExecutorService e);
+	void registerForShutdown(ExecutorService e);
 
 	/**
-	 * Starts any registered {@link Service Services} and opens the {@link
-	 * org.briarproject.api.db.DatabaseComponent DatabaseComponent}.
+	 * Opens the {@link org.briarproject.api.db.DatabaseComponent
+	 * DatabaseComponent} and starts any registered {@link
+	 * org.briarproject.api.clients.Client Clients} and {@link Service
+	 * Services}.
 	 */
-	public StartResult startServices();
+	StartResult startServices();
 
 	/**
 	 * Stops any registered {@link Service Services}, shuts down any
@@ -34,20 +51,21 @@ public interface LifecycleManager {
 	 * and closes the {@link org.briarproject.api.db.DatabaseComponent
 	 * DatabaseComponent}.
 	 */
-	public void stopServices();
+	void stopServices();
 
 	/**
 	 * Waits for the {@link org.briarproject.api.db.DatabaseComponent
 	 * DatabaseComponent} to be opened before returning.
 	 */
-	public void waitForDatabase() throws InterruptedException;
+	void waitForDatabase() throws InterruptedException;
 
 	/**
 	 * Waits for the {@link org.briarproject.api.db.DatabaseComponent
-	 * DatabaseComponent} to be opened and all registered {@link Service
+	 * DatabaseComponent} to be opened and all registered {@link
+	 * org.briarproject.api.clients.Client Clients} and {@link Service
 	 * Services} to start before returning.
 	 */
-	public void waitForStartup() throws InterruptedException;
+	void waitForStartup() throws InterruptedException;
 
 	/**
 	 * Waits for all registered {@link Service Services} to stop, all
@@ -55,5 +73,5 @@ public interface LifecycleManager {
 	 * to shut down, and the {@link org.briarproject.api.db.DatabaseComponent
 	 * DatabaseComponent} to be closed before returning.
 	 */
-	public void waitForShutdown() throws InterruptedException;
+	void waitForShutdown() throws InterruptedException;
 }
\ No newline at end of file
diff --git a/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java b/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java
index bb0ede9441d3df0d73d33a3eaedd0caf53f313ff..56ed9f5300917b20a23c348c7da01467275df80d 100644
--- a/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java
+++ b/briar-core/src/org/briarproject/clients/PrivateGroupFactoryImpl.java
@@ -16,6 +16,8 @@ import javax.inject.Inject;
 
 class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 
+	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
+
 	private final GroupFactory groupFactory;
 	private final ClientHelper clientHelper;
 
@@ -26,6 +28,11 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 		this.clientHelper = clientHelper;
 	}
 
+	@Override
+	public Group createLocalGroup(ClientId clientId) {
+		return groupFactory.createGroup(clientId, LOCAL_GROUP_DESCRIPTOR);
+	}
+
 	@Override
 	public Group createPrivateGroup(ClientId clientId, Contact contact) {
 		AuthorId local = contact.getLocalAuthorId();
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 1b4d1c1b58096c2efeea2ce6411422c73e8bd3d9..79ff34325514239bc6331f5a36ab39b63e22a88a 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -231,6 +231,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		db.addTransportKeys(txn, c, k);
 	}
 
+	public boolean containsContact(Transaction transaction, AuthorId remote,
+			AuthorId local) throws DbException {
+		T txn = unbox(transaction);
+		if (!db.containsLocalAuthor(txn, local))
+			throw new NoSuchLocalAuthorException();
+		return db.containsContact(txn, remote, local);
+	}
+
+	public boolean containsGroup(Transaction transaction, GroupId g)
+			throws DbException {
+		T txn = unbox(transaction);
+		return db.containsGroup(txn, g);
+	}
+
 	public void deleteMessage(Transaction transaction, MessageId m)
 			throws DbException {
 		if (transaction.isReadOnly()) throw new IllegalArgumentException();
@@ -345,14 +359,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		return db.getContacts(txn, a);
 	}
 
-	public boolean containsContact(Transaction transaction, AuthorId remote,
-			AuthorId local) throws DbException {
-		T txn = unbox(transaction);
-		if (!db.containsLocalAuthor(txn, local))
-			throw new NoSuchLocalAuthorException();
-		return db.containsContact(txn, remote, local);
-	}
-
 	public DeviceId getDeviceId(Transaction transaction) throws DbException {
 		T txn = unbox(transaction);
 		return db.getDeviceId(txn);
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index 4a51872e7abc05aed8f2cdbfb59a6ecda10eca7d..f801e2770cf200a7fb0f1997d4dea5470a2cdec0 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -3,14 +3,13 @@ package org.briarproject.forum;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.data.BdfReaderFactory;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.data.MetadataParser;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.forum.ForumSharingManager;
 import org.briarproject.api.identity.AuthorFactory;
+import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
 
@@ -73,9 +72,11 @@ public class ForumModule {
 	@Provides
 	@Singleton
 	ForumSharingManager provideForumSharingManager(
+			LifecycleManager lifecycleManager,
 			ContactManager contactManager,
 			ValidationManager validationManager,
 			ForumSharingManagerImpl forumSharingManager) {
+		lifecycleManager.registerClient(forumSharingManager);
 		contactManager.registerAddContactHook(forumSharingManager);
 		contactManager.registerRemoveContactHook(forumSharingManager);
 		validationManager.registerIncomingMessageHook(
diff --git a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
index c30834e8f6b0a570644533b7bbd0e3b47b5a8fda..b975a4537790396f2e5ad6a5dc5b3d074d9a5f97 100644
--- a/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumSharingManagerImpl.java
@@ -1,6 +1,7 @@
 package org.briarproject.forum;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.Contact;
@@ -43,15 +44,13 @@ import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 
-class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
-		RemoveContactHook, IncomingMessageHook {
+class ForumSharingManagerImpl implements ForumSharingManager, Client,
+		AddContactHook, RemoveContactHook, IncomingMessageHook {
 
 	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
 			"cd11a5d04dccd9e2931d6fc3df456313"
 					+ "63bb3e9d9d0e9405fccdb051f41f5449"));
 
-	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
-
 	private final DatabaseComponent db;
 	private final ForumManager forumManager;
 	private final ClientHelper clientHelper;
@@ -73,8 +72,14 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 		this.privateGroupFactory = privateGroupFactory;
 		this.random = random;
 		this.clock = clock;
-		localGroup = groupFactory.createGroup(CLIENT_ID,
-				LOCAL_GROUP_DESCRIPTOR);
+		localGroup = privateGroupFactory.createLocalGroup(CLIENT_ID);
+	}
+
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		db.addGroup(txn, localGroup);
+		// Ensure we've set things up for any pre-existing contacts
+		for (Contact c : db.getContacts(txn)) addingContact(txn, c);
 	}
 
 	@Override
@@ -82,6 +87,8 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 		try {
 			// Create a group to share with the contact
 			Group g = getContactGroup(c);
+			// Return if we've already set things up for this contact
+			if (db.containsGroup(txn, g.getId())) return;
 			// Store the group and share it with the contact
 			db.addGroup(txn, g);
 			db.setVisibleToContact(txn, c.getId(), g.getId(), true);
@@ -297,8 +304,6 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
 
 	private List<Forum> getForumsSharedWithAllContacts(Transaction txn)
 			throws DbException, FormatException {
-		// Ensure the local group exists
-		db.addGroup(txn, localGroup);
 		// Find the latest update in the local group
 		LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
 		if (latest == null) return Collections.emptyList();
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java
index a01c62b1010fc5b6b964e4d8471faca613bb6a72..e28af29e4d1c0ffbc886e342739d6afc977d3b98 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java
@@ -1,6 +1,7 @@
 package org.briarproject.introduction;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
 import org.briarproject.api.clients.PrivateGroupFactory;
@@ -31,7 +32,6 @@ import org.briarproject.api.introduction.SessionId;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.ClientId;
 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.MessageId;
@@ -82,14 +82,13 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 
 class IntroductionManagerImpl extends BdfIncomingMessageHook
-		implements IntroductionManager, AddContactHook, RemoveContactHook {
+		implements IntroductionManager, Client, AddContactHook,
+		RemoveContactHook {
 
 	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
 			"23b1897c198a90ae75b976ac023d0f32"
 					+ "80ca67b12f2346b2c23a34f34e2434c3"));
 
-	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
-
 	private static final Logger LOG =
 			Logger.getLogger(IntroductionManagerImpl.class.getName());
 
@@ -104,8 +103,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 	@Inject
 	IntroductionManagerImpl(DatabaseComponent db,
 			MessageQueueManager messageQueueManager,
-			ClientHelper clientHelper, GroupFactory groupFactory,
-			PrivateGroupFactory privateGroupFactory,
+			ClientHelper clientHelper, PrivateGroupFactory privateGroupFactory,
 			MetadataEncoder metadataEncoder, MetadataParser metadataParser,
 			CryptoComponent cryptoComponent,
 			TransportPropertyManager transportPropertyManager,
@@ -117,6 +115,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 		this.messageQueueManager = messageQueueManager;
 		this.privateGroupFactory = privateGroupFactory;
 		this.metadataEncoder = metadataEncoder;
+		// TODO: Inject these dependencies for easier testing
 		this.introducerManager =
 				new IntroducerManager(this, clientHelper, clock,
 						cryptoComponent);
@@ -124,8 +123,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 				new IntroduceeManager(db, this, clientHelper, clock,
 						cryptoComponent, transportPropertyManager,
 						authorFactory, contactManager);
-		localGroup =
-				groupFactory.createGroup(CLIENT_ID, LOCAL_GROUP_DESCRIPTOR);
+		localGroup = privateGroupFactory.createLocalGroup(CLIENT_ID);
 	}
 
 	@Override
@@ -133,11 +131,21 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 		return CLIENT_ID;
 	}
 
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		db.addGroup(txn, localGroup);
+		// Ensure we've set things up for any pre-existing contacts
+		for (Contact c : db.getContacts(txn)) addingContact(txn, c);
+	}
+
 	@Override
 	public void addingContact(Transaction txn, Contact c) throws DbException {
 		try {
-			// create an introduction group for sending introduction messages
+			// Create an introduction group for sending introduction messages
 			Group g = getIntroductionGroup(c);
+			// Return if we've already set things up for this contact
+			if (db.containsGroup(txn, g.getId())) return;
+			// Store the group and share it with the contact
 			db.addGroup(txn, g);
 			db.setVisibleToContact(txn, c.getId(), g.getId(), true);
 			// Attach the contact ID to the group
@@ -169,7 +177,6 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 						introducerManager.abort(txn, d);
 					}
 				}
-
 			}
 		} catch (FormatException e) {
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -189,9 +196,6 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 	protected void incomingMessage(Transaction txn, Message m, BdfList body,
 			BdfDictionary message)	throws DbException {
 
-		// add local group for engine states to make sure it exists
-		db.addGroup(txn, localGroup);
-
 		// Get message data and type
 		GroupId groupId = m.getGroupId();
 		message.put(GROUP_ID, groupId);
@@ -265,15 +269,13 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 	public void makeIntroduction(Contact c1, Contact c2, String msg)
 			throws DbException, FormatException {
 
-			Transaction txn = db.startTransaction(false);
-			try {
-				// add local group for session states to make sure it exists
-				db.addGroup(txn, getLocalGroup());
-				introducerManager.makeIntroduction(txn, c1, c2, msg);
-				txn.setComplete();
-			} finally {
-				db.endTransaction(txn);
-			}
+		Transaction txn = db.startTransaction(false);
+		try {
+			introducerManager.makeIntroduction(txn, c1, c2, msg);
+			txn.setComplete();
+		} finally {
+			db.endTransaction(txn);
+		}
 	}
 
 	@Override
@@ -468,7 +470,7 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 			}
 			if (LOG.isLoggable(WARNING)) {
 				LOG.warning(
-						"No session state found for this message with session ID " +
+						"No session state found for message with session ID " +
 								Arrays.hashCode(sessionId));
 			}
 			throw new FormatException();
@@ -505,5 +507,4 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
 		db.deleteMessage(txn, messageId);
 		db.deleteMessageMetadata(txn, messageId);
 	}
-
 }
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionModule.java b/briar-core/src/org/briarproject/introduction/IntroductionModule.java
index 686a34edadb0d4451cc4162bcf79bac33345581b..4fb0e493df2349fbe75da4acbe4a25f5932e18ea 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionModule.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionModule.java
@@ -5,6 +5,7 @@ import org.briarproject.api.clients.MessageQueueManager;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.data.MetadataEncoder;
 import org.briarproject.api.introduction.IntroductionManager;
+import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.system.Clock;
 
 import javax.inject.Inject;
@@ -41,15 +42,17 @@ public class IntroductionModule {
 	@Provides
 	@Singleton
 	IntroductionManager getIntroductionManager(
+			LifecycleManager lifecycleManager,
 			ContactManager contactManager,
 			MessageQueueManager messageQueueManager,
 			IntroductionManagerImpl introductionManager) {
 
+		lifecycleManager.registerClient(introductionManager);
 		contactManager.registerAddContactHook(introductionManager);
 		contactManager.registerRemoveContactHook(introductionManager);
-		messageQueueManager
-				.registerIncomingMessageHook(introductionManager.getClientId(),
-						introductionManager);
+		messageQueueManager.registerIncomingMessageHook(
+				introductionManager.getClientId(),
+				introductionManager);
 
 		return introductionManager;
 	}
diff --git a/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
index f153a3a37714ca1ae8c7935fa12c2d9d950138ea..bd2d31765cf4f228e15875bc08b7ec9b0e0f429c 100644
--- a/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
+++ b/briar-core/src/org/briarproject/lifecycle/LifecycleManagerImpl.java
@@ -1,14 +1,16 @@
 package org.briarproject.lifecycle;
 
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.ShutdownEvent;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.Service;
 
 import java.io.IOException;
-import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -31,8 +33,9 @@ class LifecycleManagerImpl implements LifecycleManager {
 
 	private final DatabaseComponent db;
 	private final EventBus eventBus;
-	private final Collection<Service> services;
-	private final Collection<ExecutorService> executors;
+	private final List<Service> services;
+	private final List<Client> clients;
+	private final List<ExecutorService> executors;
 	private final Semaphore startStopSemaphore = new Semaphore(1);
 	private final CountDownLatch dbLatch = new CountDownLatch(1);
 	private final CountDownLatch startupLatch = new CountDownLatch(1);
@@ -43,15 +46,22 @@ class LifecycleManagerImpl implements LifecycleManager {
 		this.db = db;
 		this.eventBus = eventBus;
 		services = new CopyOnWriteArrayList<Service>();
+		clients = new CopyOnWriteArrayList<Client>();
 		executors = new CopyOnWriteArrayList<ExecutorService>();
 	}
 
-	public void register(Service s) {
+	public void registerService(Service s) {
 		if (LOG.isLoggable(INFO))
 			LOG.info("Registering service " + s.getClass().getName());
 		services.add(s);
 	}
 
+	public void registerClient(Client c) {
+		if (LOG.isLoggable(INFO))
+			LOG.info("Registering client " + c.getClass().getName());
+		clients.add(c);
+	}
+
 	public void registerForShutdown(ExecutorService e) {
 		if (LOG.isLoggable(INFO))
 			LOG.info("Registering executor " + e.getClass().getName());
@@ -74,15 +84,28 @@ class LifecycleManagerImpl implements LifecycleManager {
 				else LOG.info("Creating database took " + duration + " ms");
 			}
 			dbLatch.countDown();
+			Transaction txn = db.startTransaction(false);
+			try {
+				for (Client c : clients) {
+					start = System.currentTimeMillis();
+					c.createLocalState(txn);
+					duration = System.currentTimeMillis() - start;
+					if (LOG.isLoggable(INFO)) {
+						LOG.info("Starting " + c.getClass().getName()
+								+ " took " + duration + " ms");
+					}
+				}
+				txn.setComplete();
+			} finally {
+				db.endTransaction(txn);
+			}
 			for (Service s : services) {
 				start = System.currentTimeMillis();
 				boolean started = s.start();
 				duration = System.currentTimeMillis() - start;
 				if (!started) {
-					if (LOG.isLoggable(WARNING)) {
-						String name = s.getClass().getName();
-						LOG.warning(name + " did not start");
-					}
+					if (LOG.isLoggable(WARNING))
+						LOG.warning(s.getClass().getName() + " did not start");
 					return SERVICE_ERROR;
 				}
 				if (LOG.isLoggable(INFO)) {
diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
index e7e64552f5c3a6d42f6b8ec2123c2edf03ebfe1b..93a0cc979cfbab9f5296d95158555132d28c650d 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -1,6 +1,7 @@
 package org.briarproject.messaging;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.Contact;
@@ -28,7 +29,7 @@ import java.util.Map;
 
 import javax.inject.Inject;
 
-class MessagingManagerImpl implements MessagingManager, AddContactHook,
+class MessagingManagerImpl implements MessagingManager, Client, AddContactHook,
 		RemoveContactHook {
 
 	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
@@ -47,11 +48,19 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
 		this.privateGroupFactory = privateGroupFactory;
 	}
 
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		// Ensure we've set things up for any pre-existing contacts
+		for (Contact c : db.getContacts(txn)) addingContact(txn, c);
+	}
+
 	@Override
 	public void addingContact(Transaction txn, Contact c) throws DbException {
 		try {
 			// Create a group to share with the contact
 			Group g = getContactGroup(c);
+			// Return if we've already set things up for this contact
+			if (db.containsGroup(txn, g.getId())) return;
 			// Store the group and share it with the contact
 			db.addGroup(txn, g);
 			db.setVisibleToContact(txn, c.getId(), g.getId(), true);
diff --git a/briar-core/src/org/briarproject/messaging/MessagingModule.java b/briar-core/src/org/briarproject/messaging/MessagingModule.java
index b943a6921d87c689ed83ac44dbf36424e9338207..8fec2d9b6c10b24a9adc0c6398d27555a3a59324 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingModule.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingModule.java
@@ -3,6 +3,7 @@ package org.briarproject.messaging;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessageFactory;
 import org.briarproject.api.sync.ValidationManager;
@@ -43,8 +44,10 @@ public class MessagingModule {
 
 	@Provides
 	@Singleton
-	MessagingManager getMessagingManager(ContactManager contactManager,
+	MessagingManager getMessagingManager(LifecycleManager lifecycleManager,
+			ContactManager contactManager,
 			MessagingManagerImpl messagingManager) {
+		lifecycleManager.registerClient(messagingManager);
 		contactManager.registerAddContactHook(messagingManager);
 		contactManager.registerRemoveContactHook(messagingManager);
 		return messagingManager;
diff --git a/briar-core/src/org/briarproject/plugins/PluginsModule.java b/briar-core/src/org/briarproject/plugins/PluginsModule.java
index d106d8920f549b308a30006094e01818ec8e1313..41294866593643833f27c5c9f69fa3218f28fa0d 100644
--- a/briar-core/src/org/briarproject/plugins/PluginsModule.java
+++ b/briar-core/src/org/briarproject/plugins/PluginsModule.java
@@ -64,7 +64,7 @@ public class PluginsModule {
 	@Singleton
 	PluginManager getPluginManager(LifecycleManager lifecycleManager,
 			PluginManagerImpl pluginManager) {
-		lifecycleManager.register(pluginManager);
+		lifecycleManager.registerService(pluginManager);
 		return pluginManager;
 	}
 }
diff --git a/briar-core/src/org/briarproject/properties/PropertiesModule.java b/briar-core/src/org/briarproject/properties/PropertiesModule.java
index e9f2fcf5801726e72acd6ea8c815be42c68cf738..cea2c7504e7a563856d6c7257da8e0c329e3dd29 100644
--- a/briar-core/src/org/briarproject/properties/PropertiesModule.java
+++ b/briar-core/src/org/briarproject/properties/PropertiesModule.java
@@ -3,6 +3,7 @@ package org.briarproject.properties;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.data.MetadataEncoder;
+import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
@@ -36,8 +37,10 @@ public class PropertiesModule {
 
 	@Provides @Singleton
 	TransportPropertyManager getTransportPropertyManager(
+			LifecycleManager lifecycleManager,
 			ContactManager contactManager,
 			TransportPropertyManagerImpl transportPropertyManager) {
+		lifecycleManager.registerClient(transportPropertyManager);
 		contactManager.registerAddContactHook(transportPropertyManager);
 		contactManager.registerRemoveContactHook(transportPropertyManager);
 		return transportPropertyManager;
diff --git a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
index 4b10c48edeef1b79ea322826b2e3661bd3d7f32b..f393e3eaa29f9c49a88df097e189797d11e5d18c 100644
--- a/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
+++ b/briar-core/src/org/briarproject/properties/TransportPropertyManagerImpl.java
@@ -3,6 +3,7 @@ package org.briarproject.properties;
 import org.briarproject.api.DeviceId;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.PrivateGroupFactory;
 import org.briarproject.api.contact.Contact;
@@ -13,13 +14,11 @@ import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.NoSuchGroupException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.ClientId;
 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.MessageId;
@@ -34,14 +33,12 @@ import java.util.Map.Entry;
 import javax.inject.Inject;
 
 class TransportPropertyManagerImpl implements TransportPropertyManager,
-		AddContactHook, RemoveContactHook {
+		Client, AddContactHook, RemoveContactHook {
 
 	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
 			"673ea091673561e28f70122f6a8ea8f4"
 					+ "97c3624b86fa07f785bb15f09fb87b4b"));
 
-	private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
-
 	private final DatabaseComponent db;
 	private final ClientHelper clientHelper;
 	private final PrivateGroupFactory privateGroupFactory;
@@ -50,20 +47,28 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 
 	@Inject
 	TransportPropertyManagerImpl(DatabaseComponent db,
-			ClientHelper clientHelper, GroupFactory groupFactory,
-			PrivateGroupFactory privateGroupFactory, Clock clock) {
+			ClientHelper clientHelper, PrivateGroupFactory privateGroupFactory,
+			Clock clock) {
 		this.db = db;
 		this.clientHelper = clientHelper;
 		this.privateGroupFactory = privateGroupFactory;
 		this.clock = clock;
-		localGroup = groupFactory.createGroup(CLIENT_ID,
-				LOCAL_GROUP_DESCRIPTOR);
+		localGroup = privateGroupFactory.createLocalGroup(CLIENT_ID);
+	}
+
+	@Override
+	public void createLocalState(Transaction txn) throws DbException {
+		db.addGroup(txn, localGroup);
+		// Ensure we've set things up for any pre-existing contacts
+		for (Contact c : db.getContacts(txn)) addingContact(txn, c);
 	}
 
 	@Override
 	public void addingContact(Transaction txn, Contact c) throws DbException {
 		// Create a group to share with the contact
 		Group g = getContactGroup(c);
+		// Return if we've already set things up for this contact
+		if (db.containsGroup(txn, g.getId())) return;
 		// Store the group and share it with the contact
 		db.addGroup(txn, g);
 		db.setVisibleToContact(txn, c.getId(), g.getId(), true);
@@ -126,9 +131,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 				db.endTransaction(txn);
 			}
 			return p;
-		} catch (NoSuchGroupException e) {
-			// Local group doesn't exist - there are no local properties
-			return null;
 		} catch (FormatException e) {
 			throw new DbException(e);
 		}
@@ -169,8 +171,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 		try {
 			Transaction txn = db.startTransaction(false);
 			try {
-				// Create the local group if necessary
-				db.addGroup(txn, localGroup);
 				// Merge the new properties with any existing properties
 				TransportProperties merged;
 				boolean changed;
@@ -230,9 +230,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
 				local.put(e.getKey(), parseProperties(message));
 			}
 			return local;
-		} catch (NoSuchGroupException e) {
-			// Local group doesn't exist - there are no local properties
-			return Collections.emptyMap();
 		} catch (FormatException e) {
 			throw new DbException(e);
 		}
diff --git a/briar-core/src/org/briarproject/sync/SyncModule.java b/briar-core/src/org/briarproject/sync/SyncModule.java
index 15d13ce66fc0174e188bc0bcd7ce2386cb90ef11..2cbbac5d796c17d288ee0cb7c82f4391d75a3f0b 100644
--- a/briar-core/src/org/briarproject/sync/SyncModule.java
+++ b/briar-core/src/org/briarproject/sync/SyncModule.java
@@ -62,7 +62,7 @@ public class SyncModule {
 	@Singleton
 	ValidationManager getValidationManager(LifecycleManager lifecycleManager,
 			EventBus eventBus, ValidationManagerImpl validationManager) {
-		lifecycleManager.register(validationManager);
+		lifecycleManager.registerService(validationManager);
 		eventBus.addListener(validationManager);
 		return validationManager;
 	}
diff --git a/briar-core/src/org/briarproject/transport/TransportModule.java b/briar-core/src/org/briarproject/transport/TransportModule.java
index d5819fbfcbe19f94b376f51dbac8db4fb4fd3d2b..5cc81e458a00c02b109721c0061bc7ae4c37ac91 100644
--- a/briar-core/src/org/briarproject/transport/TransportModule.java
+++ b/briar-core/src/org/briarproject/transport/TransportModule.java
@@ -37,7 +37,7 @@ public class TransportModule {
 	@Singleton
 	KeyManager getKeyManager(LifecycleManager lifecycleManager,
 			EventBus eventBus, KeyManagerImpl keyManager) {
-		lifecycleManager.register(keyManager);
+		lifecycleManager.registerService(keyManager);
 		eventBus.addListener(keyManager);
 		return keyManager;
 	}
diff --git a/briar-tests/src/org/briarproject/TestLifecycleModule.java b/briar-tests/src/org/briarproject/TestLifecycleModule.java
index fbf07ca0d4ff861a7de86dd0ca2fd87331305cab..04137713c0315464ffbb0e3e5989959ce3abc437 100644
--- a/briar-tests/src/org/briarproject/TestLifecycleModule.java
+++ b/briar-tests/src/org/briarproject/TestLifecycleModule.java
@@ -1,5 +1,6 @@
 package org.briarproject;
 
+import org.briarproject.api.clients.Client;
 import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.Service;
@@ -20,8 +21,14 @@ public class TestLifecycleModule {
 	@Provides
 	LifecycleManager provideLifecycleManager() {
 		return new LifecycleManager() {
+
 			@Override
-			public void register(Service s) {
+			public void registerService(Service s) {
+
+			}
+
+			@Override
+			public void registerClient(Client c) {
 
 			}
 
@@ -60,6 +67,7 @@ public class TestLifecycleModule {
 	@Provides
 	ShutdownManager provideShutdownManager() {
 		return new ShutdownManager() {
+
 			@Override
 			public int addShutdownHook(Runnable hook) {
 				return 0;
@@ -75,8 +83,7 @@ public class TestLifecycleModule {
 	@Provides
 	@IoExecutor
 	@Singleton
-	Executor provideExecutor() {
+	Executor provideIoExecutor() {
 		return Executors.newCachedThreadPool();
 	}
-
 }