diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..76ece209544803cc1568d07bd7ffd856abd8f565
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractIntroduceeSession.java
@@ -0,0 +1,157 @@
+package org.briarproject.briar.mailbox;
+
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+import org.briarproject.bramble.api.plugin.TransportId;
+import org.briarproject.bramble.api.properties.TransportProperties;
+import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.bramble.api.transport.KeySetId;
+import org.briarproject.briar.api.client.SessionId;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+@NotNullByDefault
+abstract class AbstractIntroduceeSession extends Session<IntroduceeState>
+		implements PeerSession {
+
+	final GroupId contactGroupId;
+	final Author introducer;
+	final Local local;
+	final Remote remote;
+	@Nullable
+	final byte[] masterKey;
+	@Nullable
+	final Map<TransportId, KeySetId> transportKeys;
+
+	AbstractIntroduceeSession(SessionId sessionId, IntroduceeState state,
+			long requestTimestamp, GroupId contactGroupId, Author introducer,
+			Local local, Remote remote, @Nullable byte[] masterKey,
+			@Nullable Map<TransportId, KeySetId> transportKeys,
+			long sessionCounter) {
+		super(sessionId, state, requestTimestamp, sessionCounter);
+		this.contactGroupId = contactGroupId;
+		this.introducer = introducer;
+		this.local = local;
+		this.remote = remote;
+		this.masterKey = masterKey;
+		this.transportKeys = transportKeys;
+	}
+
+	@Override
+	public GroupId getContactGroupId() {
+		return contactGroupId;
+	}
+
+	@Override
+	public long getLocalTimestamp() {
+		return local.lastMessageTimestamp;
+	}
+
+	@Nullable
+	@Override
+	public MessageId getLastLocalMessageId() {
+		return local.lastMessageId;
+	}
+
+	@Nullable
+	@Override
+	public MessageId getLastRemoteMessageId() {
+		return remote.lastMessageId;
+	}
+
+	Author getIntroducer() {
+		return introducer;
+	}
+
+	public Local getLocal() {
+		return local;
+	}
+
+	public Remote getRemote() {
+		return remote;
+	}
+
+	@Nullable
+	byte[] getMasterKey() {
+		return masterKey;
+	}
+
+	@Nullable
+	Map<TransportId, KeySetId> getTransportKeys() {
+		return transportKeys;
+	}
+
+	abstract static class Common {
+		final boolean alice;
+		@Nullable
+		final MessageId lastMessageId;
+		@Nullable
+		final byte[] ephemeralPublicKey;
+
+		final long acceptTimestamp;
+		@Nullable
+		final byte[] macKey;
+
+		private Common(boolean alice, @Nullable MessageId lastMessageId,
+				@Nullable byte[] ephemeralPublicKey, @Nullable
+				long acceptTimestamp, @Nullable byte[] macKey) {
+			this.alice = alice;
+			this.lastMessageId = lastMessageId;
+			this.ephemeralPublicKey = ephemeralPublicKey;
+			this.acceptTimestamp = acceptTimestamp;
+			this.macKey = macKey;
+		}
+	}
+
+	static class Local extends Common {
+		final long lastMessageTimestamp;
+		@Nullable
+		final byte[] ephemeralPrivateKey;
+
+		Local(boolean alice, @Nullable MessageId lastMessageId,
+				long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey,
+				@Nullable byte[] ephemeralPrivateKey, long acceptTimestamp,
+				@Nullable byte[] macKey) {
+			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
+					macKey);
+			this.lastMessageTimestamp = lastMessageTimestamp;
+			this.ephemeralPrivateKey = ephemeralPrivateKey;
+		}
+
+		Local(Local s, @Nullable MessageId lastMessageId,
+				long lastMessageTimestamp) {
+			this(s.alice, lastMessageId, lastMessageTimestamp,
+					s.ephemeralPublicKey, s.ephemeralPrivateKey,
+					s.acceptTimestamp, s.macKey);
+		}
+	}
+
+	static class Remote extends Common {
+		final Author author;
+		@Nullable
+		final Map<TransportId, TransportProperties> transportProperties;
+
+		Remote(boolean alice, Author author,
+				@Nullable MessageId lastMessageId,
+				@Nullable byte[] ephemeralPublicKey, @Nullable
+				Map<TransportId, TransportProperties> transportProperties,
+				long acceptTimestamp, @Nullable byte[] macKey) {
+			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
+					macKey);
+			this.author = author;
+			this.transportProperties = transportProperties;
+		}
+
+		Remote(Remote s, @Nullable MessageId lastMessageId) {
+			this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
+					s.transportProperties, s.acceptTimestamp, s.macKey);
+		}
+
+	}
+
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
index 25bc7337151a837067028b2e54cd8e43359019d0..5397d7b7e5e6d21af504c736aff6ebefa2e8f203 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/AbstractProtocolEngine.java
@@ -21,8 +21,9 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
 import static org.briarproject.briar.mailbox.MessageType.ABORT;
-import static org.briarproject.briar.mailbox.MessageType.CONTACT_REQUEST;
 import static org.briarproject.briar.mailbox.MessageType.DECLINE;
+import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_ACCEPT;
+import static org.briarproject.briar.mailbox.MessageType.INTRODUCEE_REQUEST;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_ACCEPT;
 import static org.briarproject.briar.mailbox.MessageType.MAILBOX_REQUEST;
 
@@ -73,24 +74,45 @@ abstract class AbstractProtocolEngine<S extends Session>
 	}
 
 	Message sendMailboxAcceptMessage(Transaction txn, PeerSession s,
-			long timestamp, byte[] ephemeralPublicKey, long acceptTimestamp,
+			long timestamp,
+			byte[] ephemeralPublicKey, long acceptTimestamp,
 			long messageCounter) throws DbException {
 		Message m = messageEncoder
-				.encodeMailboxAcceptMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId(),
-						ephemeralPublicKey, acceptTimestamp, messageCounter);
-		sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m, messageCounter);
+				.encodeIntroduceeRequestMessage(s.getContactGroupId(),
+						timestamp, s.getLastLocalMessageId(), s.getSessionId(),
+						ephemeralPublicKey,
+						acceptTimestamp,
+						messageCounter);
+		sendMessage(txn, MAILBOX_ACCEPT, s.getSessionId(), m,
+				messageCounter);
 		return m;
 	}
 
