Index: /trunk/downstream.go
===================================================================
--- /trunk/downstream.go	(revision 427)
+++ /trunk/downstream.go	(revision 428)
@@ -288,4 +288,71 @@
 
 	dc.conn.SendMessage(msg)
+}
+
+// sendMessageWithID sends an outgoing message with the specified internal ID.
+func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
+	dc.SendMessage(msg)
+
+	if id == "" || !dc.messageSupportsHistory(msg) {
+		return
+	}
+
+	dc.sendPing(id)
+}
+
+// advanceMessageWithID advances history to the specified message ID without
+// sending a message. This is useful e.g. for self-messages when echo-message
+// isn't enabled.
+func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
+	if id == "" || !dc.messageSupportsHistory(msg) {
+		return
+	}
+
+	dc.sendPing(id)
+}
+
+// ackMsgID acknowledges that a message has been received.
+func (dc *downstreamConn) ackMsgID(id string) {
+	netName, entity, _, _, err := parseMsgID(id)
+	if err != nil {
+		dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
+		return
+	}
+
+	network := dc.user.getNetwork(netName)
+	if network == nil {
+		return
+	}
+
+	history, ok := network.history[entity]
+	if !ok {
+		return
+	}
+
+	history.clients[dc.clientName] = id
+}
+
+func (dc *downstreamConn) sendPing(msgID string) {
+	token := "soju-msgid-" + base64.RawURLEncoding.EncodeToString([]byte(msgID))
+	dc.SendMessage(&irc.Message{
+		Command: "PING",
+		Params:  []string{token},
+	})
+}
+
+func (dc *downstreamConn) handlePong(token string) {
+	if !strings.HasPrefix(token, "soju-msgid-") {
+		dc.logger.Printf("received unrecognized PONG token %q", token)
+		return
+	}
+	token = strings.TrimPrefix(token, "soju-msgid-")
+	b, err := base64.RawURLEncoding.DecodeString(token)
+	if err != nil {
+		dc.logger.Printf("received malformed PONG token: %v", err)
+		return
+	}
+	msgID := string(b)
+
+	dc.ackMsgID(msgID)
 }
 
@@ -876,4 +943,18 @@
 }
 
+// messageSupportsHistory checks whether the provided message can be sent as
+// part of an history batch.
+func (dc *downstreamConn) messageSupportsHistory(msg *irc.Message) bool {
+	// Don't replay all messages, because that would mess up client
+	// state. For instance we just sent the list of users, sending
+	// PART messages for one of these users would be incorrect.
+	// TODO: add support for draft/event-playback
+	switch msg.Command {
+	case "PRIVMSG", "NOTICE":
+		return true
+	}
+	return false
+}
+
 func (dc *downstreamConn) sendNetworkHistory(net *network) {
 	if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
@@ -907,13 +988,5 @@
 
 		for _, msg := range history {
-			// Don't replay all messages, because that would mess up client
-			// state. For instance we just sent the list of users, sending
-			// PART messages for one of these users would be incorrect.
-			ignore := true
-			switch msg.Command {
-			case "PRIVMSG", "NOTICE":
-				ignore = false
-			}
-			if ignore {
+			if !dc.messageSupportsHistory(msg) {
 				continue
 			}
@@ -984,4 +1057,10 @@
 		})
 		return nil
+	case "PONG":
+		if len(msg.Params) == 0 {
+			return newNeedMoreParamsError(msg.Command)
+		}
+		token := msg.Params[len(msg.Params)-1]
+		dc.handlePong(token)
 	case "USER":
 		return ircError{&irc.Message{
Index: /trunk/upstream.go
===================================================================
--- /trunk/upstream.go	(revision 427)
+++ /trunk/upstream.go	(revision 428)
@@ -1608,7 +1608,11 @@
 }
 
-func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
+// appendLog appends a message to the log file.
+//
+// The internal message ID is returned. If the message isn't recorded in the
+// log file, an empty string is returned.
+func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string) {
 	if uc.user.msgStore == nil {
-		return
+		return ""
 	}
 
@@ -1623,5 +1627,5 @@
 		if err != nil {
 			uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
-			return
+			return ""
 		}
 
@@ -1647,12 +1651,8 @@
 	if err != nil {
 		uc.logger.Printf("failed to log message: %v", err)
-		return
-	}
-
-	if !detached && msgID != "" {
-		uc.forEachDownstream(func(dc *downstreamConn) {
-			history.clients[dc.clientName] = msgID
-		})
-	}
+		return ""
+	}
+
+	return msgID
 }
 
@@ -1663,6 +1663,7 @@
 // forwarded to all connections except origin.
 func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
+	var msgID string
 	if target != "" {
-		uc.appendLog(target, msg)
+		msgID = uc.appendLog(target, msg)
 	}
 
@@ -1674,5 +1675,7 @@
 	uc.forEachDownstream(func(dc *downstreamConn) {
 		if dc != origin || dc.caps["echo-message"] {
-			dc.SendMessage(dc.marshalMessage(msg, uc.network))
+			dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
+		} else {
+			dc.advanceMessageWithID(msg, msgID)
 		}
 	})
