diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationItem.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationItem.java
index 39dcb24a4678430b686f1f56b374cedb982f6127..9070c738f5e934e97b0533ddaa7f9fd112e70aed 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationItem.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationItem.java
@@ -124,6 +124,9 @@ abstract class ConversationItem {
 				text = ctx.getString(
 						R.string.introduction_response_accepted_sent,
 						ir.getName());
+				text += "\n\n" + ctx.getString(
+						R.string.introduction_response_accepted_sent_info,
+						ir.getName());
 			} else {
 				text = ctx.getString(
 						R.string.introduction_response_declined_sent,
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index 38b04cd534ba1ac46342e839d5f61b8e7d90514b..d77582999d08210d1ab55c8a1a10c0dc8b8bb51c 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -156,6 +156,7 @@
 	<string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
 	<string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string>
 	<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
+	<string name="introduction_response_accepted_sent_info">Before %1$s gets added to your contacts, they need to accept the introduction as well. This might take some time.</string>
 	<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
 	<string name="introduction_response_accepted_received">%1$s accepted the introduction to %2$s.</string>
 	<string name="introduction_response_declined_received">%1$s declined the introduction to %2$s.</string>
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java
index 8711193f15b9e4696fa0bebb8bb2d8f61d9aeb16..9a267c5c205304087f62c4add918d73bd1613c2c 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionManager.java
@@ -25,6 +25,9 @@ public interface IntroductionManager extends ConversationClient {
 	 */
 	int CLIENT_VERSION = 1;
 
+	/**
+	 * Returns true if both contacts can be introduced at this moment.
+	 */
 	boolean canIntroduce(Contact c1, Contact c2) throws DbException;
 
 	/**
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
index ae395b596928734020c2fa89c9afcaaad62174b1..03b3486ada568e39f8ad457fb2b6c45159dff2ea 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
@@ -144,15 +144,15 @@ abstract class AbstractProtocolEngine<S extends Session>
 		}
 	}
 
-	void broadcastIntroductionResponseReceivedEvent(Transaction txn,
-			Session s, AuthorId sender, AbstractIntroductionMessage m)
+	void broadcastIntroductionResponseReceivedEvent(Transaction txn, Session s,
+			AuthorId sender, Author otherAuthor, AbstractIntroductionMessage m)
 			throws DbException {
 		AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
 		Contact c = contactManager.getContact(txn, sender, localAuthorId);
 		IntroductionResponse response =
 				new IntroductionResponse(s.getSessionId(), m.getMessageId(),
 						m.getGroupId(), s.getRole(), m.getTimestamp(), false,
-						false, false, false, c.getAuthor().getName(),
+						false, false, false, otherAuthor.getName(),
 						m instanceof AcceptMessage);
 		IntroductionResponseReceivedEvent e =
 				new IntroductionResponseReceivedEvent(c.getId(), response);
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
index 9f7f83ab644627a00f20de8f7cff06013ba08b4a..e2ea32ed62209bfbf7dc93bd65deb6078fb1e3a1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
@@ -355,7 +355,7 @@ class IntroduceeProtocolEngine
 
 		// Broadcast IntroductionResponseReceivedEvent
 		broadcastIntroductionResponseReceivedEvent(txn, s,
-				s.getIntroducer().getId(), m);
+				s.getIntroducer().getId(), s.getRemote().author, m);
 
 		if (s.getState() == AWAIT_RESPONSES) {
 			// Mark the request message unavailable to answer
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
index 50b9d01a949723be57fbc9baa7b64916e0762e6a..a346a0bf32129320a99861377f7a90cb8159b4f1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
@@ -261,19 +261,24 @@ class IntroducerProtocolEngine
 		// Create the next state
 		IntroducerState state = AWAIT_AUTHS;
 		Introducee introduceeA, introduceeB;
+		Author sender, other;
 		if (senderIsAlice) {
 			if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_B;
 			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
 			introduceeB = new Introducee(s.getIntroduceeB(), sent);
+			sender = introduceeA.author;
+			other = introduceeB.author;
 		} else {
 			if (s.getState() == AWAIT_RESPONSES) state = AWAIT_RESPONSE_A;
 			introduceeA = new Introducee(s.getIntroduceeA(), sent);
 			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
+			sender = introduceeB.author;
+			other = introduceeA.author;
 		}
 
 		// Broadcast IntroductionResponseReceivedEvent
-		Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
-		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
+		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
+				other, m);
 
 		// Move to the next state
 		return new IntroducerSession(s.getSessionId(), state,
@@ -313,17 +318,22 @@ class IntroducerProtocolEngine
 				m.getTransportProperties(), false);
 
 		Introducee introduceeA, introduceeB;
+		Author sender, other;
 		if (senderIsAlice) {
 			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
 			introduceeB = new Introducee(s.getIntroduceeB(), sent);
+			sender = introduceeA.author;
+			other = introduceeB.author;
 		} else {
 			introduceeA = new Introducee(s.getIntroduceeA(), sent);
 			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
+			sender = introduceeB.author;
+			other = introduceeA.author;
 		}
 
 		// Broadcast IntroductionResponseReceivedEvent
-		Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
-		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
+		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
+				other, m);
 
 		return new IntroducerSession(s.getSessionId(), START,
 				s.getRequestTimestamp(), introduceeA, introduceeB);
@@ -360,19 +370,24 @@ class IntroducerProtocolEngine
 		// Create the next state
 		IntroducerState state = START;
 		Introducee introduceeA, introduceeB;
+		Author sender, other;
 		if (senderIsAlice) {
 			if (s.getState() == AWAIT_RESPONSES) state = A_DECLINED;
 			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
 			introduceeB = new Introducee(s.getIntroduceeB(), sent);
+			sender = introduceeA.author;
+			other = introduceeB.author;
 		} else {
 			if (s.getState() == AWAIT_RESPONSES) state = B_DECLINED;
 			introduceeA = new Introducee(s.getIntroduceeA(), sent);
 			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
+			sender = introduceeB.author;
+			other = introduceeA.author;
 		}
 
 		// Broadcast IntroductionResponseReceivedEvent
-		Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
-		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
+		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
+				other, m);
 
 		return new IntroducerSession(s.getSessionId(), state,
 				s.getRequestTimestamp(), introduceeA, introduceeB);
@@ -405,17 +420,22 @@ class IntroducerProtocolEngine
 		Message sent = sendDeclineMessage(txn, i, timestamp, false);
 
 		Introducee introduceeA, introduceeB;
+		Author sender, other;
 		if (senderIsAlice) {
 			introduceeA = new Introducee(s.getIntroduceeA(), m.getMessageId());
 			introduceeB = new Introducee(s.getIntroduceeB(), sent);
+			sender = introduceeA.author;
+			other = introduceeB.author;
 		} else {
 			introduceeA = new Introducee(s.getIntroduceeA(), sent);
 			introduceeB = new Introducee(s.getIntroduceeB(), m.getMessageId());
+			sender = introduceeB.author;
+			other = introduceeA.author;
 		}
 
 		// Broadcast IntroductionResponseReceivedEvent
-		Author sender = senderIsAlice ? introduceeA.author : introduceeB.author;
-		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(), m);
+		broadcastIntroductionResponseReceivedEvent(txn, s, sender.getId(),
+				other, m);
 
 		return new IntroducerSession(s.getSessionId(), START,
 				s.getRequestTimestamp(), introduceeA, introduceeB);
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
index df0d46b8811e60e6f8f58919ef388a590fd4363e..b8e5b646bccb84c37dae41f385c5102200f449f6 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.introduction.IntroductionManager;
 import org.briarproject.briar.api.introduction.IntroductionMessage;
 import org.briarproject.briar.api.introduction.IntroductionRequest;
+import org.briarproject.briar.api.introduction.IntroductionResponse;
 import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
 import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
 import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
@@ -51,6 +52,7 @@ import static org.briarproject.bramble.test.TestUtils.getTransportProperties;
 import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
 import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_ID;
 import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
+import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
 import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_DECLINED;
 import static org.briarproject.briar.introduction.IntroducerState.A_DECLINED;
 import static org.briarproject.briar.introduction.IntroducerState.B_DECLINED;
@@ -146,24 +148,32 @@ public class IntroductionIntegrationTest
 		sync0To1(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener1.requestReceived);
+		assertEquals(introducee2.getAuthor().getName(),
+				listener1.getRequest().getName());
 		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
 
 		// sync second REQUEST message
 		sync0To2(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener2.requestReceived);
+		assertEquals(introducee1.getAuthor().getName(),
+				listener2.getRequest().getName());
 		assertGroupCount(messageTracker2, g2.getId(), 2, 1);
 
 		// sync first ACCEPT message
 		sync1To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response1Received);
+		assertEquals(introducee2.getAuthor().getName(),
+				listener0.getResponse().getName());
 		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
 
 		// sync second ACCEPT message
 		sync2To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response2Received);
+		assertEquals(introducee1.getAuthor().getName(),
+				listener0.getResponse().getName());
 		assertGroupCount(messageTracker0, g2.getId(), 2, 1);
 
 		// sync forwarded ACCEPT messages to introducees
@@ -259,6 +269,10 @@ public class IntroductionIntegrationTest
 		assertEquals(alice ? A_DECLINED : B_DECLINED,
 				introducerSession.getState());
 
+		// assert that the name on the decline event is correct
+		assertEquals(introducee2.getAuthor().getName(),
+				listener0.getResponse().getName());
+
 		// sync second response
 		sync2To0(1, true);
 		eventWaiter.await(TIMEOUT, 1);
@@ -271,6 +285,11 @@ public class IntroductionIntegrationTest
 		// sync first forwarded response
 		sync0To2(1, true);
 
+		// assert that the name on the decline event is correct
+		eventWaiter.await(TIMEOUT, 1);
+		assertEquals(introducee1.getAuthor().getName(),
+				listener2.getResponse().getName());
+
 		// note how the introducer does not forward the second response,
 		// because after the first decline the protocol finished
 
@@ -339,6 +358,11 @@ public class IntroductionIntegrationTest
 		sync0To2(1, true);
 		sync0To1(1, true);
 
+		// assert that the name on the decline event is correct
+		eventWaiter.await(TIMEOUT, 1);
+		assertEquals(contact2From0.getAuthor().getName(),
+				listener1.getResponse().getName());
+
 		assertFalse(contactManager1
 				.contactExists(author2.getId(), author1.getId()));
 		assertFalse(contactManager2
@@ -408,8 +432,6 @@ public class IntroductionIntegrationTest
 		assertFalse(contactManager2
 				.contactExists(author1.getId(), author2.getId()));
 
-		// since introducee2 was already in FINISHED state when
-		// introducee1's response arrived, she ignores and deletes it
 		assertDefaultUiMessages();
 		assertFalse(listener0.aborted);
 		assertFalse(listener1.aborted);
@@ -417,7 +439,7 @@ public class IntroductionIntegrationTest
 	}
 
 	@Test
-	public void testResponseAndAckInOneSession() throws Exception {
+	public void testResponseAndAuthInOneSync() throws Exception {
 		addListeners(true, true);
 
 		// make introduction
@@ -449,10 +471,125 @@ public class IntroductionIntegrationTest
 				.respondToIntroduction(contactId0From2, listener2.sessionId, time,
 						true);
 
-		// sync second response and ACK and make sure there is no abort
+		// sync second response and AUTH
 		sync2To0(2, true);
 		eventWaiter.await(TIMEOUT, 1);
 		assertTrue(listener0.response2Received);
+
+		// Forward AUTH
+		sync0To1(1, true);
+
+		// Second AUTH and ACTIATE and forward them
+		sync1To0(2, true);
+		sync0To2(2, true);
+
+		assertTrue(contactManager1
+				.contactExists(author2.getId(), author1.getId()));
+		assertTrue(contactManager2
+				.contactExists(author1.getId(), author2.getId()));
+
+		assertDefaultUiMessages();
+		assertFalse(listener0.aborted);
+		assertFalse(listener1.aborted);
+		assertFalse(listener2.aborted);
+	}
+
+	/**
+	 * When an introducee declines an introduction,
+	 * the other introducee needs to respond before returning to START state,
+	 * otherwise a subsequent attempt at introducing the same contacts will fail
+	 */
+	@Test
+	public void testAutomaticSecondDecline() throws Exception {
+		// introducee1 declines automatically and introducee2 doesn't answer
+		addListeners(false, true);
+		listener2.answerRequests = false;
+
+		// make introduction
+		long time = clock.currentTimeMillis();
+		Contact introducee1 = contact1From0;
+		Contact introducee2 = contact2From0;
+		introductionManager0
+				.makeIntroduction(introducee1, introducee2, null, time);
+
+		// sync request messages
+		sync0To1(1, true);
+		sync0To2(1, true);
+
+		// assert that introducee1 is in correct state
+		IntroduceeSession introduceeSession = getIntroduceeSession(c1);
+		assertEquals(LOCAL_DECLINED, introduceeSession.getState());
+
+		// sync first response
+		sync1To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response1Received);
+
+		// assert that introducer is in correct state
+		boolean alice = c0.getIntroductionCrypto()
+				.isAlice(introducee1.getAuthor().getId(),
+						introducee2.getAuthor().getId());
+		IntroducerSession introducerSession = getIntroducerSession();
+		assertEquals(alice ? A_DECLINED : B_DECLINED,
+				introducerSession.getState());
+
+		// assert that introducee2 is in correct state
+		introduceeSession = getIntroduceeSession(c2);
+		assertEquals(AWAIT_RESPONSES, introduceeSession.getState());
+
+		// forward first DECLINE
+		sync0To2(1, true);
+
+		// assert that the name on the decline event is correct
+		eventWaiter.await(TIMEOUT, 1);
+		assertEquals(introducee1.getAuthor().getName(),
+				listener2.getResponse().getName());
+
+		// assert that introducee2 is in correct state
+		introduceeSession = getIntroduceeSession(c2);
+		assertEquals(IntroduceeState.START, introduceeSession.getState());
+
+		// second response should be an immediate automatic DECLINE
+		sync2To0(1, true);
+		eventWaiter.await(TIMEOUT, 1);
+		assertTrue(listener0.response2Received);
+
+		// assert that introducer now moved to START state
+		introducerSession = getIntroducerSession();
+		assertEquals(START, introducerSession.getState());
+
+		// introducee1 is still waiting for second response
+		introduceeSession = getIntroduceeSession(c1);
+		assertEquals(LOCAL_DECLINED, introduceeSession.getState());
+
+		// forward automatic decline
+		sync0To1(1, true);
+
+		// introducee1 can finally move to the START
+		introduceeSession = getIntroduceeSession(c1);
+		assertEquals(IntroduceeState.START, introduceeSession.getState());
+
+		Group g1 = introductionManager0.getContactGroup(introducee1);
+		Group g2 = introductionManager0.getContactGroup(introducee2);
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId1From0)
+						.size());
+		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
+		assertEquals(2,
+				introductionManager0.getIntroductionMessages(contactId2From0)
+						.size());
+		assertGroupCount(messageTracker0, g2.getId(), 2, 1);
+		assertEquals(2,
+				introductionManager1.getIntroductionMessages(contactId0From1)
+						.size());
+		assertGroupCount(messageTracker1, g1.getId(), 2, 1);
+		// the automatic DECLINE is invisible in the UI
+		// so there's just the remote REQUEST and remote DECLINE
+		assertEquals(2,
+				introductionManager2.getIntroductionMessages(contactId0From2)
+						.size());
+		assertGroupCount(messageTracker2, g2.getId(), 2, 2);
+
 		assertFalse(listener0.aborted);
 		assertFalse(listener1.aborted);
 		assertFalse(listener2.aborted);
@@ -1012,11 +1149,25 @@ public class IntroductionIntegrationTest
 
 	@MethodsNotNullByDefault
 	@ParametersNotNullByDefault
-	private class IntroduceeListener implements EventListener {
+	private abstract class IntroductionListener implements EventListener {
+
+		protected volatile boolean aborted = false;
+		protected volatile Event latestEvent;
+
+		IntroductionResponse getResponse() {
+			assertTrue(
+					latestEvent instanceof IntroductionResponseReceivedEvent);
+			return ((IntroductionResponseReceivedEvent) latestEvent)
+					.getIntroductionResponse();
+		}
+	}
+
+	@MethodsNotNullByDefault
+	@ParametersNotNullByDefault
+	private class IntroduceeListener extends IntroductionListener {
 
 		private volatile boolean requestReceived = false;
 		private volatile boolean succeeded = false;
-		private volatile boolean aborted = false;
 		private volatile boolean answerRequests = true;
 		private volatile SessionId sessionId;
 
@@ -1031,6 +1182,7 @@ public class IntroductionIntegrationTest
 		@Override
 		public void eventOccurred(Event e) {
 			if (e instanceof IntroductionRequestReceivedEvent) {
+				latestEvent = e;
 				IntroductionRequestReceivedEvent introEvent =
 						((IntroductionRequestReceivedEvent) e);
 				requestReceived = true;
@@ -1053,29 +1205,42 @@ public class IntroductionIntegrationTest
 				} finally {
 					eventWaiter.resume();
 				}
+			} else if (e instanceof IntroductionResponseReceivedEvent) {
+				// only broadcast for DECLINE messages in introducee role
+				latestEvent = e;
+				eventWaiter.resume();
 			} else if (e instanceof IntroductionSucceededEvent) {
+				latestEvent = e;
 				succeeded = true;
 				Contact contact = ((IntroductionSucceededEvent) e).getContact();
 				eventWaiter
 						.assertFalse(contact.getId().equals(contactId0From1));
 				eventWaiter.resume();
 			} else if (e instanceof IntroductionAbortedEvent) {
+				latestEvent = e;
 				aborted = true;
 				eventWaiter.resume();
 			}
 		}
+
+		private IntroductionRequest getRequest() {
+			assertTrue(
+					latestEvent instanceof IntroductionRequestReceivedEvent);
+			return ((IntroductionRequestReceivedEvent) latestEvent)
+					.getIntroductionRequest();
+		}
 	}
 
 	@NotNullByDefault
-	private class IntroducerListener implements EventListener {
+	private class IntroducerListener extends IntroductionListener {
 
 		private volatile boolean response1Received = false;
 		private volatile boolean response2Received = false;
-		private volatile boolean aborted = false;
 
 		@Override
 		public void eventOccurred(Event e) {
 			if (e instanceof IntroductionResponseReceivedEvent) {
+				latestEvent = e;
 				ContactId c =
 						((IntroductionResponseReceivedEvent) e)
 								.getContactId();
@@ -1086,6 +1251,7 @@ public class IntroductionIntegrationTest
 				}
 				eventWaiter.resume();
 			} else if (e instanceof IntroductionAbortedEvent) {
+				latestEvent = e;
 				aborted = true;
 				eventWaiter.resume();
 			}