diff --git a/editor/tview.go b/editor/tview.go
index 543215b51da345f20cc198fea558776a777a6efd..c4d5a5801802def8a886bf3c2ec12504b9672f58 100644
--- a/editor/tview.go
+++ b/editor/tview.go
@@ -9,7 +9,7 @@ import (
 )
 
 // Editor is the editor for text files.
-func Editor(placeholder, title string) (string, error) {
+func Editor(placeholder, title, message string) (string, error) {
 	theme := tview.Theme{
 		PrimitiveBackgroundColor:    tcell.ColorDefault,
 		ContrastBackgroundColor:     tcell.ColorDefault,
@@ -138,6 +138,9 @@ Press Enter for more help, press Escape to return.`)
 		return event
 	})
 
+	if message != "" {
+		textArea.SetText(message, true)
+	}
 	err := app.SetRoot(pages, true).Run()
 	if err != nil {
 		return "", err
diff --git a/folders/messages.go b/folders/messages.go
index 92f859bffd994afa53dbd3675f862ef5397a3e95..8450cf4040a34e0d5c38a0b621a1ba51020b9f88 100644
--- a/folders/messages.go
+++ b/folders/messages.go
@@ -41,7 +41,7 @@ func CreateMessage(author, subject, message, folder string, permanent, shutdown
 // ReadMessage reads a message for a user.
 func ReadMessage(login, folder string, msgid int64) (*storage.Message, error) {
 	ctx := storage.Context()
-	fmt.Printf("TODO: Make sure %s can read this message.\n", login)
+	fmt.Printf("TODO(ReadMessage): Make sure %s can read this message.\n", login)
 	msg, err := this.Q.ReadMessage(ctx, storage.ReadMessageParams{
 		Folder: folder,
 		ID:     msgid,
diff --git a/repl/command.go b/repl/command.go
index b2b37b93d86791b80420e4d8dc89a2a80919d63d..43ee9340e5961bb89b3414602d5fdea03f6bd32c 100644
--- a/repl/command.go
+++ b/repl/command.go
@@ -834,6 +834,7 @@ the same as the ADD command except for /NOINDENT and /EXTRACT.
   message. This is  the default - suppress with /NOEXTRACT.  The text of
   the message is indented with > at the beginning of each line. This can
   be suppressed with /NOINDENT.`,
+				Default: "true",
 			},
 			"/INDENT": {
 				Description: `  See /EXTRACT for information on this qualifier.
diff --git a/repl/messages.go b/repl/messages.go
index 6c9ff65f7ff05e003d05711dfef2e84950ab227b..bf805ee851f8897429e09aba26828ca80fa2b890 100644
--- a/repl/messages.go
+++ b/repl/messages.go
@@ -138,7 +138,10 @@ func ActionAdd(cmd *dclish.Command) error {
 	}
 	// TODO: check we have permission for shutdown and permanent
 
-	message, err := editor.Editor(fmt.Sprintf("Enter message for '%s'...", optSubject), "Edit message")
+	message, err := editor.Editor(
+		fmt.Sprintf("Enter message for '%s'...", optSubject),
+		"Edit message",
+		"")
 	if err != nil {
 		return err
 	}
@@ -293,7 +296,45 @@ func ActionRead(cmd *dclish.Command) error {
 
 // ActionReply handles the `REPLY` command.
 func ActionReply(cmd *dclish.Command) error {
-	fmt.Printf("TODO: unimplemented...\n%s\n", cmd.Description)
+	extract := true
+	if cmd.Flags["/EXTRACT"].Value == "false" {
+		extract = false
+	}
+	indent := true
+	if cmd.Flags["/INDENT"].Value == "false" {
+		indent = false
+	}
+	fmt.Printf("TODO: getting message %d.\n", this.MsgID)
+	original, err := folders.ReadMessage(this.User.Login, this.Folder.Name, this.MsgID)
+	origMsg := ""
+	if extract {
+		if indent {
+			origMsg = "> " + strings.Join(strings.Split(original.Message, "\n"), "\n> ")
+			fmt.Println("TODO: extract and indent are true.")
+		} else {
+			origMsg = original.Message
+			fmt.Println("TODO: extract is true.")
+		}
+	} else {
+		fmt.Println("TODO: neither is true.")
+	}
+
+	// TODO; this isn't working - the message isn't getting loaded.
+	subject := "Re: " + original.Subject
+	message, err := editor.Editor(
+		fmt.Sprintf("Enter message for '%s'...", subject),
+		"Edit message",
+		origMsg)
+	if err != nil {
+		fmt.Printf("ERROR: Editor failure (%s).\n", err)
+		return nil
+	}
+	err = folders.CreateMessage(this.User.Login, subject,
+		message, this.Folder.Name, 0, 0, nil)
+	if err != nil {
+		fmt.Printf("ERROR: CreateMessage failure (%s).\n", err)
+		return nil
+	}
 	return nil
 }
 
diff --git a/storage/messages.sql.go b/storage/messages.sql.go
index a916720e298ecb15511515134c9fa06f54b16edb..d804c873c029be4111beea24076c144d5af24a42 100644
--- a/storage/messages.sql.go
+++ b/storage/messages.sql.go
@@ -15,12 +15,12 @@ INSERT INTO messages (
   id, folder, author, subject, message, permanent, shutdown, expiration
 ) VALUES (
   (SELECT COALESCE(MAX(id), 0) + 1 FROM messages AS m WHERE m.folder = ?1),
-  ?2, ?1, ?3, ?4, ?5, ?6, ?7)
+  ?1, ?2, ?3, ?4, ?5, ?6, ?7)
 `
 
 type CreateMessageParams struct {
-	Author     string
 	Folder     string
+	Author     string
 	Subject    string
 	Message    string
 	Permanent  int64
@@ -30,8 +30,8 @@ type CreateMessageParams struct {
 
 func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) error {
 	_, err := q.db.ExecContext(ctx, createMessage,
-		arg.Author,
 		arg.Folder,
+		arg.Author,
 		arg.Subject,
 		arg.Message,
 		arg.Permanent,
@@ -162,7 +162,7 @@ func (q *Queries) LastMsgidIgnoringSeen(ctx context.Context, folder string) (int
 const nextMsgid = `-- name: NextMsgid :one
 SELECT CAST(COALESCE(MIN(id), 0) AS INT) FROM messages AS m
   WHERE m.folder = ?1 AND m.id > ?2
-  AND id NOT IN (SELECT id FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3)
+  AND id NOT IN (SELECT msgid FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3)
 `
 
 type NextMsgidParams struct {
@@ -198,7 +198,7 @@ func (q *Queries) NextMsgidIgnoringSeen(ctx context.Context, arg NextMsgidIgnori
 const prevMsgid = `-- name: PrevMsgid :one
 SELECT CAST(COALESCE(MAX(id), 0) AS INT) FROM messages AS m
   WHERE m.folder = ?1 AND m.id < ?2
-  AND id NOT IN (SELECT id FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3)
+  AND id NOT IN (SELECT msgid FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3)
 `
 
 type PrevMsgidParams struct {
diff --git a/storage/queries/messages.sql b/storage/queries/messages.sql
index aaf975c1eeeddc40ca5979125a6f67094d84bf41..d6f48c654c0157869d4a1a7012b61d6eeed1cd1d 100644
--- a/storage/queries/messages.sql
+++ b/storage/queries/messages.sql
@@ -3,7 +3,7 @@ INSERT INTO messages (
   id, folder, author, subject, message, permanent, shutdown, expiration
 ) VALUES (
   (SELECT COALESCE(MAX(id), 0) + 1 FROM messages AS m WHERE m.folder = ?1),
-  ?2, ?1, ?3, ?4, ?5, ?6, ?7);
+  ?1, ?2, ?3, ?4, ?5, ?6, ?7);
 
 -- name: SetMessageSeen :exec
 INSERT INTO seen (login, folder, msgid) VALUES (?, ?, ?);
@@ -17,7 +17,7 @@ SELECT * FROM messages WHERE folder = ? AND id = ?;
 -- name: NextMsgid :one
 SELECT CAST(COALESCE(MIN(id), 0) AS INT) FROM messages AS m
   WHERE m.folder = ?1 AND m.id > ?2
-  AND id NOT IN (SELECT id FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3);
+  AND id NOT IN (SELECT msgid FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3);
 
 -- name: NextMsgidIgnoringSeen :one
 SELECT CAST(MIN(id) AS INT) FROM messages AS m
@@ -26,7 +26,7 @@ SELECT CAST(MIN(id) AS INT) FROM messages AS m
 -- name: PrevMsgid :one
 SELECT CAST(COALESCE(MAX(id), 0) AS INT) FROM messages AS m
   WHERE m.folder = ?1 AND m.id < ?2
-  AND id NOT IN (SELECT id FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3);
+  AND id NOT IN (SELECT msgid FROM seen AS s WHERE s.folder = ?1 AND s.login = ?3);
 
 -- name: PrevMsgidIgnoringSeen :one
 SELECT CAST(MAX(id) AS INT) FROM messages AS m