-	Message sendContactRequestMessage(Transaction txn, PeerSession s,
-			long timestamp, Author author, Author introduceeAuthor,
+	Message sendIntroduceeRequestMessage(Transaction txn, PeerSession s,
+			long timestamp,
+			byte[] ephemeralPublicKey, long acceptTimestamp,
 			long messageCounter) throws DbException {
 		Message m = messageEncoder
-				.encodeRequestMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), introduceeAuthor,
+				.encodeIntroduceeRequestMessage(s.getContactGroupId(),
+						timestamp, s.getLastLocalMessageId(), s.getSessionId(),
+						ephemeralPublicKey,
+						acceptTimestamp,
 						messageCounter);
-		sendMessage(txn, CONTACT_REQUEST, s.getSessionId(), m, 0);
+		sendMessage(txn, INTRODUCEE_REQUEST, s.getSessionId(), m,
+				messageCounter);
+		return m;
+	}
+
+	Message sendIntroduceeResponseMessage(Transaction txn, PeerSession s,
+			long timestamp, byte[] ephemeralPublicKey, byte[] mac,
+			byte[] signature, long messageCounter) throws DbException {
+		Message m = messageEncoder
+				.encodeIntroduceeAcceptMessage(s.getContactGroupId(),
+						timestamp,
+						s.getLastLocalMessageId(), s.getSessionId(),
+						ephemeralPublicKey, mac, signature, messageCounter);
+		sendMessage(txn, INTRODUCEE_ACCEPT, s.getSessionId(), m,
+				messageCounter);
 		return m;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ac793092687577d4d87d15ce5f3d57a298e6d50
--- /dev/null
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeProtocolEngine.java
@@ -0,0 +1,213 @@
+package org.briarproject.briar.mailbox;
+
+import org.briarproject.bramble.api.FormatException;
+import org.briarproject.bramble.api.client.ClientHelper;
+import org.briarproject.bramble.api.client.ContactGroupFactory;
+import org.briarproject.bramble.api.contact.ContactManager;
+import org.briarproject.bramble.api.crypto.KeyPair;
+import org.briarproject.bramble.api.crypto.SecretKey;
+import org.briarproject.bramble.api.data.BdfDictionary;
+import org.briarproject.bramble.api.db.DatabaseComponent;
+import org.briarproject.bramble.api.db.DbException;
+import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.IdentityManager;
+import org.briarproject.bramble.api.identity.LocalAuthor;
+import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageId;
+import org.briarproject.bramble.api.system.Clock;
+import org.briarproject.briar.api.client.MessageTracker;
+import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.api.mailbox.event.MailboxIntroductionRequestReceivedEvent;
+
+import java.security.GeneralSecurityException;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.util.LogUtils.logException;
+import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_AUTH;
+
+class IntroduceeProtocolEngine
+		extends AbstractProtocolEngine<IntroduceeSession> {
+
+	private final static Logger LOG =
+			Logger.getLogger(IntroduceeProtocolEngine.class.getName());
+
+	@Inject
+	IntroduceeProtocolEngine(DatabaseComponent db, ClientHelper clientHelper,
+			ContactManager contactManager,
+			ContactGroupFactory contactGroupFactory,
+			MessageTracker messageTracker, IdentityManager identityManager,
+			MailboxMessageParser messageParser,
+			MailboxMessageEncoder messageEncoder, Clock clock,
+			MailboxIntroductionCrypto crypto) {
+		super(db, clientHelper, contactManager, contactGroupFactory,
+				messageTracker, identityManager, messageParser, messageEncoder,
+				clock, crypto);
+	}
+
+	@Override
+	public IntroduceeSession onRequestAction(Transaction txn,
+			IntroduceeSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onAcceptAction(Transaction txn,
+			IntroduceeSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onDeclineAction(Transaction txn,
+			IntroduceeSession session, long timestamp) throws DbException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onRequestMessage(Transaction txn,
+			IntroduceeSession session, RequestMessage m)
+			throws DbException, FormatException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IntroduceeSession onMailboxAcceptMessage(Transaction txn,
+			IntroduceeSession session, IntroduceeRequestMessage m)
+			throws DbException, FormatException {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public IntroduceeSession onIntroduceeRequestMessage(Transaction txn,
+			IntroduceeSession session, IntroduceeRequestMessage m)
+			throws DbException, FormatException {
+		switch (session.getState()) {
+			case START:
+				return onRemoteRequest(txn, session, m);
+			case AWAIT_LOCAL_RESPONSE:
+			case LOCAL_DECLINED:
+			case LOCAL_ACCEPTED:
+			case AWAIT_REMOTE_RESPONSE:
+			case MAILBOX_ADDED:
+				return abort(txn, session);
+			default:
+				throw new AssertionError();
+		}
+	}
+
+	private IntroduceeSession onRemoteRequest(Transaction txn,
+			IntroduceeSession s,
+			IntroduceeRequestMessage m) throws DbException {
+		// The dependency, if any, must be the last remote message
+		if (isInvalidDependency(s, m.getPreviousMessageId()))
+			return abort(txn, s);
+
+		// Add SessionId to message metadata
+		addSessionId(txn, m.getMessageId(), s.getSessionId());
+
+		// Broadcast IntroductionRequestReceivedEvent
+		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
+		txn.attach(new MailboxIntroductionRequestReceivedEvent(
+				localAuthor.getId()));
+
+		// Create ephemeral key pair and get local transport properties
+		KeyPair keyPair = crypto.generateKeyPair();
+		byte[] publicKey = keyPair.getPublic().getEncoded();
+		byte[] privateKey = keyPair.getPrivate().getEncoded();
+		long localTimestamp = clock.currentTimeMillis();
+		try {
+			SecretKey secretKey = crypto.deriveMasterKey(publicKey, privateKey,
+					m.getEphemeralPublicKey());
+			SecretKey aliceMacKey = crypto.deriveMacKey(secretKey, false);
+			SecretKey bobMacKey = crypto.deriveMacKey(secretKey, false);
+			byte[] mac = crypto.authMac(bobMacKey, s, localAuthor.getId());
+			byte[] signature =
+					crypto.sign(bobMacKey, localAuthor.getPrivateKey());
+			// Send ephemeral public key and timestamp back
+			Message reply =
+					sendIntroduceeResponseMessage(txn, s, localTimestamp,
+							publicKey, mac, signature, s.getMessageCounter());
+			//TODO: Check for reasons to decline and if any, move to LOCAL_DECLINE
+			// Move to the AWAIT_REMOTE_RESPONSE state
+			return IntroduceeSession
+					.addLocalAuth(s, AWAIT_AUTH, reply, secretKey, aliceMacKey,
+							bobMacKey,
+							localTimestamp);
+		} catch (GeneralSecurityException e) {
+			logException(LOG, WARNING, e);
+			return abort(txn, s);
+		}
+	}
+
+	private IntroduceeSession abort(Transaction txn, IntroduceeSession s)
+			throws DbException {
+		/*
+		// Mark the request message unavailable to answer
+		markRequestsUnavailableToAnswer(txn, s);
+
+		// Send an ABORT message
+		Message sent = sendAbortMessage(txn, s, getLocalTimestamp(s));
+
+		// Broadcast abort event for testing
+		txn.attach(new IntroductionAbortedEvent(s.getSessionId()));
+
+		// Reset the session back to initial state
+		return IntroduceeSession.clear(s, START, sent.getId(),
+				sent.getTimestamp(), s.getLastRemoteMessageId());
+				*/
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onContactAcceptMessage(Transaction txn,
+			IntroduceeSession session, IntroduceeRequestMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onDeclineMessage(Transaction txn,
+			IntroduceeSession session, DeclineMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onAuthMessage(Transaction txn,
+			IntroduceeSession session,
+			MailboxAuthMessage m) throws DbException, FormatException {
+		return null;
+	}
+
+	@Override
+	public IntroduceeSession onAbortMessage(Transaction txn,
+			IntroduceeSession session, AbortMessage m)
+			throws DbException, FormatException {
+		return null;
+	}
+
+
+	private boolean isInvalidDependency(AbstractIntroduceeSession s,
+			@Nullable MessageId dependency) {
+		return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
+	}
+
+	private long getLocalTimestamp(AbstractIntroduceeSession s) {
+		return getLocalTimestamp(s.getLocalTimestamp(),
+				s.getRequestTimestamp());
+	}
+
+	private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)
+			throws DbException {
+		BdfDictionary meta = new BdfDictionary();
+		messageEncoder.addSessionId(meta, sessionId);
+		try {
+			clientHelper.mergeMessageMetadata(txn, m, meta);
+		} catch (FormatException e) {
+			throw new AssertionError(e);
+		}
+	}
+}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java
similarity index 87%
rename from briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java
rename to briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java
index 6b740de8282dd027020b6bd5e21b874213c56ed0..88146af1d39b28c0711c338b50450d32104f6d09 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxAcceptMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeRequestMessage.java
@@ -10,13 +10,13 @@ import javax.annotation.concurrent.Immutable;
 
 @Immutable
 @NotNullByDefault
-class MailboxAcceptMessage extends AbstractMailboxIntroductionMessage {
+class IntroduceeRequestMessage extends AbstractMailboxIntroductionMessage {
 
 	private final SessionId sessionId;
 	private final byte[] ephemeralPublicKey;
 	private final long acceptTimestamp;
 
-	protected MailboxAcceptMessage(MessageId messageId, GroupId groupId,
+	protected IntroduceeRequestMessage(MessageId messageId, GroupId groupId,
 			long timestamp, @Nullable MessageId previousMessageId,
 			SessionId sessionId, byte[] ephemeralPublicKey,
 			long acceptTimestamp) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
index 0ae5cf1f1d0edfbedac8d14c4b64c765e40fdfcc..825ada662de4e3aa730271e793c7bde89bf5b63c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeSession.java
@@ -1,45 +1,108 @@
 package org.briarproject.briar.mailbox;
 
+import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.transport.KeySetId;
 import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.api.mailbox.Role;
 
 import java.util.Map;
 
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE;
+import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE;
+import static org.briarproject.briar.mailbox.IntroduceeState.START;
+
 @Immutable
 @NotNullByDefault
-abstract class IntroduceeSession extends Session<IntroduceeState>
-		implements PeerSession {
+class IntroduceeSession extends AbstractIntroduceeSession {
 
-	final GroupId contactGroupId;
-	final Author introducer;
-	final Local local;
-	final Remote remote;
-	@Nullable
-	final byte[] masterKey;
-	@Nullable
-	final Map<TransportId, KeySetId> transportKeys;
 
 	IntroduceeSession(SessionId sessionId, IntroduceeState state,
 			long requestTimestamp, GroupId contactGroupId, Author introducer,
 			Local local, Remote remote, @Nullable byte[] masterKey,
 			@Nullable Map<TransportId, KeySetId> transportKeys,
 			long sessionCounter) {
-		super(sessionId, state, requestTimestamp, sessionCounter);
-		this.contactGroupId = contactGroupId;
-		this.introducer = introducer;
-		this.local = local;
-		this.remote = remote;
-		this.masterKey = masterKey;
-		this.transportKeys = transportKeys;
+		super(sessionId, state, requestTimestamp, contactGroupId, introducer,
+				local, remote, masterKey, transportKeys, sessionCounter);
+	}
+
+	static IntroduceeSession getInitial(GroupId contactGroupId,
+			SessionId sessionId, Author introducer, boolean localIsAlice,
+			Author remoteAuthor) {
+		Local local = new Local(localIsAlice, null, -1, null, null, -1, null);
+		Remote remote =
+				new Remote(!localIsAlice, remoteAuthor, null, null, null, -1,
+						null);
+		return new IntroduceeSession(sessionId, START, -1, contactGroupId,
+				introducer, local, remote, null, null, 0);
+	}
+
+	static IntroduceeSession addLocalAccept(IntroduceeSession s,
+			IntroduceeState state, Message m, byte[] ephemeralPublicKey,
+			byte[] ephemeralPrivateKey, long acceptTimestamp) {
+		Local local = new Local(false, m.getId(), m.getTimestamp(),
+				ephemeralPublicKey, ephemeralPrivateKey, acceptTimestamp, null);
+		return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(),
+				s.contactGroupId, s.introducer, local, s.remote, s.masterKey,
+				s.transportKeys, s.getMessageCounter() + 1);
+	}
+
+	static IntroduceeSession addLocalAuth(IntroduceeSession s,
+			IntroduceeState state,
+			Message m, SecretKey masterKey,
+			SecretKey aliceMacKey,
+			SecretKey bobMacKey, long acceptTimestamp) {
+		// add mac key and sent message
+		Local local = new Local(false, m.getId(), m.getTimestamp(),
+				null, null,
+				acceptTimestamp, bobMacKey.getBytes());
+		// just add the mac key
+		Remote remote = new Remote(true, s.remote.author,
+				s.remote.lastMessageId, null, null,
+				s.remote.acceptTimestamp,
+				aliceMacKey.getBytes());
+		// add master key
+		return new IntroduceeSession(s.getSessionId(), state,
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, masterKey.getBytes(), s.transportKeys,
+				s.getMessageCounter() + 1);
+	}
+
+	static IntroduceeSession awaitAuth(IntroduceeSession s,
+			MailboxAuthMessage m,
+			Message sent, @Nullable Map<TransportId, KeySetId> transportKeys) {
+		Local local = new Local(s.local, sent.getId(), sent.getTimestamp());
+		Remote remote = new Remote(s.remote, m.getMessageId());
+		return new IntroduceeSession(s.getSessionId(), AWAIT_REMOTE_RESPONSE,
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, null, transportKeys, s.getMessageCounter());
+	}
+
+	static IntroduceeSession clear(IntroduceeSession s, IntroduceeState state,
+			@Nullable MessageId lastLocalMessageId, long localTimestamp,
+			@Nullable MessageId lastRemoteMessageId) {
+		Local local =
+				new Local(s.local.alice, lastLocalMessageId, localTimestamp,
+						null, null, -1, null);
+		Remote remote =
+				new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId,
+						null, null, -1, null);
+		return new IntroduceeSession(s.getSessionId(), state,
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, null, null, s.getMessageCounter());
+	}
+
+	@Override
+	Role getRole() {
+		return INTRODUCEE;
 	}
 
 	@Override
@@ -86,72 +149,4 @@ abstract class IntroduceeSession extends Session<IntroduceeState>
 		return transportKeys;
 	}
 
-	abstract static class Common {
-		final boolean alice;
-		@Nullable
-		final MessageId lastMessageId;
-		@Nullable
-		final byte[] ephemeralPublicKey;
-
-		final long acceptTimestamp;
-		@Nullable
-		final byte[] macKey;
-
-		private Common(boolean alice, @Nullable MessageId lastMessageId,
-				@Nullable byte[] ephemeralPublicKey, @Nullable
-				long acceptTimestamp, @Nullable byte[] macKey) {
-			this.alice = alice;
-			this.lastMessageId = lastMessageId;
-			this.ephemeralPublicKey = ephemeralPublicKey;
-			this.acceptTimestamp = acceptTimestamp;
-			this.macKey = macKey;
-		}
-	}
-
-	static class Local extends Common {
-		final long lastMessageTimestamp;
-		@Nullable
-		final byte[] ephemeralPrivateKey;
-
-		Local(boolean alice, @Nullable MessageId lastMessageId,
-				long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey,
-				@Nullable byte[] ephemeralPrivateKey, long acceptTimestamp,
-				@Nullable byte[] macKey) {
-			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
-					macKey);
-			this.lastMessageTimestamp = lastMessageTimestamp;
-			this.ephemeralPrivateKey = ephemeralPrivateKey;
-		}
-
-		Local(Local s, @Nullable MessageId lastMessageId,
-				long lastMessageTimestamp) {
-			this(s.alice, lastMessageId, lastMessageTimestamp,
-					s.ephemeralPublicKey, s.ephemeralPrivateKey,
-					s.acceptTimestamp, s.macKey);
-		}
-	}
-
-	static class Remote extends Common {
-		final Author author;
-		@Nullable
-		final Map<TransportId, TransportProperties> transportProperties;
-
-		Remote(boolean alice, Author author,
-				@Nullable MessageId lastMessageId,
-				@Nullable byte[] ephemeralPublicKey, @Nullable
-				Map<TransportId, TransportProperties> transportProperties,
-				long acceptTimestamp, @Nullable byte[] macKey) {
-			super(alice, lastMessageId, ephemeralPublicKey, acceptTimestamp,
-					macKey);
-			this.author = author;
-			this.transportProperties = transportProperties;
-		}
-
-		Remote(Remote s, @Nullable MessageId lastMessageId) {
-			this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
-					s.transportProperties, s.acceptTimestamp, s.macKey);
-		}
-
-	}
-
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java
index 1f0aebd2c54c45008c84b8ad8b72d0f0dc47ab16..13b2bc31612d1228122c77e99baf0257d8ae70d8 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/IntroduceeState.java
@@ -14,7 +14,8 @@ enum IntroduceeState implements State {
 	LOCAL_DECLINED(2),
 	LOCAL_ACCEPTED(3),
 	AWAIT_REMOTE_RESPONSE(4),
-	MAILBOX_ADDED(5);
+	AWAIT_AUTH(5),
+	MAILBOX_ADDED(6);
 
 	private final int value;
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java
index e4eb75246ef9844fdd538d8d71f9f61ac55f1d18..ea4d38f630bbc728d1392a2ef55091f7ea5252b2 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCrypto.java
@@ -35,13 +35,18 @@ interface MailboxIntroductionCrypto {
 	 *
 	 * @return The secret master key
 	 */
-	SecretKey deriveMasterKey(IntroduceeSession s)
+	SecretKey deriveMasterKey(AbstractIntroduceeSession s)
+			throws GeneralSecurityException;
+
+	@SuppressWarnings("ConstantConditions")
+	SecretKey deriveMasterKey(byte[] ephemeralPublicKey,
+			byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey)
 			throws GeneralSecurityException;
 
 	/**
 	 * Derives a MAC key from the session's master key for Alice or Bob.
 	 *
-	 * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession)}
+	 * @param masterKey The key returned by {@link #deriveMasterKey(AbstractIntroduceeSession)}
 	 * @param alice true for Alice's MAC key, false for Bob's
 	 * @return The MAC key
 	 */
@@ -51,17 +56,17 @@ interface MailboxIntroductionCrypto {
 	 * Generates a MAC that covers both introducee's ephemeral public keys,
 	 * transport properties, Author IDs and timestamps of the accept message.
 	 */
-	byte[] authMac(SecretKey macKey, IntroduceeSession s,
+	byte[] authMac(SecretKey macKey, AbstractIntroduceeSession s,
 			AuthorId localAuthorId);
 
 	/**
 	 * Verifies a received MAC
 	 *
 	 * @param mac The MAC to verify
-	 * as returned by {@link #deriveMasterKey(IntroduceeSession)}
+	 * as returned by {@link #deriveMasterKey(AbstractIntroduceeSession)}
 	 * @throws GeneralSecurityException if the verification fails
 	 */
-	void verifyAuthMac(byte[] mac, IntroduceeSession s,
+	void verifyAuthMac(byte[] mac, AbstractIntroduceeSession s,
 			AuthorId localAuthorId)
 			throws GeneralSecurityException;
 
@@ -82,20 +87,20 @@ interface MailboxIntroductionCrypto {
 	 *
 	 * @throws GeneralSecurityException if the signature is invalid
 	 */
-	void verifySignature(byte[] signature, IntroduceeSession s)
+	void verifySignature(byte[] signature, AbstractIntroduceeSession s)
 			throws GeneralSecurityException;
 
 	/**
 	 * Generates a MAC using the local MAC key.
 	 */
-	byte[] activateMac(IntroduceeSession s);
+	byte[] activateMac(AbstractIntroduceeSession s);
 
 	/**
 	 * Verifies a MAC from an ACTIVATE message.
 	 *
 	 * @throws GeneralSecurityException if the verification fails
 	 */
-	void verifyActivateMac(byte[] mac, IntroduceeSession s)
+	void verifyActivateMac(byte[] mac, AbstractIntroduceeSession s)
 			throws GeneralSecurityException;
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java
index 8e117c9806c64fe3142f92be7f1ac9cf3ee058bb..debe6e83f0d53edc6aa8444bc6036920a7305f3c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionCryptoImpl.java
@@ -28,9 +28,9 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.LABE
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
 import static org.briarproject.briar.api.introduction.IntroductionManager.MAJOR_VERSION;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Common;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Local;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Remote;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Common;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote;
 
 @Immutable
 @NotNullByDefault
@@ -72,7 +72,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public SecretKey deriveMasterKey(IntroduceeSession s)
+	public SecretKey deriveMasterKey(AbstractIntroduceeSession s)
 			throws GeneralSecurityException {
 		return deriveMasterKey(
 				s.getLocal().ephemeralPublicKey,
@@ -82,6 +82,19 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 		);
 	}
 
+	@Override
+	@SuppressWarnings("ConstantConditions")
+	public SecretKey deriveMasterKey(byte[] ephemeralPublicKey,
+			byte[] ephemeralPrivateKey, byte[] remoteEphemeralPublicKey)
+			throws GeneralSecurityException {
+		return deriveMasterKey(
+				ephemeralPublicKey,
+				ephemeralPrivateKey,
+				remoteEphemeralPublicKey,
+				false
+		);
+	}
+
 	SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey,
 			byte[] remotePublicKey, boolean alice)
 			throws GeneralSecurityException {
@@ -110,7 +123,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public byte[] authMac(SecretKey macKey, IntroduceeSession s,
+	public byte[] authMac(SecretKey macKey, AbstractIntroduceeSession s,
 			AuthorId localAuthorId) {
 		// the macKey is not yet available in the session at this point
 		return authMac(macKey, s.getIntroducer().getId(), localAuthorId,
@@ -130,7 +143,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public void verifyAuthMac(byte[] mac, IntroduceeSession s,
+	public void verifyAuthMac(byte[] mac, AbstractIntroduceeSession s,
 			AuthorId localAuthorId) throws GeneralSecurityException {
 		verifyAuthMac(mac, new SecretKey(s.getRemote().macKey),
 				s.getIntroducer().getId(), localAuthorId, s.getLocal(),
@@ -156,12 +169,12 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 				localAuthorId,
 				local.acceptTimestamp,
 				local.ephemeralPublicKey
-				);
+		);
 		BdfList remoteInfo = BdfList.of(
 				remoteAuthorId,
 				remote.acceptTimestamp,
 				remote.ephemeralPublicKey
-				);
+		);
 		BdfList macList = BdfList.of(
 				introducerId,
 				localInfo,
@@ -186,7 +199,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public void verifySignature(byte[] signature, IntroduceeSession s)
+	public void verifySignature(byte[] signature, AbstractIntroduceeSession s)
 			throws GeneralSecurityException {
 		SecretKey macKey = new SecretKey(s.getRemote().macKey);
 		verifySignature(macKey, s.getRemote().author.getPublicKey(), signature);
@@ -206,7 +219,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 	}
 
 	@Override
-	public byte[] activateMac(IntroduceeSession s) {
+	public byte[] activateMac(AbstractIntroduceeSession s) {
 		if (s.getLocal().macKey == null)
 			throw new AssertionError("Local MAC key is null");
 		return activateMac(new SecretKey(s.getLocal().macKey));
@@ -220,7 +233,7 @@ class MailboxIntroductionCryptoImpl implements MailboxIntroductionCrypto {
 	}
 
 	@Override
-	public void verifyActivateMac(byte[] mac, IntroduceeSession s)
+	public void verifyActivateMac(byte[] mac, AbstractIntroduceeSession s)
 			throws GeneralSecurityException {
 		if (s.getRemote().macKey == null)
 			throw new AssertionError("Remote MAC key is null");
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
index 82a802fee7173a9c15674e44380b4f84792c86c4..3a81dd3735993cbd8dd1d3bc9ff5846114997367 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxIntroductionManagerImpl.java
@@ -40,6 +40,7 @@ import static org.briarproject.bramble.api.contact.ContactManager.ContactHook;
 import static org.briarproject.bramble.api.contact.ContactType.MAILBOX_OWNER;
 import static org.briarproject.bramble.api.contact.ContactType.values;
 import static org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
+import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE;
 import static org.briarproject.briar.api.mailbox.Role.MAILBOX;
 import static org.briarproject.briar.api.mailbox.Role.OWNER;
 import static org.briarproject.briar.mailbox.IntroductionConstants.GROUP_KEY_CONTACT_ID;
@@ -200,7 +201,7 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 				session = handleMessage(txn, m, body, meta.getMessageType(),
 						sessionParser.parseOwnerSession(ss.bdfSession),
 						ownerProtocolEngine);
-			} else if (role == MAILBOX) {
+			} else if (role == INTRODUCEE) {
 				session = handleMessage(txn, m, body, meta.getMessageType(),
 						sessionParser.parseMailboxSession(m.getGroupId(),
 								ss.bdfSession), mailboxProtocolEngine);
@@ -251,11 +252,15 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 				return engine.onRequestMessage(txn, session, request);
 			}
 			case MAILBOX_ACCEPT: {
-				MailboxAcceptMessage acceptMessage =
+				IntroduceeRequestMessage acceptMessage =
 						messageParser.parseMailboxAcceptMessage(m, body);
 				return engine
 						.onMailboxAcceptMessage(txn, session, acceptMessage);
 			}
+			case INTRODUCEE_REQUEST:
+				IntroduceeRequestMessage acceptMessage =
+						messageParser.parseMailboxAcceptMessage(m, body);
+				return engine.onIntroduceeRequestMessage(txn, session, acceptMessage);
 			case ABORT: {
 				AbortMessage abort = messageParser.parseAbortMessage(m, body);
 				return engine.onAbortMessage(txn, session, abort);
@@ -372,7 +377,7 @@ class MailboxIntroductionManagerImpl extends BdfIncomingMessageHook
 			d = sessionEncoder.encodeIntroducerSession((OwnerSession) session);
 		} else if (session.getRole() == MAILBOX) {
 			d = sessionEncoder
-					.encodeIntroduceeSession((IntroduceeSession) session);
+					.encodeIntroduceeSession((AbstractIntroduceeSession) session);
 		} else {
 			throw new AssertionError();
 		}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
index dc9b032e1d355dc23ac8241fe69efca6c0792cac..ed8414e5c9b5ad58329abc78561b15dee8662c26 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoder.java
@@ -27,8 +27,15 @@ interface MailboxMessageEncoder {
 			@Nullable MessageId previousMessageId, Author introduceeAuthor,
 			long messageCounter);
 
-	Message encodeMailboxAcceptMessage(GroupId contactGroupId, long timestamp,
+	Message encodeIntroduceeAcceptMessage(GroupId contactGroupId,
+			long timestamp,
 			@Nullable MessageId previousMessageId, SessionId sessionId,
+			byte[] ephemeralPublicKey, byte[] mac, byte[] signature,
+			long messageCounter);
+
+	Message encodeIntroduceeRequestMessage(GroupId contactGroupId,
+			long timestamp, @Nullable MessageId previousMessageId,
+			SessionId sessionId,
 			byte[] ephemeralPublicKey, long acceptTimestamp,
 			long messageCounter);
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
index fc2ddcff754ffb223e706e955e7945bf6cf44870..c1bdc76d8ecdbc005602b6d48e40c2149c0e7d53 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageEncoderImpl.java
@@ -83,7 +83,7 @@ class MailboxMessageEncoderImpl implements MailboxMessageEncoder {
 	}
 
 	@Override
-	public Message encodeMailboxAcceptMessage(GroupId contactGroupId,
+	public Message encodeIntroduceeRequestMessage(GroupId contactGroupId,
 			long timestamp, @Nullable MessageId previousMessageId,
 			SessionId sessionId, byte[] ephemeralPublicKey,
 			long acceptTimestamp, long messageCounter) {
@@ -93,6 +93,18 @@ class MailboxMessageEncoderImpl implements MailboxMessageEncoder {
 		return createMessage(contactGroupId, timestamp, body);
 	}
 
+	@Override
+	public Message encodeIntroduceeAcceptMessage(GroupId contactGroupId,
+			long timestamp, @Nullable MessageId previousMessageId,
+			SessionId sessionId, byte[] ephemeralPublicKey, byte[] mac,
+			byte[] signature, long messageCounter) {
+		BdfList body = BdfList.of(MAILBOX_ACCEPT.getValue(), sessionId,
+				previousMessageId, ephemeralPublicKey, mac, signature,
+				timestamp,
+				messageCounter);
+		return createMessage(contactGroupId, timestamp, body);
+	}
+
 	@Override
 	public Message encodeDeclineMessage(GroupId contactGroupId, long timestamp,
 			@Nullable MessageId previousMessageId, SessionId sessionId) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
index 7370e7bd2ae3a81e064a035bad31f32895b753ad..6ee90e89a70cc1257b8f7355539cd3af14ebd654 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParser.java
@@ -19,7 +19,7 @@ interface MailboxMessageParser {
 	RequestMessage parseRequestMessage(Message m, BdfList body)
 			throws FormatException;
 
-	MailboxAcceptMessage parseMailboxAcceptMessage(Message m, BdfList body)
+	IntroduceeRequestMessage parseMailboxAcceptMessage(Message m, BdfList body)
 			throws FormatException;
 
 	DeclineMessage parseDeclineMessage(Message m, BdfList body)
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
index 93e72e916f0037cb1d82d4f83d4274df0de1ddc1..944bf0e08628d23b240a1277bcf35f6d476d30c1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxMessageParserImpl.java
@@ -67,7 +67,7 @@ class MailboxMessageParserImpl implements MailboxMessageParser {
 	}
 
 	@Override
-	public MailboxAcceptMessage parseMailboxAcceptMessage(Message m,
+	public IntroduceeRequestMessage parseMailboxAcceptMessage(Message m,
 			BdfList body) throws FormatException {
 		SessionId sessionId = new SessionId(body.getRaw(1));
 		byte[] previousMsgBytes = body.getOptionalRaw(2);
@@ -75,7 +75,7 @@ class MailboxMessageParserImpl implements MailboxMessageParser {
 				new MessageId(previousMsgBytes));
 		byte[] ephemeralPublicKey = body.getRaw(3);
 		long acceptTimestamp = body.getLong(4);
-		return new MailboxAcceptMessage(m.getId(), m.getGroupId(),
+		return new IntroduceeRequestMessage(m.getId(), m.getGroupId(),
 				m.getTimestamp(), previousMessageId, sessionId,
 				ephemeralPublicKey, acceptTimestamp);
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
index 901c6efca8c2fbd07dde4e04a82e860d64ea1eeb..e134fc57ca654d66eca2fb582cb6fc6301466569 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxProtocolEngine.java
@@ -126,14 +126,21 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> {
 
 	@Override
 	public MailboxSession onMailboxAcceptMessage(Transaction txn,
-			MailboxSession session, MailboxAcceptMessage m)
+			MailboxSession session, IntroduceeRequestMessage m)
 			throws DbException, FormatException {
 		return null;
 	}
 
+	@Override
+	public MailboxSession onIntroduceeRequestMessage(Transaction txn,
+			MailboxSession session, IntroduceeRequestMessage m)
+			throws DbException, FormatException {
+		throw new UnsupportedOperationException();
+	}
+
 	@Override
 	public MailboxSession onContactAcceptMessage(Transaction txn,
-			MailboxSession session, MailboxAcceptMessage m)
+			MailboxSession session, IntroduceeRequestMessage m)
 			throws DbException, FormatException {
 		return null;
 	}
@@ -159,12 +166,12 @@ class MailboxProtocolEngine extends AbstractProtocolEngine<MailboxSession> {
 	}
 
 
-	private boolean isInvalidDependency(IntroduceeSession s,
+	private boolean isInvalidDependency(AbstractIntroduceeSession s,
 			@Nullable MessageId dependency) {
 		return isInvalidDependency(s.getLastRemoteMessageId(), dependency);
 	}
 
-	private long getLocalTimestamp(IntroduceeSession s) {
+	private long getLocalTimestamp(AbstractIntroduceeSession s) {
 		return getLocalTimestamp(s.getLocalTimestamp(),
 				s.getRequestTimestamp());
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
index 54f46c7493ef321186136b2f73d891207e3c2360..c9974ba924b92097ab58fc0b2e70e64178948e25 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSession.java
@@ -16,12 +16,13 @@ import java.util.Map;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import static org.briarproject.briar.api.mailbox.Role.MAILBOX;
 import static org.briarproject.briar.mailbox.IntroduceeState.AWAIT_REMOTE_RESPONSE;
 import static org.briarproject.briar.mailbox.IntroduceeState.START;
 
 @Immutable
 @NotNullByDefault
-class MailboxSession extends IntroduceeSession {
+class MailboxSession extends AbstractIntroduceeSession {
 
 
 	MailboxSession(SessionId sessionId, IntroduceeState state,
@@ -99,7 +100,7 @@ class MailboxSession extends IntroduceeSession {
 
 	@Override
 	Role getRole() {
-		return Role.MAILBOX;
+		return MAILBOX;
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
index 6ac94a2a3dc04ea5cabfa75cccf4eaf07be1c1c2..a20028f41a006f88965097195f5efe6065a5a749 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoder.java
@@ -13,6 +13,6 @@ interface MailboxSessionEncoder {
 
 	BdfDictionary encodeIntroducerSession(OwnerSession s);
 
-	BdfDictionary encodeIntroduceeSession(IntroduceeSession s);
+	BdfDictionary encodeIntroduceeSession(AbstractIntroduceeSession s);
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
index d8ef32f1cf0d75fe0155260474074f7f4d4bffe3..af188a7544a826bb0f94cdf908d089d6d459dd2e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionEncoderImpl.java
@@ -17,12 +17,13 @@ import javax.inject.Inject;
 import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
 import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
 import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Common;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Local;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Remote;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Common;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ALICE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR;
+import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_COUNTER;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_GROUP_ID;
@@ -92,7 +93,7 @@ class MailboxSessionEncoderImpl implements
 	}
 
 	@Override
-	public BdfDictionary encodeIntroduceeSession(IntroduceeSession s) {
+	public BdfDictionary encodeIntroduceeSession(AbstractIntroduceeSession s) {
 		BdfDictionary d = encodeSession(s);
 		d.put(SESSION_KEY_INTRODUCER, clientHelper.toList(s.getIntroducer()));
 		d.put(SESSION_KEY_LOCAL, encodeLocal(s.getLocal()));
@@ -134,6 +135,7 @@ class MailboxSessionEncoderImpl implements
 		d.put(SESSION_KEY_ROLE, s.getRole().getValue());
 		d.put(SESSION_KEY_STATE, s.getState().getValue());
 		d.put(SESSION_KEY_REQUEST_TIMESTAMP, s.getRequestTimestamp());
+		d.put(SESSION_KEY_COUNTER, s.getMessageCounter());
 		return d;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
index d3f995611fb2e17f78ce7845b30092d92c716242..b7beb49ff33c87e5281b8b6056318c5c0cd0e269 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MailboxSessionParserImpl.java
@@ -24,9 +24,8 @@ import javax.inject.Inject;
 import static org.briarproject.briar.api.mailbox.Role.INTRODUCEE;
 import static org.briarproject.briar.api.mailbox.Role.OWNER;
 import static org.briarproject.briar.api.mailbox.Role.fromValue;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Local;
-import static org.briarproject.briar.mailbox.IntroduceeSession.Remote;
-import static org.briarproject.briar.mailbox.IntroductionConstants.MSG_KEY_COUNTER;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Local;
+import static org.briarproject.briar.mailbox.AbstractIntroduceeSession.Remote;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_ALICE;
 import static org.briarproject.briar.mailbox.IntroductionConstants.SESSION_KEY_AUTHOR;
@@ -75,7 +74,7 @@ class MailboxSessionParserImpl implements MailboxSessionParser {
 
 	@Override
 	public long getMessageCounter(BdfDictionary d) throws FormatException {
-		return d.getLong(MSG_KEY_COUNTER);
+		return d.getLong(SESSION_KEY_COUNTER);
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java
index 644de22c4b8125d0c5f1f3675dc3a17b4b6a0c52..0b35f7721432489dc09933d971932626e1cb914e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/MessageType.java
@@ -9,8 +9,8 @@ import javax.annotation.concurrent.Immutable;
 @NotNullByDefault
 enum MessageType {
 
-	MAILBOX_REQUEST(0), MAILBOX_ACCEPT(1), CONTACT_REQUEST(2),
-	CONTACT_ACCEPT(3), CONTACT_INFO(4), MAILBOX_AUTH(5), DECLINE(5), ABORT(6);
+	MAILBOX_REQUEST(0), MAILBOX_ACCEPT(1), INTRODUCEE_REQUEST(2),
+	INTRODUCEE_ACCEPT(3), CONTACT_INFO(4), MAILBOX_AUTH(5), DECLINE(5), ABORT(6);
 
 	private final int value;
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
index adb82656b86658238d8fc3a2f398f8c952392ca1..0cfd4c22de2c061c467e4f1cc7492036c020ffd5 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/OwnerProtocolEngine.java
@@ -74,11 +74,11 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 
 	@Override
 	public OwnerSession onMailboxAcceptMessage(Transaction txn, OwnerSession s,
-			MailboxAcceptMessage m) throws DbException, FormatException {
+			IntroduceeRequestMessage m) throws DbException, FormatException {
 		switch (s.getState()) {
 			case START:
 			case AWAIT_RESPONSE_M:
-				onMailboxAccept(txn, s, m);
+				return onMailboxAccept(txn, s, m);
 			case M_DECLINED:
 			case AWAIT_RESPONSE_B:
 			case B_DECLINED:
@@ -90,19 +90,26 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 		}
 	}
 
+	@Override
+	public OwnerSession onIntroduceeRequestMessage(Transaction txn,
+			OwnerSession session, IntroduceeRequestMessage m)
+			throws DbException, FormatException {
+		throw new UnsupportedOperationException();
+	}
+
 	@Override
 	public OwnerSession onContactAcceptMessage(Transaction txn,
-			OwnerSession session, MailboxAcceptMessage m)
+			OwnerSession session, IntroduceeRequestMessage m)
 			throws DbException, FormatException {
 		throw new UnsupportedOperationException();
 	}
 
 	private OwnerSession onMailboxAccept(Transaction txn, OwnerSession s,
-			MailboxAcceptMessage m) throws DbException {
+			IntroduceeRequestMessage m) throws DbException {
 		// The dependency, if any, must be the last remote message
 		if (isInvalidDependency(s.getMailbox().lastRemoteMessageId,
 				m.getPreviousMessageId())) return abort(txn, s);
-		Message forward = sendMailboxAcceptMessage(txn, s.getIntroducee(),
+		Message forward = sendIntroduceeRequestMessage(txn, s.getIntroducee(),
 				clock.currentTimeMillis(), m.getEphemeralPublicKey(),
 				m.getAcceptTimestamp(), s.getMessageCounter());
 		broadcastMailboxIntroductionResponseReceived(txn, s.getMailbox().author,
@@ -162,7 +169,7 @@ class OwnerProtocolEngine extends AbstractProtocolEngine<OwnerSession> {
 	void broadcastMailboxIntroductionResponseReceived(Transaction txn,
 			Author from, Author to) {
 		MailboxIntroductionResponseReceivedEvent e =
-				new MailboxIntroductionResponseReceivedEvent(to, from);
+				new MailboxIntroductionResponseReceivedEvent(from, to);
 		txn.attach(e);
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
index 18ffec2e56f79f6dbea9527ce35200cc0563239a..edcd5b63e6152e9d2e4877acea6b8975be9d9670 100644
--- a/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/mailbox/ProtocolEngine.java
@@ -20,10 +20,16 @@ interface ProtocolEngine<S extends Session> {
 	S onRequestMessage(Transaction txn, S session, RequestMessage m)
 			throws DbException, FormatException;
 
-	S onMailboxAcceptMessage(Transaction txn, S session, MailboxAcceptMessage m)
+	S onMailboxAcceptMessage(Transaction txn, S session,
+			IntroduceeRequestMessage m)
 			throws DbException, FormatException;
 
-	S onContactAcceptMessage(Transaction txn, S session, MailboxAcceptMessage m)
+	S onIntroduceeRequestMessage(Transaction txn,
+			S session, IntroduceeRequestMessage m)
+			throws DbException, FormatException;
+
+	S onContactAcceptMessage(Transaction txn, S session,
+			IntroduceeRequestMessage m)
 			throws DbException, FormatException;
 
 	S onDeclineMessage(Transaction txn, S session, DeclineMessage m)
diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java
index a089eb0e06e61af209ae4d22d27d83a7ac650c58..1e6fa46e29546e19e31d3581dbb51609fa5cadea 100644
--- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerIntegrationTest.java
@@ -86,6 +86,9 @@ public class BlogManagerIntegrationTest
 		c2 = DaggerBriarIntegrationTestComponent.builder()
 				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
 		injectEagerSingletons(c2);
+		cMailbox = DaggerBriarIntegrationTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t3Dir)).build();
+		injectEagerSingletons(cMailbox);
 	}
 
 	@Test
diff --git a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
index 3d794f9ebbb4f65b8403c61800bf71653bc96594..403904d211b7ded0a2f8c739b9b11766ab67c0d1 100644
--- a/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/mailbox/MailboxIntroductionIntegrationTest.java
@@ -136,12 +136,15 @@ public class MailboxIntroductionIntegrationTest extends
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listenerMailbox.requestReceived);
 		assertEquals(authorMailbox.getId(), listenerMailbox.getRequest());
-		// sync first REQUEST message
+		// sync RESPONSE message
 		syncMailboxTo0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response1Received);
 		assertEquals(authorMailbox.getId(), listener0.getAuthors()[0]);
 		assertEquals(author1.getId(), listener0.getAuthors()[1]);
+		// sync/forward RESPONSE message to the contact
+		sync0To1(1, true);
+		eventWaiter.await(TIMEOUT, 1);
 	}
 
 
@@ -249,7 +252,7 @@ public class MailboxIntroductionIntegrationTest extends
 				} finally {
 					eventWaiter.resume();
 				}
-			} else if (e instanceof IntroductionResponseReceivedEvent) {
+			} else if (e instanceof MailboxIntroductionResponseReceivedEvent) {
 				// only broadcast for DECLINE messages in introducee role
 				latestEvent = e;
 				eventWaiter.resume();
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
index 394d19204f481d3d3b8f79a6eb09cc8dcb0eb847..7c43ea31a0ed149b7d9e43c6be0ed9dc6de02faa 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
@@ -187,7 +187,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		component.inject(new GroupInvitationModule.EagerSingletons());
 		component.inject(new IdentityModule.EagerSingletons());
 		component.inject(new IntroductionModule.EagerSingletons());
-	//	component.inject(new MailboxIntroductionModule.EagerSingletons());
+		component.inject(new MailboxIntroductionModule.EagerSingletons());
 		component.inject(new LifecycleModule.EagerSingletons());
 		component.inject(new MessagingModule.EagerSingletons());
 		component.inject(new PrivateGroupModule.EagerSingletons());