diff --git a/briar-android/src/org/briarproject/android/AndroidModule.java b/briar-android/src/org/briarproject/android/AndroidModule.java
index 1adf3309418600b19ebe03882e0cb1973fd82cb8..1a46ec730a211872a2e4e4064c1e8b00c0933f7d 100644
--- a/briar-android/src/org/briarproject/android/AndroidModule.java
+++ b/briar-android/src/org/briarproject/android/AndroidModule.java
@@ -1,23 +1,24 @@
 package org.briarproject.android;
 
-import static android.content.Context.MODE_PRIVATE;
-
-import java.io.File;
+import android.app.Application;
 
-import javax.inject.Singleton;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.android.AndroidNotificationManager;
 import org.briarproject.api.android.ReferenceManager;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseConfig;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.ui.UiCallback;
 
-import android.app.Application;
+import java.io.File;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
+import javax.inject.Singleton;
+
+import static android.content.Context.MODE_PRIVATE;
 
 public class AndroidModule extends AbstractModule {
 
@@ -81,9 +82,10 @@ public class AndroidModule extends AbstractModule {
 
 	@Provides @Singleton
 	AndroidNotificationManager getAndroidNotificationManager(
-			LifecycleManager lifecycleManager,
+			LifecycleManager lifecycleManager, EventBus eventBus,
 			AndroidNotificationManagerImpl notificationManager) {
 		lifecycleManager.register(notificationManager);
+		eventBus.addListener(notificationManager);
 		return notificationManager;
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index 8a5bcba15e5acf951ffe05faed623fa53803d873..f83f83082e1327a2750c827aa89efaaaef7b573f 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -20,11 +20,11 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageValidatedEvent;
 import org.briarproject.api.event.SettingsUpdatedEvent;
 import org.briarproject.api.forum.ForumManager;
+import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
@@ -46,7 +46,7 @@ import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static java.util.logging.Level.WARNING;
 
 class AndroidNotificationManagerImpl implements AndroidNotificationManager,
-		EventListener {
+		Service, EventListener {
 
 	private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
 	private static final int FORUM_POST_NOTIFICATION_ID = 4;
@@ -60,7 +60,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	private final DatabaseComponent db;
 	private final Executor dbExecutor;
-	private final EventBus eventBus;
 	private final MessagingManager messagingManager;
 	private final ForumManager forumManager;
 	private final AndroidExecutor androidExecutor;
@@ -79,20 +78,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@Inject
 	public AndroidNotificationManagerImpl(DatabaseComponent db,
-			@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
+			@DatabaseExecutor Executor dbExecutor,
 			MessagingManager messagingManager, ForumManager forumManager,
 			AndroidExecutor androidExecutor, Application app) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
-		this.eventBus = eventBus;
 		this.messagingManager = messagingManager;
 		this.forumManager = forumManager;
 		this.androidExecutor = androidExecutor;
 		appContext = app.getApplicationContext();
 	}
 
+	@Override
 	public boolean start() {
-		eventBus.addListener(this);
 		loadSettings();
 		return true;
 	}
@@ -110,8 +108,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		});
 	}
 
+	@Override
 	public boolean stop() {
-		eventBus.removeListener(this);
 		clearNotifications();
 		return true;
 	}
diff --git a/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java b/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java
index ed3223c4b7652ec44f31e6674399efaa6de7bb97..3aad189dc32c3b404fa8cf1ba695c8e55e2acebd 100644
--- a/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java
+++ b/briar-api/src/org/briarproject/api/android/AndroidNotificationManager.java
@@ -1,10 +1,9 @@
 package org.briarproject.api.android;
 
-import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.sync.GroupId;
 
 /** Manages notifications for private messages and forum posts. */
-public interface AndroidNotificationManager extends Service {
+public interface AndroidNotificationManager {
 
 	void showPrivateMessageNotification(GroupId g);
 
diff --git a/briar-api/src/org/briarproject/api/contact/Contact.java b/briar-api/src/org/briarproject/api/contact/Contact.java
index 5c65c57d21f22a0ebd3d8e5c55bc9c2c87f44480..18a2cb446c31334407bfca08f916f6a94eea2d65 100644
--- a/briar-api/src/org/briarproject/api/contact/Contact.java
+++ b/briar-api/src/org/briarproject/api/contact/Contact.java
@@ -1,5 +1,6 @@
 package org.briarproject.api.contact;
 
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 
@@ -8,11 +9,14 @@ public class Contact {
 	private final ContactId id;
 	private final Author author;
 	private final AuthorId localAuthorId;
+	private final StorageStatus status;
 
-	public Contact(ContactId id, Author author, AuthorId localAuthorId) {
+	public Contact(ContactId id, Author author, AuthorId localAuthorId,
+			StorageStatus status) {
 		this.id = id;
 		this.author = author;
 		this.localAuthorId = localAuthorId;
+		this.status = status;
 	}
 
 	public ContactId getId() {
@@ -27,6 +31,10 @@ public class Contact {
 		return localAuthorId;
 	}
 
+	public StorageStatus getStatus() {
+		return status;
+	}
+
 	@Override
 	public int hashCode() {
 		return id.hashCode();
@@ -34,7 +42,6 @@ public class Contact {
 
 	@Override
 	public boolean equals(Object o) {
-		if (o instanceof Contact) return id.equals(((Contact) o).id);
-		return false;
+		return o instanceof Contact && id.equals(((Contact) o).id);
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/contact/ContactId.java b/briar-api/src/org/briarproject/api/contact/ContactId.java
index 1805818d7c884ff640b03d05f5f7b11c5c4d1fa5..736597a388a7b729ccf85bc4e078526fd3ec2b49 100644
--- a/briar-api/src/org/briarproject/api/contact/ContactId.java
+++ b/briar-api/src/org/briarproject/api/contact/ContactId.java
@@ -23,7 +23,6 @@ public class ContactId {
 
 	@Override
 	public boolean equals(Object o) {
-		if (o instanceof ContactId) return id == ((ContactId) o).id;
-		return false;
+		return o instanceof ContactId && id == ((ContactId) o).id;
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java
index f323c07371df5267ddc814ca9961e8743b9c1a2d..08c9dc46a1509fa5efca37571502e49898af6830 100644
--- a/briar-api/src/org/briarproject/api/contact/ContactManager.java
+++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java
@@ -8,6 +8,12 @@ import java.util.Collection;
 
 public interface ContactManager {
 
+	/** Registers a hook to be called whenever a contact is added. */
+	void registerAddContactHook(AddContactHook hook);
+
+	/** Registers a hook to be called whenever a contact is removed. */
+	void registerRemoveContactHook(RemoveContactHook hook);
+
 	/**
 	 * Stores a contact associated with the given local and remote pseudonyms,
 	 * and returns an ID for the contact.
@@ -22,4 +28,12 @@ public interface ContactManager {
 
 	/** Removes a contact and all associated state. */
 	void removeContact(ContactId c) throws DbException;
+
+	interface AddContactHook {
+		void addingContact(ContactId c);
+	}
+
+	interface RemoveContactHook {
+		void removingContact(ContactId c);
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 84cca32ea7d2333d80d60b4b7a4b3654ce5face5..e67f0cb912690bdcd529dbdaa5575caa935d425b 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -152,6 +152,9 @@ public interface DatabaseComponent {
 	/** Returns all contacts. */
 	Collection<Contact> getContacts() throws DbException;
 
+	/** Returns all contacts associated with the given local pseudonym. */
+	Collection<ContactId> getContacts(AuthorId a) throws DbException;
+
 	/** Returns the group with the given ID, if the user subscribes to it. */
 	Group getGroup(GroupId g) throws DbException;
 
@@ -297,6 +300,13 @@ public interface DatabaseComponent {
 	 */
 	void removeTransport(TransportId t) throws DbException;
 
+	/** Sets the status of the given contact. */
+	void setContactStatus(ContactId c, StorageStatus s) throws DbException;
+
+	/** Sets the status of the given local pseudonym. */
+	void setLocalAuthorStatus(AuthorId a, StorageStatus s)
+		throws DbException;
+
 	/** Marks the given message as valid or invalid. */
 	void setMessageValidity(Message m, ClientId c, boolean valid)
 			throws DbException;
diff --git a/briar-api/src/org/briarproject/api/db/StorageStatus.java b/briar-api/src/org/briarproject/api/db/StorageStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc2d554ffb08863f6409067ca727bc169aad05e0
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/db/StorageStatus.java
@@ -0,0 +1,21 @@
+package org.briarproject.api.db;
+
+public enum StorageStatus {
+
+	ADDING(0), ACTIVE(1), REMOVING(2);
+
+	private final int value;
+
+	StorageStatus(int value) {
+		this.value = value;
+	}
+
+	public int getValue() {
+		return value;
+	}
+
+	public static StorageStatus fromValue(int value) {
+		for (StorageStatus s : values()) if (s.value == value) return s;
+		throw new IllegalArgumentException();
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/identity/IdentityManager.java b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
index 207fea87d55170a1341993416b0ca0259c94cbb4..9a5a030abdeab1d485ee7a22fd6f8d3a78ac1ea5 100644
--- a/briar-api/src/org/briarproject/api/identity/IdentityManager.java
+++ b/briar-api/src/org/briarproject/api/identity/IdentityManager.java
@@ -6,6 +6,12 @@ import java.util.Collection;
 
 public interface IdentityManager {
 
+	/** Registers a hook to be called whenever a local pseudonym is added. */
+	void registerAddIdentityHook(AddIdentityHook hook);
+
+	/** Registers a hook to be called whenever a local pseudonym is removed. */
+	void registerRemoveIdentityHook(RemoveIdentityHook hook);
+
 	/** Stores a local pseudonym. */
 	void addLocalAuthor(LocalAuthor a) throws DbException;
 
@@ -17,4 +23,12 @@ public interface IdentityManager {
 
 	/** Removes a local pseudonym and all associated state. */
 	void removeLocalAuthor(AuthorId a) throws DbException;
+
+	interface AddIdentityHook {
+		void addingIdentity(AuthorId a);
+	}
+
+	interface RemoveIdentityHook {
+		void removingIdentity(AuthorId a);
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/identity/LocalAuthor.java b/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
index 9f5626d77b01cbde395f84dc02b97dec179e85e3..08ee121e533a7fb666705d3583032a7029390f43 100644
--- a/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
+++ b/briar-api/src/org/briarproject/api/identity/LocalAuthor.java
@@ -1,16 +1,20 @@
 package org.briarproject.api.identity;
 
+import org.briarproject.api.db.StorageStatus;
+
 /** A pseudonym for the local user. */
 public class LocalAuthor extends Author {
 
 	private final byte[] privateKey;
 	private final long created;
+	private final StorageStatus status;
 
 	public LocalAuthor(AuthorId id, String name, byte[] publicKey,
-			byte[] privateKey, long created) {
+			byte[] privateKey, long created, StorageStatus status) {
 		super(id, name, publicKey);
 		this.privateKey = privateKey;
 		this.created = created;
+		this.status = status;
 	}
 
 	/**  Returns the private key used to generate the pseudonym's signatures. */
@@ -18,7 +22,16 @@ public class LocalAuthor extends Author {
 		return privateKey;
 	}
 
+	/**
+	 * Returns the time the pseudonym was created, in milliseconds since the
+	 * Unix epoch.
+	 */
 	public long getTimeCreated() {
 		return created;
 	}
+
+	/** Returns the status of the pseudonym. */
+	public StorageStatus getStatus() {
+		return status;
+	}
 }
diff --git a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
index 7ce3241f05c2c6bd379fa1487c035f5e7daad534..f22a5131af2f4734f65b3dcdd4ef0734dcf8a7cf 100644
--- a/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
+++ b/briar-api/src/org/briarproject/api/messaging/MessagingManager.java
@@ -13,12 +13,6 @@ public interface MessagingManager {
 	/** Returns the unique ID of the messaging client. */
 	ClientId getClientId();
 
-	/**
-	 * Informs the messaging manager that a new contact has been added.
-	 * Creates a private conversation with the contact.
-	 */
-	void addContact(ContactId c) throws DbException;
-
 	/** Stores a local private message. */
 	void addLocalMessage(PrivateMessage m) throws DbException;
 
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
index efc336cf020c17686bd1441e50202828b437fecc..35248962d65da3a6e6d2de481a21e4c08f874f7b 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java
+++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java
@@ -1,16 +1,15 @@
 package org.briarproject.api.plugins;
 
-import java.util.Collection;
-
 import org.briarproject.api.TransportId;
-import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 
+import java.util.Collection;
+
 /**
  * Responsible for starting transport plugins at startup, stopping them at
  * shutdown, and providing access to plugins for exchanging invitations.
  */
-public interface PluginManager extends Service {
+public interface PluginManager {
 
 	/**
 	 * Returns the plugin for the given transport, or null if no such plugin
diff --git a/briar-api/src/org/briarproject/api/sync/MessageValidator.java b/briar-api/src/org/briarproject/api/sync/MessageValidator.java
index d318421a472c16b5a78f597028c8786901477fd7..d3d7ef520a2d0f29554f74dcb160417a28f86e19 100644
--- a/briar-api/src/org/briarproject/api/sync/MessageValidator.java
+++ b/briar-api/src/org/briarproject/api/sync/MessageValidator.java
@@ -1,9 +1,8 @@
 package org.briarproject.api.sync;
 
 import org.briarproject.api.db.Metadata;
-import org.briarproject.api.lifecycle.Service;
 
-public interface MessageValidator extends Service {
+public interface MessageValidator {
 
 	/**
 	 * Validates the given message and returns its metadata if the message
diff --git a/briar-api/src/org/briarproject/api/sync/ValidationManager.java b/briar-api/src/org/briarproject/api/sync/ValidationManager.java
index 729e96ddccd6a47a06a100d4e1f2ac7af01c286b..35fcea79c44b4c2e7dd27963cb141c69101c8cba 100644
--- a/briar-api/src/org/briarproject/api/sync/ValidationManager.java
+++ b/briar-api/src/org/briarproject/api/sync/ValidationManager.java
@@ -1,12 +1,30 @@
 package org.briarproject.api.sync;
 
-import org.briarproject.api.lifecycle.Service;
-
 /**
  * Responsible for managing message validators and passing them messages to
  * validate.
  */
-public interface ValidationManager extends Service {
+public interface ValidationManager {
+
+	enum Status {
+
+		UNKNOWN(0), INVALID(1), VALID(2);
+
+		private final int value;
+
+		Status(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public static Status fromValue(int value) {
+			for (Status s : values()) if (s.value == value) return s;
+			throw new IllegalArgumentException();
+		}
+	}
 
 	/** Sets the message validator for the given client. */
 	void setMessageValidator(ClientId c, MessageValidator v);
diff --git a/briar-api/src/org/briarproject/api/transport/KeyManager.java b/briar-api/src/org/briarproject/api/transport/KeyManager.java
index e2f76f081b89d057eed77a873add5149e85afa41..32800d7af410448fff4e9b8679349f091d217a4e 100644
--- a/briar-api/src/org/briarproject/api/transport/KeyManager.java
+++ b/briar-api/src/org/briarproject/api/transport/KeyManager.java
@@ -3,7 +3,6 @@ package org.briarproject.api.transport;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
-import org.briarproject.api.lifecycle.Service;
 
 import java.util.Collection;
 
@@ -11,7 +10,7 @@ import java.util.Collection;
  * Responsible for managing transport keys and recognising the pseudo-random
  * tags of incoming streams.
  */
-public interface KeyManager extends Service {
+public interface KeyManager {
 
 	/**
 	 * Informs the key manager that a new contact has been added. Derives and
diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
index 3a1648f16cc3d875e198cb81178b50e9911e8960..fa1c096219f35df6889f892839273b8806af4b94 100644
--- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
@@ -7,38 +7,127 @@ import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.NoSuchContactException;
+import org.briarproject.api.event.ContactAddedEvent;
+import org.briarproject.api.event.ContactRemovedEvent;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager.RemoveIdentityHook;
+import org.briarproject.api.lifecycle.Service;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
 
-class ContactManagerImpl implements ContactManager {
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.db.StorageStatus.ACTIVE;
+import static org.briarproject.api.db.StorageStatus.ADDING;
+import static org.briarproject.api.db.StorageStatus.REMOVING;
+
+class ContactManagerImpl implements ContactManager, Service,
+		RemoveIdentityHook {
+
+	private static final Logger LOG =
+			Logger.getLogger(ContactManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
+	private final EventBus eventBus;
+	private final List<AddContactHook> addHooks;
+	private final List<RemoveContactHook> removeHooks;
 
 	@Inject
-	ContactManagerImpl(DatabaseComponent db) {
+	ContactManagerImpl(DatabaseComponent db, EventBus eventBus) {
 		this.db = db;
+		this.eventBus = eventBus;
+		addHooks = new CopyOnWriteArrayList<AddContactHook>();
+		removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
+	}
+
+	@Override
+	public boolean start() {
+		// Finish adding/removing any partly added/removed contacts
+		try {
+			for (Contact c : db.getContacts()) {
+				if (c.getStatus().equals(ADDING)) {
+					for (AddContactHook hook : addHooks)
+						hook.addingContact(c.getId());
+					db.setContactStatus(c.getId(), ACTIVE);
+					eventBus.broadcast(new ContactAddedEvent(c.getId()));
+				} else if (c.getStatus().equals(REMOVING)) {
+					for (RemoveContactHook hook : removeHooks)
+						hook.removingContact(c.getId());
+					db.removeContact(c.getId());
+					eventBus.broadcast(new ContactRemovedEvent(c.getId()));
+				}
+			}
+			return true;
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return false;
+		}
+	}
+
+	@Override
+	public boolean stop() {
+		return true;
+	}
+
+	@Override
+	public void registerAddContactHook(AddContactHook hook) {
+		addHooks.add(hook);
+	}
+
+	@Override
+	public void registerRemoveContactHook(RemoveContactHook hook) {
+		removeHooks.add(hook);
 	}
 
 	@Override
 	public ContactId addContact(Author remote, AuthorId local)
 			throws DbException {
-		return db.addContact(remote, local);
+		ContactId c = db.addContact(remote, local);
+		for (AddContactHook hook : addHooks) hook.addingContact(c);
+		db.setContactStatus(c, ACTIVE);
+		eventBus.broadcast(new ContactAddedEvent(c));
+		return c;
 	}
 
 	@Override
 	public Contact getContact(ContactId c) throws DbException {
-		return db.getContact(c);
+		Contact contact = db.getContact(c);
+		if (contact.getStatus().equals(ACTIVE)) return contact;
+		throw new NoSuchContactException();
 	}
 
 	@Override
 	public Collection<Contact> getContacts() throws DbException {
-		return db.getContacts();
+		Collection<Contact> contacts = db.getContacts();
+		// Filter out any contacts that are being added or removed
+		List<Contact> active = new ArrayList<Contact>(contacts.size());
+		for (Contact c : contacts)
+			if (c.getStatus().equals(ACTIVE)) active.add(c);
+		return Collections.unmodifiableList(active);
 	}
 
 	@Override
 	public void removeContact(ContactId c) throws DbException {
+		db.setContactStatus(c, REMOVING);
+		for (RemoveContactHook hook : removeHooks) hook.removingContact(c);
 		db.removeContact(c);
+		eventBus.broadcast(new ContactRemovedEvent(c));
+	}
+
+	@Override
+	public void removingIdentity(AuthorId a) {
+		// Remove any contacts of the local pseudonym that's being removed
+		try {
+			for (ContactId c : db.getContacts(a)) removeContact(c);
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
 	}
 }
diff --git a/briar-core/src/org/briarproject/contact/ContactModule.java b/briar-core/src/org/briarproject/contact/ContactModule.java
index 6bd786aa3b63715bef22dea8948ab4787a32aeeb..5bfb8e404e0748b33a6095c3a5c263f6a3c2086b 100644
--- a/briar-core/src/org/briarproject/contact/ContactModule.java
+++ b/briar-core/src/org/briarproject/contact/ContactModule.java
@@ -1,13 +1,25 @@
 package org.briarproject.contact;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 
 import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.lifecycle.LifecycleManager;
+
+import javax.inject.Singleton;
 
 public class ContactModule extends AbstractModule {
 
 	@Override
-	protected void configure() {
-		bind(ContactManager.class).to(ContactManagerImpl.class);
+	protected void configure() {}
+
+	@Provides @Singleton
+	ContactManager getContactManager(LifecycleManager lifecycleManager,
+			IdentityManager identityManager,
+			ContactManagerImpl contactManager) {
+		lifecycleManager.register(contactManager);
+		identityManager.registerRemoveIdentityHook(contactManager);
+		return contactManager;
 	}
 }
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index f512b855303e249fc994924cd5baa80737694241..30e1754530c28d6a8e622dcdf0968b9c661b1d33 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -7,6 +7,7 @@ import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
@@ -334,7 +335,7 @@ interface Database<T> {
 	 * Locking: read
 	 */
 	Collection<MessageStatus> getMessageStatus(T txn, ContactId c, GroupId g)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the status of the given message with respect to the given
@@ -343,7 +344,7 @@ interface Database<T> {
 	 * Locking: read
 	 */
 	MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the IDs of some messages received from the given contact that
@@ -388,7 +389,7 @@ interface Database<T> {
 	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
-		throws DbException;
+			throws DbException;
 
 	/**
 	 * Returns the message with the given ID, in serialised form.
@@ -631,7 +632,27 @@ interface Database<T> {
 	 */
 	void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
 
-	/** Marks the given message as valid or invalid. */
+	/**
+	 * Sets the status of the given contact.
+	 * <p>
+	 * Locking: write.
+	 */
+	void setContactStatus(T txn, ContactId c, StorageStatus s)
+			throws DbException;
+
+	/**
+	 * Sets the status of the given local pseudonym.
+	 * <p>
+	 * Locking: write.
+	 */
+	void setLocalAuthorStatus(T txn, AuthorId a, StorageStatus s)
+			throws DbException;
+
+	/**
+	 * Marks the given message as valid or invalid.
+	 * <p>
+	 * Locking: write.
+	 */
 	void setMessageValidity(T txn, MessageId m, boolean valid)
 			throws DbException;
 
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 3378b8ff537e468bc58a476ddf6bf3cf922daf48..07aaee5ccd879fae76b1ef43c77c030e044df8b9 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -16,11 +16,8 @@ import org.briarproject.api.db.NoSuchLocalAuthorException;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
-import org.briarproject.api.event.ContactAddedEvent;
-import org.briarproject.api.event.ContactRemovedEvent;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.LocalAuthorAddedEvent;
-import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
@@ -147,7 +144,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 
 	public ContactId addContact(Author remote, AuthorId local)
 			throws DbException {
-		ContactId c;
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
@@ -156,8 +152,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 					throw new ContactExistsException();
 				if (!db.containsLocalAuthor(txn, local))
 					throw new NoSuchLocalAuthorException();
-				c = db.addContact(txn, remote, local);
+				ContactId c = db.addContact(txn, remote, local);
 				db.commitTransaction(txn);
+				return c;
 			} catch (DbException e) {
 				db.abortTransaction(txn);
 				throw e;
@@ -165,8 +162,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		eventBus.broadcast(new ContactAddedEvent(c));
-		return c;
 	}
 
 	public void addContactGroup(ContactId c, Group g) throws DbException {
@@ -221,7 +216,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	public void addLocalMessage(Message m, ClientId c, Metadata meta)
@@ -575,6 +569,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		}
 	}
 
+	public Collection<ContactId> getContacts(AuthorId a) throws DbException {
+		lock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if (!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				Collection<ContactId> contacts = db.getContacts(txn, a);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
 	public Group getGroup(GroupId g) throws DbException {
 		lock.readLock().lock();
 		try {
@@ -1219,7 +1232,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		eventBus.broadcast(new ContactRemovedEvent(c));
 	}
 
 	public void removeGroup(Group g) throws DbException {
@@ -1246,14 +1258,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 	}
 
 	public void removeLocalAuthor(AuthorId a) throws DbException {
-		Collection<ContactId> affected;
 		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
 				if (!db.containsLocalAuthor(txn, a))
 					throw new NoSuchLocalAuthorException();
-				affected = db.getContacts(txn, a);
 				db.removeLocalAuthor(txn, a);
 				db.commitTransaction(txn);
 			} catch (DbException e) {
@@ -1263,9 +1273,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		} finally {
 			lock.writeLock().unlock();
 		}
-		for (ContactId c : affected)
-			eventBus.broadcast(new ContactRemovedEvent(c));
-		eventBus.broadcast(new LocalAuthorRemovedEvent(a));
 	}
 
 	public void removeTransport(TransportId t) throws DbException {
@@ -1287,6 +1294,44 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		eventBus.broadcast(new TransportRemovedEvent(t));
 	}
 
+	public void setContactStatus(ContactId c, StorageStatus s)
+			throws DbException {
+		lock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if (!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setContactStatus(txn, c, s);
+				db.commitTransaction(txn);
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	public void setLocalAuthorStatus(AuthorId a, StorageStatus s)
+			throws DbException {
+		lock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if (!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				db.setLocalAuthorStatus(txn, a, s);
+				db.commitTransaction(txn);
+			} catch (DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			lock.writeLock().unlock();
+		}
+	}
+
 	public void setMessageValidity(Message m, ClientId c, boolean valid)
 			throws DbException {
 		lock.writeLock().lock();
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 80c9a1dd90d26ca7b86bfcf720c792bce3851431..c2ea25a9ce72e173f14d1022589fbd63b91a9f1f 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -9,6 +9,7 @@ import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbClosedException;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
@@ -51,7 +52,11 @@ import java.util.logging.Logger;
 
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.db.Metadata.REMOVE;
+import static org.briarproject.api.db.StorageStatus.ADDING;
 import static org.briarproject.api.sync.SyncConstants.MAX_SUBSCRIPTIONS;
+import static org.briarproject.api.sync.ValidationManager.Status.INVALID;
+import static org.briarproject.api.sync.ValidationManager.Status.UNKNOWN;
+import static org.briarproject.api.sync.ValidationManager.Status.VALID;
 import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
 
 /**
@@ -60,12 +65,8 @@ import static org.briarproject.db.ExponentialBackoff.calculateExpiry;
  */
 abstract class JdbcDatabase implements Database<Connection> {
 
-	private static final int SCHEMA_VERSION = 14;
-	private static final int MIN_SCHEMA_VERSION = 14;
-
-	private static final int VALIDATION_UNKNOWN = 0;
-	private static final int VALIDATION_INVALID = 1;
-	private static final int VALIDATION_VALID = 2;
+	private static final int SCHEMA_VERSION = 16;
+	private static final int MIN_SCHEMA_VERSION = 16;
 
 	private static final String CREATE_SETTINGS =
 			"CREATE TABLE settings"
@@ -81,6 +82,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " publicKey BINARY NOT NULL,"
 					+ " privateKey BINARY NOT NULL,"
 					+ " created BIGINT NOT NULL,"
+					+ " status INT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
 	private static final String CREATE_CONTACTS =
@@ -90,6 +92,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " name VARCHAR NOT NULL,"
 					+ " publicKey BINARY NOT NULL,"
 					+ " localAuthorId HASH NOT NULL,"
+					+ " status INT NOT NULL,"
 					+ " PRIMARY KEY (contactId),"
 					+ " UNIQUE (authorId),"
 					+ " FOREIGN KEY (localAuthorId)"
@@ -533,13 +536,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			// Create a contact row
 			String sql = "INSERT INTO contacts"
-					+ " (authorId, name, publicKey, localAuthorId)"
-					+ " VALUES (?, ?, ?, ?)";
+					+ " (authorId, name, publicKey, localAuthorId, status)"
+					+ " VALUES (?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, remote.getId().getBytes());
 			ps.setString(2, remote.getName());
 			ps.setBytes(3, remote.getPublicKey());
 			ps.setBytes(4, local.getBytes());
+			ps.setInt(5, ADDING.getValue());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -717,15 +721,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 			throws DbException {
 		PreparedStatement ps = null;
 		try {
-			String sql = "INSERT INTO localAuthors"
-					+ " (authorId, name, publicKey, privateKey, created)"
-					+ " VALUES (?, ?, ?, ?, ?)";
+			String sql = "INSERT INTO localAuthors (authorId, name, publicKey,"
+					+ " privateKey, created, status)"
+					+ " VALUES (?, ?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, a.getId().getBytes());
 			ps.setString(2, a.getName());
 			ps.setBytes(3, a.getPublicKey());
 			ps.setBytes(4, a.getPrivateKey());
 			ps.setLong(5, a.getTimeCreated());
+			ps.setInt(6, a.getStatus().getValue());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -747,7 +752,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.setBytes(2, m.getGroupId().getBytes());
 			ps.setLong(3, m.getTimestamp());
 			ps.setBoolean(4, local);
-			ps.setInt(5, local ? VALIDATION_VALID : VALIDATION_UNKNOWN);
+			ps.setInt(5, local ? VALID.getValue() : UNKNOWN.getValue());
 			byte[] raw = m.getRaw();
 			ps.setInt(6, raw.length);
 			ps.setBytes(7, raw);
@@ -1192,7 +1197,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, localAuthorId"
+			String sql = "SELECT authorId, name, publicKey, localAuthorId,"
+					+ " status"
 					+ " FROM contacts"
 					+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1203,10 +1209,11 @@ abstract class JdbcDatabase implements Database<Connection> {
 			String name = rs.getString(2);
 			byte[] publicKey = rs.getBytes(3);
 			AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
+			StorageStatus status = StorageStatus.fromValue(rs.getInt(5));
 			rs.close();
 			ps.close();
 			Author author = new Author(authorId, name, publicKey);
-			return new Contact(c, author, localAuthorId);
+			return new Contact(c, author, localAuthorId, status);
 		} catch (SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1240,7 +1247,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT contactId, authorId, name, publicKey,"
-					+ " localAuthorId"
+					+ " localAuthorId, status"
 					+ " FROM contacts";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
@@ -1252,7 +1259,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				byte[] publicKey = rs.getBytes(4);
 				Author author = new Author(authorId, name, publicKey);
 				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-				contacts.add(new Contact(contactId, author, localAuthorId));
+				StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
+				contacts.add(new Contact(contactId, author, localAuthorId,
+						status));
 			}
 			rs.close();
 			ps.close();
@@ -1339,7 +1348,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT name, publicKey, privateKey, created"
+			String sql = "SELECT name, publicKey, privateKey, created, status"
 					+ " FROM localAuthors"
 					+ " WHERE authorId = ?";
 			ps = txn.prepareStatement(sql);
@@ -1350,8 +1359,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 			byte[] publicKey = rs.getBytes(2);
 			byte[] privateKey = rs.getBytes(3);
 			long created = rs.getLong(4);
+			StorageStatus status = StorageStatus.fromValue(rs.getInt(5));
 			LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey,
-					privateKey, created);
+					privateKey, created, status);
 			if (rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
@@ -1368,7 +1378,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, privateKey, created"
+			String sql = "SELECT authorId, name, publicKey, privateKey,"
+					+ " created, status"
 					+ " FROM localAuthors";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
@@ -1379,8 +1390,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				byte[] publicKey = rs.getBytes(3);
 				byte[] privateKey = rs.getBytes(4);
 				long created = rs.getLong(5);
+				StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
 				authors.add(new LocalAuthor(authorId, name, publicKey,
-						privateKey, created));
+						privateKey, created, status));
 			}
 			rs.close();
 			ps.close();
@@ -1612,7 +1624,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " ORDER BY timestamp DESC LIMIT ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setInt(2, VALIDATION_VALID);
+			ps.setInt(2, VALID.getValue());
 			ps.setLong(3, now);
 			ps.setInt(4, maxMessages);
 			rs = ps.executeQuery();
@@ -1674,7 +1686,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " ORDER BY timestamp DESC";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setInt(2, VALIDATION_VALID);
+			ps.setInt(2, VALID.getValue());
 			ps.setLong(3, now);
 			rs = ps.executeQuery();
 			List<MessageId> ids = new ArrayList<MessageId>();
@@ -1704,7 +1716,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " JOIN groups AS g ON m.groupId = g.groupId"
 					+ " WHERE valid = ? AND clientId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setInt(1, VALIDATION_UNKNOWN);
+			ps.setInt(1, UNKNOWN.getValue());
 			ps.setBytes(2, c.getBytes());
 			rs = ps.executeQuery();
 			List<MessageId> ids = new ArrayList<MessageId>();
@@ -1799,7 +1811,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " ORDER BY timestamp DESC";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
-			ps.setInt(2, VALIDATION_VALID);
+			ps.setInt(2, VALID.getValue());
 			ps.setLong(3, now);
 			rs = ps.executeQuery();
 			List<MessageId> ids = new ArrayList<MessageId>();
@@ -1846,7 +1858,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			String sql = "SELECT c.contactId, authorId, c.name, publicKey,"
-					+ " localAuthorId"
+					+ " localAuthorId, status"
 					+ " FROM contacts AS c"
 					+ " JOIN contactGroups AS cg"
 					+ " ON c.contactId = cg.contactId"
@@ -1862,7 +1874,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 				byte[] publicKey = rs.getBytes(4);
 				Author author = new Author(authorId, name, publicKey);
 				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-				contacts.add(new Contact(contactId, author, localAuthorId));
+				StorageStatus status = StorageStatus.fromValue(rs.getInt(6));
+				contacts.add(new Contact(contactId, author, localAuthorId,
+						status));
 			}
 			rs.close();
 			ps.close();
@@ -2390,7 +2404,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void mergeSettings(Connection txn, Settings s, String namespace) throws DbException {
+	public void mergeSettings(Connection txn, Settings s, String namespace)
+			throws DbException {
 		PreparedStatement ps = null;
 		try {
 			// Update any settings that already exist
@@ -2687,13 +2702,48 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public void setContactStatus(Connection txn, ContactId c, StorageStatus s)
+		throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE contacts SET status = ? WHERE contactId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, s.getValue());
+			ps.setInt(2, c.getInt());
+			int affected = ps.executeUpdate();
+			if (affected < 0 || affected > 1) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
+	public void setLocalAuthorStatus(Connection txn, AuthorId a,
+			StorageStatus s) throws DbException {
+		PreparedStatement ps = null;
+		try {
+			String sql = "UPDATE localAuthors SET status = ?"
+					+ " WHERE authorId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, s.getValue());
+			ps.setBytes(2, a.getBytes());
+			int affected = ps.executeUpdate();
+			if (affected < 0 || affected > 1) throw new DbStateException();
+			ps.close();
+		} catch (SQLException e) {
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public void setMessageValidity(Connection txn, MessageId m, boolean valid)
 		throws DbException {
 		PreparedStatement ps = null;
 		try {
 			String sql = "UPDATE messages SET valid = ? WHERE messageId = ?";
 			ps = txn.prepareStatement(sql);
-			ps.setInt(1, valid ? VALIDATION_VALID : VALIDATION_INVALID);
+			ps.setInt(1, valid ? VALID.getValue() : INVALID.getValue());
 			ps.setBytes(2, m.getBytes());
 			int affected = ps.executeUpdate();
 			if (affected < 0) throw new DbStateException();
diff --git a/briar-core/src/org/briarproject/forum/ForumModule.java b/briar-core/src/org/briarproject/forum/ForumModule.java
index 60ac05bbe641f3f5efda11782c3adc0668627cae..94f0f829cdd6406702740dfe094fdcf84376f702 100644
--- a/briar-core/src/org/briarproject/forum/ForumModule.java
+++ b/briar-core/src/org/briarproject/forum/ForumModule.java
@@ -11,7 +11,6 @@ import org.briarproject.api.data.ObjectReader;
 import org.briarproject.api.forum.ForumManager;
 import org.briarproject.api.forum.ForumPostFactory;
 import org.briarproject.api.identity.Author;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
 
@@ -26,16 +25,17 @@ public class ForumModule extends AbstractModule {
 	}
 
 	@Provides @Singleton
-	ForumPostValidator getValidator(LifecycleManager lifecycleManager,
-			CryptoComponent crypto, ValidationManager validationManager,
+	ForumPostValidator getValidator(ValidationManager validationManager,
+			ForumManager forumManager, CryptoComponent crypto,
 			BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
 			ObjectReader<Author> authorReader, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		ForumPostValidator validator = new ForumPostValidator(crypto,
-				validationManager, bdfReaderFactory, bdfWriterFactory,
-				authorReader, metadataEncoder, clock);
-		lifecycleManager.register(validator);
+				bdfReaderFactory, bdfWriterFactory, authorReader,
+				metadataEncoder, clock);
+		validationManager.setMessageValidator(forumManager.getClientId(),
+				validator);
 		return validator;
 	}
 }
diff --git a/briar-core/src/org/briarproject/forum/ForumPostValidator.java b/briar-core/src/org/briarproject/forum/ForumPostValidator.java
index 205b290f478ca8589f0c683f9e7b56e7bb0c74eb..1ee770cf1875bc3294df0ee9151cd87891110b80 100644
--- a/briar-core/src/org/briarproject/forum/ForumPostValidator.java
+++ b/briar-core/src/org/briarproject/forum/ForumPostValidator.java
@@ -18,7 +18,6 @@ import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.MessageValidator;
-import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
 
 import java.io.ByteArrayInputStream;
@@ -34,7 +33,6 @@ import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENG
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
-import static org.briarproject.forum.ForumManagerImpl.CLIENT_ID;
 
 class ForumPostValidator implements MessageValidator {
 
@@ -42,7 +40,6 @@ class ForumPostValidator implements MessageValidator {
 			Logger.getLogger(ForumPostValidator.class.getName());
 
 	private final CryptoComponent crypto;
-	private final ValidationManager validationManager;
 	private final BdfReaderFactory bdfReaderFactory;
 	private final BdfWriterFactory bdfWriterFactory;
 	private final ObjectReader<Author> authorReader;
@@ -52,13 +49,11 @@ class ForumPostValidator implements MessageValidator {
 
 	@Inject
 	ForumPostValidator(CryptoComponent crypto,
-			ValidationManager validationManager,
 			BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory,
 			ObjectReader<Author> authorReader,
 			MetadataEncoder metadataEncoder, Clock clock) {
 		this.crypto = crypto;
-		this.validationManager = validationManager;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
 		this.authorReader = authorReader;
@@ -67,17 +62,6 @@ class ForumPostValidator implements MessageValidator {
 		keyParser = crypto.getSignatureKeyParser();
 	}
 
-	@Override
-	public boolean start() {
-		validationManager.setMessageValidator(CLIENT_ID, this);
-		return true;
-	}
-
-	@Override
-	public boolean stop() {
-		return true;
-	}
-
 	@Override
 	public Metadata validateMessage(Message m) {
 		// Reject the message if it's too far in the future
diff --git a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
index 89e4f324d31ee6c35106e14c13553d413bd815e9..a51ce023880530c5ffc915823a78d2209fe8271c 100644
--- a/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
+++ b/briar-core/src/org/briarproject/identity/IdentityManagerImpl.java
@@ -4,38 +4,114 @@ import com.google.inject.Inject;
 
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.NoSuchLocalAuthorException;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.LocalAuthorAddedEvent;
+import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.lifecycle.Service;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
 
-class IdentityManagerImpl implements IdentityManager {
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.db.StorageStatus.ACTIVE;
+import static org.briarproject.api.db.StorageStatus.ADDING;
+import static org.briarproject.api.db.StorageStatus.REMOVING;
+
+class IdentityManagerImpl implements IdentityManager, Service {
+
+	private static final Logger LOG =
+			Logger.getLogger(IdentityManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
+	private final EventBus eventBus;
+	private final List<AddIdentityHook> addHooks;
+	private final List<RemoveIdentityHook> removeHooks;
 
 	@Inject
-	IdentityManagerImpl(DatabaseComponent db) {
+	IdentityManagerImpl(DatabaseComponent db, EventBus eventBus) {
 		this.db = db;
+		this.eventBus = eventBus;
+		addHooks = new CopyOnWriteArrayList<AddIdentityHook>();
+		removeHooks = new CopyOnWriteArrayList<RemoveIdentityHook>();
+	}
+
+	@Override
+	public boolean start() {
+		// Finish adding/removing any partly added/removed pseudonyms
+		try {
+			for (LocalAuthor a : db.getLocalAuthors()) {
+				if (a.getStatus().equals(ADDING)) {
+					for (AddIdentityHook hook : addHooks)
+						hook.addingIdentity(a.getId());
+					db.setLocalAuthorStatus(a.getId(), ACTIVE);
+					eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
+				} else if (a.getStatus().equals(REMOVING)) {
+					for (RemoveIdentityHook hook : removeHooks)
+						hook.removingIdentity(a.getId());
+					db.removeLocalAuthor(a.getId());
+					eventBus.broadcast(new LocalAuthorRemovedEvent(a.getId()));
+				}
+			}
+			return true;
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+			return false;
+		}
+	}
+
+	@Override
+	public boolean stop() {
+		return false;
+	}
+
+	@Override
+	public void registerAddIdentityHook(AddIdentityHook hook) {
+		addHooks.add(hook);
+	}
+
+	@Override
+	public void registerRemoveIdentityHook(RemoveIdentityHook hook) {
+		removeHooks.add(hook);
 	}
 
 	@Override
 	public void addLocalAuthor(LocalAuthor a) throws DbException {
 		db.addLocalAuthor(a);
+		for (AddIdentityHook hook : addHooks) hook.addingIdentity(a.getId());
+		db.setLocalAuthorStatus(a.getId(), ACTIVE);
+		eventBus.broadcast(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	@Override
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
-		return db.getLocalAuthor(a);
+		LocalAuthor author = db.getLocalAuthor(a);
+		if (author.getStatus().equals(ACTIVE)) return author;
+		throw new NoSuchLocalAuthorException();
 	}
 
 	@Override
 	public Collection<LocalAuthor> getLocalAuthors() throws DbException {
-		return db.getLocalAuthors();
+		Collection<LocalAuthor> authors = db.getLocalAuthors();
+		// Filter out any pseudonyms that are being added or removed
+		List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size());
+		for (LocalAuthor a : authors)
+			if (a.getStatus().equals(ACTIVE)) active.add(a);
+		return Collections.unmodifiableList(active);
 	}
 
 	@Override
 	public void removeLocalAuthor(AuthorId a) throws DbException {
+		db.setLocalAuthorStatus(a, REMOVING);
+		for (RemoveIdentityHook hook : removeHooks) hook.removingIdentity(a);
 		db.removeLocalAuthor(a);
+		eventBus.broadcast(new LocalAuthorRemovedEvent(a));
 	}
 }
diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java
index 794f542ec7eaa2df0ac52448ff9a668d41aeeb16..590fc54582717cf6e7c4e6e345c22d5f7c032853 100644
--- a/briar-core/src/org/briarproject/invitation/AliceConnector.java
+++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java
@@ -14,7 +14,6 @@ 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;
@@ -48,7 +47,7 @@ class AliceConnector extends Connector {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager, MessagingManager messagingManager,
+			ContactManager contactManager,
 			TransportPropertyManager transportPropertyManager, Clock clock,
 			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
 			LocalAuthor localAuthor,
@@ -57,9 +56,8 @@ class AliceConnector extends Connector {
 		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
 				keyManager, connectionManager, contactManager,
-				messagingManager, transportPropertyManager, clock,
-				reuseConnection, group, plugin, localAuthor, localProps,
-				random);
+				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 270ecfbf197f06f4a546ba6185efe7cb18d137da..584a06a3da458d72dacb515dce588b5cd11a1db4 100644
--- a/briar-core/src/org/briarproject/invitation/BobConnector.java
+++ b/briar-core/src/org/briarproject/invitation/BobConnector.java
@@ -14,7 +14,6 @@ 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;
@@ -48,7 +47,7 @@ class BobConnector extends Connector {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager, MessagingManager messagingManager,
+			ContactManager contactManager,
 			TransportPropertyManager transportPropertyManager, Clock clock,
 			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
 			LocalAuthor localAuthor,
@@ -57,9 +56,8 @@ class BobConnector extends Connector {
 		super(crypto, bdfReaderFactory, bdfWriterFactory, streamReaderFactory,
 				streamWriterFactory, authorFactory, groupFactory,
 				keyManager, connectionManager, contactManager,
-				messagingManager, transportPropertyManager, clock,
-				reuseConnection, group, plugin, localAuthor, localProps,
-				random);
+				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 deb68a9d8ad12a7340e285b3e1c6b6bb324ba1a5..21f84ef2f466503f16d8e624788461fa00cf2521 100644
--- a/briar-core/src/org/briarproject/invitation/Connector.java
+++ b/briar-core/src/org/briarproject/invitation/Connector.java
@@ -20,7 +20,6 @@ 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;
@@ -65,7 +64,6 @@ abstract class Connector extends Thread {
 	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;
@@ -89,7 +87,7 @@ abstract class Connector extends Thread {
 			StreamWriterFactory streamWriterFactory,
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
-			ContactManager contactManager, MessagingManager messagingManager,
+			ContactManager contactManager,
 			TransportPropertyManager transportPropertyManager, Clock clock,
 			boolean reuseConnection, ConnectorGroup group, DuplexPlugin plugin,
 			LocalAuthor localAuthor,
@@ -106,7 +104,6 @@ abstract class Connector extends Thread {
 		this.keyManager = keyManager;
 		this.connectionManager = connectionManager;
 		this.contactManager = contactManager;
-		this.messagingManager = messagingManager;
 		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.reuseConnection = reuseConnection;
@@ -287,8 +284,6 @@ abstract class Connector extends Thread {
 		// Derive transport keys for each transport shared with the contact
 		keyManager.addContact(contactId, remoteProps.keySet(), master,
 				timestamp, alice);
-		// Create a private messaging conversation
-		messagingManager.addContact(contactId);
 	}
 
 	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 378fc54a5b421dc01b62c6119f897cb6f698a4c8..1a24b782c659fe07bdafba3c772c188a0df381a8 100644
--- a/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
+++ b/briar-core/src/org/briarproject/invitation/ConnectorGroup.java
@@ -16,7 +16,6 @@ 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;
@@ -58,7 +57,6 @@ class ConnectorGroup extends Thread implements InvitationTask {
 	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;
@@ -85,7 +83,6 @@ class ConnectorGroup extends Thread implements InvitationTask {
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
 			IdentityManager identityManager, ContactManager contactManager,
-			MessagingManager messagingManager,
 			TransportPropertyManager transportPropertyManager, Clock clock,
 			PluginManager pluginManager, AuthorId localAuthorId,
 			int localInvitationCode, int remoteInvitationCode,
@@ -102,7 +99,6 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		this.connectionManager = connectionManager;
 		this.identityManager = identityManager;
 		this.contactManager = contactManager;
-		this.messagingManager = messagingManager;
 		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
@@ -201,8 +197,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
-				messagingManager, transportPropertyManager, clock,
-				reuseConnection, this, plugin, localAuthor, localProps, random);
+				transportPropertyManager, clock, reuseConnection, this, plugin,
+				localAuthor, localProps, random);
 	}
 
 	private Connector createBobConnector(DuplexPlugin plugin,
@@ -213,8 +209,8 @@ class ConnectorGroup extends Thread implements InvitationTask {
 		return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, contactManager,
-				messagingManager, transportPropertyManager, clock,
-				reuseConnection, this, plugin, localAuthor, localProps, random);
+				transportPropertyManager, clock, reuseConnection, this, plugin,
+				localAuthor, localProps, random);
 	}
 
 	public void localConfirmationSucceeded() {
diff --git a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
index 9879f7f5518d33d570a16b3af9c36013abebfb8f..db131b37657976d1a434d2ee80e7e70ecb5d9804 100644
--- a/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
+++ b/briar-core/src/org/briarproject/invitation/InvitationTaskFactoryImpl.java
@@ -9,7 +9,6 @@ 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;
@@ -34,7 +33,6 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 	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;
@@ -47,7 +45,6 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 			AuthorFactory authorFactory, GroupFactory groupFactory,
 			KeyManager keyManager, ConnectionManager connectionManager,
 			IdentityManager identityManager, ContactManager contactManager,
-			MessagingManager messagingManager,
 			TransportPropertyManager transportPropertyManager,
 			Clock clock, PluginManager pluginManager) {
 		this.crypto = crypto;
@@ -61,7 +58,6 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 		this.connectionManager = connectionManager;
 		this.identityManager = identityManager;
 		this.contactManager = contactManager;
-		this.messagingManager = messagingManager;
 		this.transportPropertyManager = transportPropertyManager;
 		this.clock = clock;
 		this.pluginManager = pluginManager;
@@ -72,8 +68,7 @@ class InvitationTaskFactoryImpl implements InvitationTaskFactory {
 		return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
 				streamReaderFactory, streamWriterFactory, authorFactory,
 				groupFactory, keyManager, connectionManager, identityManager,
-				contactManager, messagingManager, transportPropertyManager,
-				clock, pluginManager, localAuthorId, localCode, remoteCode,
-				reuseConnection);
+				contactManager, 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 39ba71647e09b77b985d94a39b9b0b777f1e6a7d..49502aa043ee0be44d7448565d415023fc2e4089 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java
@@ -6,6 +6,8 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.UniqueId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.contact.ContactManager.AddContactHook;
+import org.briarproject.api.contact.ContactManager.RemoveContactHook;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfReader;
 import org.briarproject.api.data.BdfReaderFactory;
@@ -42,7 +44,8 @@ import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 
-class MessagingManagerImpl implements MessagingManager {
+class MessagingManagerImpl implements MessagingManager, AddContactHook,
+		RemoveContactHook {
 
 	static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
 			"6bcdc006c0910b0f44e40644c3b31f1a"
@@ -72,21 +75,20 @@ class MessagingManagerImpl implements MessagingManager {
 	}
 
 	@Override
-	public ClientId getClientId() {
-		return CLIENT_ID;
-	}
-
-	@Override
-	public void addContact(ContactId c) throws DbException {
-		// Create the conversation group
-		Group conversation = createConversationGroup(db.getContact(c));
-		// Subscribe to the group and share it with the contact
-		db.addGroup(conversation);
-		db.addContactGroup(c, conversation);
-		db.setVisibility(conversation.getId(), Collections.singletonList(c));
+	public void addingContact(ContactId c) {
+		try {
+			// Create the conversation group
+			Group g = getConversationGroup(db.getContact(c));
+			// Subscribe to the group and share it with the contact
+			db.addGroup(g);
+			db.addContactGroup(c, g);
+			db.setVisibility(g.getId(), Collections.singletonList(c));
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
 	}
 
-	private Group createConversationGroup(Contact c) {
+	private Group getConversationGroup(Contact c) {
 		AuthorId local = c.getLocalAuthorId();
 		AuthorId remote = c.getAuthor().getId();
 		byte[] descriptor = createGroupDescriptor(local, remote);
@@ -113,6 +115,20 @@ class MessagingManagerImpl implements MessagingManager {
 		return out.toByteArray();
 	}
 
+	@Override
+	public void removingContact(ContactId c) {
+		try {
+			db.removeGroup(getConversationGroup(db.getContact(c)));
+		} catch (DbException e) {
+			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
+		}
+	}
+
+	@Override
+	public ClientId getClientId() {
+		return CLIENT_ID;
+	}
+
 	@Override
 	public void addLocalMessage(PrivateMessage m) throws DbException {
 		BdfDictionary d = new BdfDictionary();
@@ -131,9 +147,9 @@ class MessagingManagerImpl implements MessagingManager {
 
 	@Override
 	public ContactId getContactId(GroupId g) throws DbException {
-		// TODO: Make this more efficient
+		// TODO: Use metadata to attach the contact ID to the group
 		for (Contact c : db.getContacts()) {
-			Group conversation = createConversationGroup(c);
+			Group conversation = getConversationGroup(c);
 			if (conversation.getId().equals(g)) return c.getId();
 		}
 		throw new NoSuchContactException();
@@ -141,7 +157,7 @@ class MessagingManagerImpl implements MessagingManager {
 
 	@Override
 	public GroupId getConversationId(ContactId c) throws DbException {
-		return createConversationGroup(db.getContact(c)).getId();
+		return getConversationGroup(db.getContact(c)).getId();
 	}
 
 	@Override
diff --git a/briar-core/src/org/briarproject/messaging/MessagingModule.java b/briar-core/src/org/briarproject/messaging/MessagingModule.java
index 95a28a3f130c638c3dab0af04dedd470fd8938d5..1854afcc769a0600036ddf9cc7eb0ad8167af0ff 100644
--- a/briar-core/src/org/briarproject/messaging/MessagingModule.java
+++ b/briar-core/src/org/briarproject/messaging/MessagingModule.java
@@ -3,9 +3,9 @@ package org.briarproject.messaging;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
+import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.data.BdfReaderFactory;
 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;
@@ -17,18 +17,26 @@ public class MessagingModule extends AbstractModule {
 
 	@Override
 	protected void configure() {
-		bind(MessagingManager.class).to(MessagingManagerImpl.class);
 		bind(PrivateMessageFactory.class).to(PrivateMessageFactoryImpl.class);
 	}
 
 	@Provides @Singleton
-	PrivateMessageValidator getValidator(LifecycleManager lifecycleManager,
-			ValidationManager validationManager,
+	PrivateMessageValidator getValidator(ValidationManager validationManager,
+			MessagingManager messagingManager,
 			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
 			Clock clock) {
 		PrivateMessageValidator validator = new PrivateMessageValidator(
-				validationManager, bdfReaderFactory, metadataEncoder, clock);
-		lifecycleManager.register(validator);
+				bdfReaderFactory, metadataEncoder, clock);
+		validationManager.setMessageValidator(messagingManager.getClientId(),
+				validator);
 		return validator;
 	}
+
+	@Provides @Singleton
+	MessagingManager getMessagingManager(ContactManager contactManager,
+			MessagingManagerImpl messagingManager) {
+		contactManager.registerAddContactHook(messagingManager);
+		contactManager.registerRemoveContactHook(messagingManager);
+		return messagingManager;
+	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
index a3b4919380799809513fa8ee5b6e0d4572facbbd..c0134934f1283334830f0910ca152eebb7917148 100644
--- a/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
+++ b/briar-core/src/org/briarproject/messaging/PrivateMessageValidator.java
@@ -10,7 +10,6 @@ import org.briarproject.api.db.Metadata;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.MessageValidator;
-import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.system.Clock;
 
 import java.io.ByteArrayInputStream;
@@ -23,39 +22,24 @@ import static org.briarproject.api.messaging.MessagingConstants.MAX_CONTENT_TYPE
 import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
 import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
-import static org.briarproject.messaging.MessagingManagerImpl.CLIENT_ID;
 
 class PrivateMessageValidator implements MessageValidator {
 
 	private static final Logger LOG =
 			Logger.getLogger(PrivateMessageValidator.class.getName());
 
-	private final ValidationManager validationManager;
 	private final BdfReaderFactory bdfReaderFactory;
 	private final MetadataEncoder metadataEncoder;
 	private final Clock clock;
 
 	@Inject
-	PrivateMessageValidator(ValidationManager validationManager,
-			BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
-			Clock clock) {
-		this.validationManager = validationManager;
+	PrivateMessageValidator(BdfReaderFactory bdfReaderFactory,
+			MetadataEncoder metadataEncoder, Clock clock) {
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.metadataEncoder = metadataEncoder;
 		this.clock = clock;
 	}
 
-	@Override
-	public boolean start() {
-		validationManager.setMessageValidator(CLIENT_ID, this);
-		return true;
-	}
-
-	@Override
-	public boolean stop() {
-		return true;
-	}
-
 	@Override
 	public Metadata validateMessage(Message m) {
 		// Reject the message if it's too far in the future
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index abdf1657800d0b496084a3baffb7013fced02aa8..0625b2d2461c69635a7b347d577cad0b76cac0d9 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -10,6 +10,7 @@ import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.TransportDisabledEvent;
 import org.briarproject.api.event.TransportEnabledEvent;
 import org.briarproject.api.lifecycle.IoExecutor;
+import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.plugins.ConnectionManager;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginCallback;
@@ -46,7 +47,7 @@ import javax.inject.Inject;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-class PluginManagerImpl implements PluginManager {
+class PluginManagerImpl implements PluginManager, Service {
 
 	private static final Logger LOG =
 			Logger.getLogger(PluginManagerImpl.class.getName());
@@ -88,6 +89,7 @@ class PluginManagerImpl implements PluginManager {
 		duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
 	}
 
+	@Override
 	public boolean start() {
 		// Instantiate and start the simplex plugins
 		LOG.info("Starting simplex plugins");
@@ -115,6 +117,7 @@ class PluginManagerImpl implements PluginManager {
 		return true;
 	}
 
+	@Override
 	public boolean stop() {
 		// Stop the poller
 		LOG.info("Stopping poller");
diff --git a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
index bbe2674d0a9632ccbdeba9a3e6c8c36912856c57..da217728a3221daacd937b8d617f14a6e275b8fa 100644
--- a/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
+++ b/briar-core/src/org/briarproject/sync/AuthorFactoryImpl.java
@@ -14,6 +14,8 @@ import java.io.IOException;
 
 import javax.inject.Inject;
 
+import static org.briarproject.api.db.StorageStatus.ADDING;
+
 class AuthorFactoryImpl implements AuthorFactory {
 
 	private final CryptoComponent crypto;
@@ -35,7 +37,7 @@ class AuthorFactoryImpl implements AuthorFactory {
 	public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
 			byte[] privateKey) {
 		return new LocalAuthor(getId(name, publicKey), name, publicKey,
-				privateKey, clock.currentTimeMillis());
+				privateKey, clock.currentTimeMillis(), ADDING);
 	}
 
 	private AuthorId getId(String name, byte[] publicKey) {
diff --git a/briar-core/src/org/briarproject/sync/SyncModule.java b/briar-core/src/org/briarproject/sync/SyncModule.java
index cce031fd94941fe09e7059325a3118861052f308..77633c7d2fbea88d2f20bd707db4b3d7b541e14a 100644
--- a/briar-core/src/org/briarproject/sync/SyncModule.java
+++ b/briar-core/src/org/briarproject/sync/SyncModule.java
@@ -4,9 +4,9 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
 import org.briarproject.api.data.ObjectReader;
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
-import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupFactory;
 import org.briarproject.api.sync.MessageFactory;
@@ -48,9 +48,9 @@ public class SyncModule extends AbstractModule {
 	}
 
 	@Provides @Singleton
-	ValidationManager getValidationManager(LifecycleManager lifecycleManager,
+	ValidationManager getValidationManager(EventBus eventBus,
 			ValidationManagerImpl validationManager) {
-		lifecycleManager.register(validationManager);
+		eventBus.addListener(validationManager);
 		return validationManager;
 	}
 }
diff --git a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
index 68040be941b37f2aa0edf7ccef1a59f5ec91fd78..9f977ef15bd076bb80721f7ed2285e3b63cee238 100644
--- a/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
+++ b/briar-core/src/org/briarproject/sync/ValidationManagerImpl.java
@@ -11,7 +11,6 @@ import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.sync.ClientId;
@@ -38,32 +37,18 @@ class ValidationManagerImpl implements ValidationManager, EventListener {
 	private final DatabaseComponent db;
 	private final Executor dbExecutor;
 	private final Executor cryptoExecutor;
-	private final EventBus eventBus;
 	private final Map<ClientId, MessageValidator> validators;
 
 	@Inject
 	ValidationManagerImpl(DatabaseComponent db,
 			@DatabaseExecutor Executor dbExecutor,
-			@CryptoExecutor Executor cryptoExecutor, EventBus eventBus) {
+			@CryptoExecutor Executor cryptoExecutor) {
 		this.db = db;
 		this.dbExecutor = dbExecutor;
 		this.cryptoExecutor = cryptoExecutor;
-		this.eventBus = eventBus;
 		validators = new ConcurrentHashMap<ClientId, MessageValidator>();
 	}
 
-	@Override
-	public boolean start() {
-		eventBus.addListener(this);
-		return true;
-	}
-
-	@Override
-	public boolean stop() {
-		eventBus.removeListener(this);
-		return true;
-	}
-
 	@Override
 	public void setMessageValidator(ClientId c, MessageValidator v) {
 		validators.put(c, v);
diff --git a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
index c36e844d2efddcc5cb25fd42c7e3afced68321a5..9804cab617f39db1b14bc6df07053f1dbe62a078 100644
--- a/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
+++ b/briar-core/src/org/briarproject/transport/KeyManagerImpl.java
@@ -9,10 +9,10 @@ import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.ContactRemovedEvent;
 import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.TransportAddedEvent;
 import org.briarproject.api.event.TransportRemovedEvent;
+import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.system.Clock;
 import org.briarproject.api.system.Timer;
 import org.briarproject.api.transport.KeyManager;
@@ -29,7 +29,7 @@ import javax.inject.Inject;
 
 import static java.util.logging.Level.WARNING;
 
-class KeyManagerImpl implements KeyManager, EventListener {
+class KeyManagerImpl implements KeyManager, Service, EventListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(KeyManagerImpl.class.getName());
@@ -37,26 +37,24 @@ class KeyManagerImpl implements KeyManager, EventListener {
 	private final DatabaseComponent db;
 	private final CryptoComponent crypto;
 	private final ExecutorService dbExecutor;
-	private final EventBus eventBus;
 	private final Timer timer;
 	private final Clock clock;
 	private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
 
 	@Inject
 	KeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
-			@DatabaseExecutor ExecutorService dbExecutor, EventBus eventBus,
-			Timer timer, Clock clock) {
+			@DatabaseExecutor ExecutorService dbExecutor, Timer timer,
+			Clock clock) {
 		this.db = db;
 		this.crypto = crypto;
 		this.dbExecutor = dbExecutor;
-		this.eventBus = eventBus;
 		this.timer = timer;
 		this.clock = clock;
 		managers = new ConcurrentHashMap<TransportId, TransportKeyManager>();
 	}
 
+	@Override
 	public boolean start() {
-		eventBus.addListener(this);
 		try {
 			Map<TransportId, Integer> latencies = db.getTransportLatencies();
 			for (Entry<TransportId, Integer> e : latencies.entrySet())
@@ -68,8 +66,8 @@ class KeyManagerImpl implements KeyManager, EventListener {
 		return true;
 	}
 
+	@Override
 	public boolean stop() {
-		eventBus.removeListener(this);
 		return true;
 	}
 
diff --git a/briar-core/src/org/briarproject/transport/TransportModule.java b/briar-core/src/org/briarproject/transport/TransportModule.java
index 36a7b3e3e7cb17dd5689006ea2fd1e4013ec18ae..367340a4cc3113cad2bd89f133a4dd2a7f71bca2 100644
--- a/briar-core/src/org/briarproject/transport/TransportModule.java
+++ b/briar-core/src/org/briarproject/transport/TransportModule.java
@@ -3,6 +3,7 @@ package org.briarproject.transport;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
+import org.briarproject.api.event.EventBus;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.transport.KeyManager;
 import org.briarproject.api.transport.StreamReaderFactory;
@@ -20,8 +21,9 @@ public class TransportModule extends AbstractModule {
 
 	@Provides @Singleton
 	KeyManager getKeyManager(LifecycleManager lifecycleManager,
-			KeyManagerImpl keyManager) {
+			EventBus eventBus, KeyManagerImpl keyManager) {
 		lifecycleManager.register(keyManager);
+		eventBus.addListener(keyManager);
 		return keyManager;
 	}
 }
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index a6bd31ab33e861eda3e7a2803a8fa01b9126a80c..b4ff178a566555b8deee33b651d16d7233c7a507 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -2,7 +2,6 @@ package org.briarproject.db;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestUtils;
-import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.Contact;
@@ -16,11 +15,8 @@ import org.briarproject.api.db.NoSuchLocalAuthorException;
 import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.db.NoSuchSubscriptionException;
 import org.briarproject.api.db.NoSuchTransportException;
-import org.briarproject.api.event.ContactAddedEvent;
-import org.briarproject.api.event.ContactRemovedEvent;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.LocalAuthorAddedEvent;
-import org.briarproject.api.event.LocalAuthorRemovedEvent;
 import org.briarproject.api.event.LocalSubscriptionsUpdatedEvent;
 import org.briarproject.api.event.LocalTransportsUpdatedEvent;
 import org.briarproject.api.event.MessageAddedEvent;
@@ -98,11 +94,12 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 		authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
+		long timestamp = System.currentTimeMillis();
 		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				StorageStatus.ACTIVE);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
-		long timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		message = new Message(messageId, groupId, timestamp, raw);
@@ -113,7 +110,8 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 				"bar", "baz"));
 		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, localAuthorId);
+		contact = new Contact(contactId, author, localAuthorId,
+				StorageStatus.ACTIVE);
 	}
 
 	private <T> DatabaseComponent createDatabaseComponent(Database<T> database,
@@ -142,7 +140,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).containsContact(txn, authorId);
 			will(returnValue(false));
@@ -150,7 +147,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			will(returnValue(true));
 			oneOf(database).addContact(txn, author, localAuthorId);
 			will(returnValue(contactId));
-			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			// getContacts()
 			oneOf(database).getContacts(txn);
 			will(returnValue(Collections.singletonList(contact)));
@@ -183,14 +179,10 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			oneOf(database).containsContact(txn, contactId);
 			will(returnValue(true));
 			oneOf(database).removeContact(txn, contactId);
-			oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
 			// removeLocalAuthor()
 			oneOf(database).containsLocalAuthor(txn, localAuthorId);
 			will(returnValue(true));
-			oneOf(database).getContacts(txn, localAuthorId);
-			will(returnValue(Collections.emptyList()));
 			oneOf(database).removeLocalAuthor(txn, localAuthorId);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
 			// close()
 			oneOf(shutdown).removeShutdownHook(shutdownHandle);
 			oneOf(database).close();
@@ -660,7 +652,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
 			oneOf(database).commitTransaction(txn);
-			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
@@ -671,7 +662,6 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 			oneOf(database).addContact(txn, author, localAuthorId);
 			will(returnValue(contactId));
 			oneOf(database).commitTransaction(txn);
-			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			// Check whether the transport is in the DB (which it's not)
 			exactly(6).of(database).startTransaction();
 			will(returnValue(txn));
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index f034e3f4be0165ea7d47cce36fd08af602427076..c0cde9e40dab1358eef222487e6327dd743ae689 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -3,12 +3,14 @@ package org.briarproject.db;
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestDatabaseConfig;
 import org.briarproject.TestUtils;
+import org.briarproject.api.Settings;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.LocalAuthor;
@@ -21,7 +23,6 @@ import org.briarproject.api.sync.MessageStatus;
 import org.briarproject.api.transport.IncomingKeys;
 import org.briarproject.api.transport.OutgoingKeys;
 import org.briarproject.api.transport.TransportKeys;
-import org.briarproject.api.Settings;
 import org.briarproject.system.SystemClock;
 import org.junit.After;
 import org.junit.Before;
@@ -80,10 +81,11 @@ public class H2DatabaseTest extends BriarTestCase {
 		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
 		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
+		timestamp = System.currentTimeMillis();
 		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], 1234);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				StorageStatus.ACTIVE);
 		messageId = new MessageId(TestUtils.getRandomId());
-		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		random.nextBytes(raw);
@@ -94,7 +96,7 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	@Before
 	public void setUp() {
-		testDir.mkdirs();
+		assertTrue(testDir.mkdirs());
 	}
 
 	@Test
diff --git a/briar-tests/src/org/briarproject/sync/ConstantsTest.java b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
index 6b92a026952a8de35dfa2b8421df2e98e665dc62..a67afd361459b5c2595134332aa5ce27e03016e2 100644
--- a/briar-tests/src/org/briarproject/sync/ConstantsTest.java
+++ b/briar-tests/src/org/briarproject/sync/ConstantsTest.java
@@ -35,11 +35,13 @@ import org.briarproject.api.sync.PacketWriterFactory;
 import org.briarproject.api.sync.Request;
 import org.briarproject.api.sync.SubscriptionUpdate;
 import org.briarproject.api.sync.TransportUpdate;
+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.forum.ForumModule;
+import org.briarproject.identity.IdentityModule;
 import org.briarproject.messaging.MessagingModule;
 import org.junit.Test;
 
@@ -63,6 +65,8 @@ import static org.junit.Assert.assertTrue;
 
 public class ConstantsTest extends BriarTestCase {
 
+	// TODO: Break this up into tests that are relevant for each package
+
 	private final CryptoComponent crypto;
 	private final GroupFactory groupFactory;
 	private final AuthorFactory authorFactory;
@@ -73,9 +77,9 @@ public class ConstantsTest extends BriarTestCase {
 	public ConstantsTest() throws Exception {
 		Injector i = Guice.createInjector(new TestDatabaseModule(),
 				new TestLifecycleModule(), new TestSystemModule(),
-				new CryptoModule(), new DatabaseModule(), new DataModule(),
-				new EventModule(), new ForumModule(), new MessagingModule(),
-				new SyncModule());
+				new ContactModule(), new CryptoModule(), new DatabaseModule(),
+				new DataModule(), new EventModule(), new ForumModule(),
+				new IdentityModule(), new MessagingModule(), new SyncModule());
 		crypto = i.getInstance(CryptoComponent.class);
 		groupFactory = i.getInstance(GroupFactory.class);
 		authorFactory = i.getInstance(AuthorFactory.class);
diff --git a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
index 3a7be3b74b4254f3299a1bbb75a2cd878f3fdf02..198ac61a33ac967be0fe4f6730f430b7e7b35263 100644
--- a/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/sync/SimplexMessagingIntegrationTest.java
@@ -5,7 +5,6 @@ import com.google.inject.Injector;
 
 import org.briarproject.BriarTestCase;
 import org.briarproject.TestDatabaseModule;
-import org.briarproject.TestLifecycleModule;
 import org.briarproject.TestSystemModule;
 import org.briarproject.TestUtils;
 import org.briarproject.api.TransportId;
@@ -13,6 +12,7 @@ import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.StorageStatus;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventBus;
 import org.briarproject.api.event.EventListener;
@@ -21,6 +21,7 @@ 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.lifecycle.LifecycleManager;
 import org.briarproject.api.messaging.MessagingManager;
 import org.briarproject.api.messaging.PrivateMessage;
 import org.briarproject.api.messaging.PrivateMessageFactory;
@@ -40,6 +41,7 @@ import org.briarproject.data.DataModule;
 import org.briarproject.db.DatabaseModule;
 import org.briarproject.event.EventModule;
 import org.briarproject.identity.IdentityModule;
+import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.messaging.MessagingModule;
 import org.briarproject.plugins.ImmediateExecutor;
 import org.briarproject.transport.TransportModule;
@@ -78,17 +80,17 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	@Before
 	public void setUp() {
-		testDir.mkdirs();
+		assertTrue(testDir.mkdirs());
 		alice = createInjector(aliceDir);
 		bob = createInjector(bobDir);
 	}
 
 	private Injector createInjector(File dir) {
 		return Guice.createInjector(new TestDatabaseModule(dir),
-				new TestLifecycleModule(), new TestSystemModule(),
-				new ContactModule(), new CryptoModule(), new DatabaseModule(),
-				new DataModule(), new EventModule(), new IdentityModule(),
-				new SyncModule(), new MessagingModule(), new TransportModule());
+				new TestSystemModule(), new ContactModule(), new CryptoModule(),
+				new DatabaseModule(), new DataModule(), new EventModule(),
+				new IdentityModule(), new LifecycleModule(),
+				new MessagingModule(), new SyncModule(), new TransportModule());
 	}
 
 	@Test
@@ -97,35 +99,43 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	}
 
 	private byte[] write() throws Exception {
-		// Open Alice's database
+		// Instantiate Alice's services
+		LifecycleManager lifecycleManager =
+				alice.getInstance(LifecycleManager.class);
 		DatabaseComponent db = alice.getInstance(DatabaseComponent.class);
-		assertFalse(db.open());
-		// Add the transport
-		db.addTransport(transportId, MAX_LATENCY);
-		// Start Alice's key manager
+		IdentityManager identityManager =
+				alice.getInstance(IdentityManager.class);
+		ContactManager contactManager = alice.getInstance(ContactManager.class);
+		MessagingManager messagingManager =
+				alice.getInstance(MessagingManager.class);
 		KeyManager keyManager = alice.getInstance(KeyManager.class);
-		keyManager.start();
+		PrivateMessageFactory privateMessageFactory =
+				alice.getInstance(PrivateMessageFactory.class);
+		PacketWriterFactory packetWriterFactory =
+				alice.getInstance(PacketWriterFactory.class);
+		EventBus eventBus = alice.getInstance(EventBus.class);
+		StreamWriterFactory streamWriterFactory =
+				alice.getInstance(StreamWriterFactory.class);
+
+		// Start the lifecycle manager
+		lifecycleManager.startServices();
+		lifecycleManager.waitForStartup();
+		// Add a transport
+		db.addTransport(transportId, MAX_LATENCY);
 		// Add an identity for Alice
 		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], timestamp);
-		IdentityManager identityManager =
-				alice.getInstance(IdentityManager.class);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				StorageStatus.ADDING);
 		identityManager.addLocalAuthor(aliceAuthor);
 		// Add Bob as a contact
 		Author bobAuthor = new Author(bobId, "Bob",
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		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);
 		// Derive and store the transport keys
 		keyManager.addContact(contactId, Collections.singletonList(transportId),
 				master, timestamp, true);
+
 		// Send Bob a message
-		PrivateMessageFactory privateMessageFactory =
-				alice.getInstance(PrivateMessageFactory.class);
 		GroupId groupId = messagingManager.getConversationId(contactId);
 		byte[] body = "Hi Bob!".getBytes("UTF-8");
 		PrivateMessage message = privateMessageFactory.createPrivateMessage(
@@ -136,14 +146,9 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		assertNotNull(ctx);
 		// Create a stream writer
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		StreamWriterFactory streamWriterFactory =
-				alice.getInstance(StreamWriterFactory.class);
-		OutputStream streamWriter =
-				streamWriterFactory.createStreamWriter(out, ctx);
+		OutputStream streamWriter = streamWriterFactory.createStreamWriter(
+				out, ctx);
 		// Create an outgoing sync session
-		EventBus eventBus = alice.getInstance(EventBus.class);
-		PacketWriterFactory packetWriterFactory =
-				alice.getInstance(PacketWriterFactory.class);
 		PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
 				streamWriter);
 		SyncSession session = new SimplexOutgoingSession(db,
@@ -152,40 +157,50 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Write whatever needs to be written
 		session.run();
 		streamWriter.close();
+
 		// Clean up
-		keyManager.stop();
-		db.close();
+		lifecycleManager.stopServices();
+		lifecycleManager.waitForShutdown();
+
 		// Return the contents of the stream
 		return out.toByteArray();
 	}
 
 	private void read(byte[] stream) throws Exception {
-		// Open Bob's database
+		// Instantiate Bob's services
+		LifecycleManager lifecycleManager =
+				bob.getInstance(LifecycleManager.class);
 		DatabaseComponent db = bob.getInstance(DatabaseComponent.class);
-		assertFalse(db.open());
-		// Add the transport
-		db.addTransport(transportId, MAX_LATENCY);
-		// Start Bob's key manager
+		IdentityManager identityManager =
+				bob.getInstance(IdentityManager.class);
+		ContactManager contactManager = bob.getInstance(ContactManager.class);
 		KeyManager keyManager = bob.getInstance(KeyManager.class);
-		keyManager.start();
+		StreamReaderFactory streamReaderFactory =
+				bob.getInstance(StreamReaderFactory.class);
+		PacketReaderFactory packetReaderFactory =
+				bob.getInstance(PacketReaderFactory.class);
+		EventBus eventBus = bob.getInstance(EventBus.class);
+		// Bob needs a MessagingManager even though we're not using it directly
+		bob.getInstance(MessagingManager.class);
+
+		// Start the lifecyle manager
+		lifecycleManager.startServices();
+		lifecycleManager.waitForStartup();
+		// Add a transport
+		db.addTransport(transportId, MAX_LATENCY);
 		// Add an identity for Bob
 		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100], timestamp);
-		IdentityManager identityManager =
-				bob.getInstance(IdentityManager.class);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
+				StorageStatus.ADDING);
 		identityManager.addLocalAuthor(bobAuthor);
 		// Add Alice as a contact
 		Author aliceAuthor = new Author(aliceId, "Alice",
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		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);
 		// Derive and store the transport 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);
@@ -197,19 +212,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		StreamContext ctx = keyManager.getStreamContext(transportId, tag);
 		assertNotNull(ctx);
 		// Create a stream reader
-		StreamReaderFactory streamReaderFactory =
-				bob.getInstance(StreamReaderFactory.class);
-		InputStream streamReader =
-				streamReaderFactory.createStreamReader(in, ctx);
+		InputStream streamReader = streamReaderFactory.createStreamReader(
+				in, ctx);
 		// Create an incoming sync session
-		EventBus eventBus = bob.getInstance(EventBus.class);
-		PacketReaderFactory packetReaderFactory =
-				bob.getInstance(PacketReaderFactory.class);
 		PacketReader packetReader = packetReaderFactory.createPacketReader(
 				streamReader);
-		SyncSession session = new IncomingSession(db,
-				new ImmediateExecutor(), eventBus, contactId, transportId,
-				packetReader);
+		SyncSession session = new IncomingSession(db, new ImmediateExecutor(),
+				eventBus, contactId, transportId, packetReader);
 		// No messages should have been added yet
 		assertFalse(listener.messageAdded);
 		// Read whatever needs to be read
@@ -217,9 +226,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		streamReader.close();
 		// The private message from Alice should have been added
 		assertTrue(listener.messageAdded);
+
 		// Clean up
-		keyManager.stop();
-		db.close();
+		lifecycleManager.stopServices();
+		lifecycleManager.waitForShutdown();
 	}
 
 	@After