diff --git a/src/sbbs3/getmsg.cpp b/src/sbbs3/getmsg.cpp
index a344af34387720cb78b4da13c3d6f443d87ae96c..0837a6f7a4e0b82cfb2ee98834e0c57c3e4cdb8a 100644
--- a/src/sbbs3/getmsg.cpp
+++ b/src/sbbs3/getmsg.cpp
@@ -119,19 +119,24 @@ void sbbs_t::show_msgattr(smbmsg_t* msg)
 /****************************************************************************/
 /* Displays a message header to the screen                                  */
 /****************************************************************************/
-void sbbs_t::show_msghdr(smbmsg_t* msg)
+void sbbs_t::show_msghdr(smb_t* smb, smbmsg_t* msg)
 {
 	char	str[MAX_PATH+1];
 	char	age[64];
 	char	*sender=NULL;
 	int 	i;
+	smb_t	saved_smb = this->smb;
 
-	attr(LIGHTGRAY);
-	if(useron.misc&CLRSCRN)
-		outchar(FF);
-	else
-		CRLF;
+	this->smb = *smb;	// Needed for @-codes and JS bbs.smb_* properties
+	current_msg = msg;	// Needed for @-codes and JS bbs.msg_* properties
 
+	attr(LIGHTGRAY);
+	if(!tos) {
+		if(useron.misc&CLRSCRN)
+			outchar(FF);
+		else
+			CRLF;
+	}
 	if(!menu("msghdr", P_NOERROR)) {
 		bprintf(text[MsgSubj],msg->subj);
 		if(msg->tags && *msg->tags)
@@ -170,23 +175,24 @@ void sbbs_t::show_msghdr(smbmsg_t* msg)
 			bprintf(text[ForwardedFrom],sender
 				,timestr(*(time32_t *)msg->hfield_dat[i])); 
 	}
+	this->smb = saved_smb;
 }
 
 /****************************************************************************/
 /* Displays message header and text (if not deleted)                        */
 /****************************************************************************/
-void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
+bool sbbs_t::show_msg(smb_t* smb, smbmsg_t* msg, long p_mode, post_t* post)
 {
 	char*	txt;
 
 	if((msg->hdr.type == SMB_MSG_TYPE_NORMAL && post != NULL && (post->upvotes || post->downvotes))
 		|| msg->hdr.type == SMB_MSG_TYPE_POLL)
-		msg->user_voted = smb_voted_already(&smb, msg->hdr.number
-					,cfg.sub[smb.subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL);
+		msg->user_voted = smb_voted_already(smb, msg->hdr.number
+					,cfg.sub[smb->subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL);
 
-	show_msghdr(msg);
+	show_msghdr(smb, msg);
 
-	if(msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL && smb.subnum < cfg.total_subs) {
+	if(msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL && smb->subnum < cfg.total_subs) {
 		char* answer;
 		int longest_answer = 0;
 
@@ -222,8 +228,8 @@ void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
 			bool results_visible = false;
 			if((msg->hdr.auxattr&POLL_RESULTS_MASK) == POLL_RESULTS_OPEN)
 				results_visible = true;
-			else if((msg->from_net.type == NET_NONE && sub_op(smb.subnum)) 
-				|| smb_msg_is_from(msg, cfg.sub[smb.subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL))
+			else if((msg->from_net.type == NET_NONE && sub_op(smb->subnum)) 
+				|| smb_msg_is_from(msg, cfg.sub[smb->subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL))
 				results_visible = true;
 			else if((msg->hdr.auxattr&POLL_RESULTS_MASK) == POLL_RESULTS_CLOSED)
 				results_visible = (msg->hdr.auxattr&POLL_CLOSED) ? true : false;
@@ -244,44 +250,46 @@ void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
 		}
 		if(!msg->user_voted && !(useron.misc&EXPERT) && !(msg->hdr.auxattr&POLL_CLOSED) && !(useron.rest&FLAG('V')))
 			mnemonics(text[VoteInThisPollNow]);
-		return;
+		return true;
 	}
-	if((txt=smb_getmsgtxt(&smb, msg, 0)) != NULL) {
-		char* p = txt;
-		if(!(console&CON_RAW_IN)) {
-			mode|=P_WORDWRAP;
-			p = smb_getplaintext(msg, txt);
-			if(p == NULL)
-				p = txt;
-			else
-				bputs(text[MIMEDecodedPlainText]);
-		}
-		truncsp(p);
-		SKIP_CRLF(p);
-		putmsg(p, mode);
-		smb_freemsgtxt(txt);
-		if(column)
-			CRLF;
-	}
-	if((txt=smb_getmsgtxt(&smb,msg,GETMSGTXT_TAIL_ONLY))!=NULL) {
-		putmsg(txt, mode&(~P_WORDWRAP));
-		smb_freemsgtxt(txt);
+	if((txt=smb_getmsgtxt(smb, msg, 0)) == NULL)
+		return false;
+	char* p = txt;
+	if(!(console&CON_RAW_IN)) {
+		p_mode|=P_WORDWRAP;
+		p = smb_getplaintext(msg, txt);
+		if(p == NULL)
+			p = txt;
+		else
+			bputs(text[MIMEDecodedPlainText]);
 	}
+	truncsp(p);
+	SKIP_CRLF(p);
+	putmsg(p, p_mode, msg->columns);
+	smb_freemsgtxt(txt);
+	if(column)
+		CRLF;
+	if((txt=smb_getmsgtxt(smb,msg,GETMSGTXT_TAIL_ONLY))==NULL)
+		return false;
+
+	putmsg(txt, p_mode&(~P_WORDWRAP));
+	smb_freemsgtxt(txt);
+	return true;
 }
 
 /****************************************************************************/
 /* Writes message header and text data to a text file						*/
 /****************************************************************************/
-void sbbs_t::msgtotxt(smbmsg_t* msg, char *str, bool header, ulong mode)
+bool sbbs_t::msgtotxt(smb_t* smb, smbmsg_t* msg, const char *fname, bool header, ulong gettxt_mode)
 {
 	char	*buf;
 	char	tmp[128];
 	int 	i;
 	FILE	*out;
 
-	if((out=fnopen(&i,str,O_WRONLY|O_CREAT|O_APPEND))==NULL) {
-		errormsg(WHERE,ERR_OPEN,str,0);
-		return; 
+	if((out=fnopen(&i,fname,O_WRONLY|O_CREAT|O_APPEND))==NULL) {
+		errormsg(WHERE,ERR_OPEN,fname,0);
+		return false; 
 	}
 	if(header) {
 		fprintf(out,"\r\n");
@@ -302,14 +310,17 @@ void sbbs_t::msgtotxt(smbmsg_t* msg, char *str, bool header, ulong mode)
 		fprintf(out,"\r\n\r\n"); 
 	}
 
-	buf=smb_getmsgtxt(&smb,msg,mode);
+	bool result = false;
+	buf=smb_getmsgtxt(smb, msg, gettxt_mode);
 	if(buf!=NULL) {
 		strip_invalid_attr(buf);
 		fputs(buf,out);
 		smb_freemsgtxt(buf); 
+		result = true;
 	} else if(smb_getmsgdatlen(msg)>2)
-		errormsg(WHERE,ERR_READ,smb.file,smb_getmsgdatlen(msg));
+		errormsg(WHERE,ERR_READ,smb->file,smb_getmsgdatlen(msg));
 	fclose(out);
+	return result;
 }
 
 /****************************************************************************/
diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp
index 5ab10728b4920b6050bcb746fb3aeb505131144a..03ffafc8e7e27790dc05d1c5e2e6a5883f9252f7 100644
--- a/src/sbbs3/js_bbs.cpp
+++ b/src/sbbs3/js_bbs.cpp
@@ -2632,7 +2632,7 @@ js_email(JSContext *cx, uintN argc, jsval *arglist)
 		else if(JSVAL_IS_OBJECT(argv[i])) {
 			if((hdrobj = JSVAL_TO_OBJECT(argv[i])) == NULL)
 				return JS_FALSE;
-			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg)) {
+			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg, /* post: */NULL)) {
 				if(!js_ParseMsgHeaderObject(cx, hdrobj, &msg))
 					return JS_FALSE;
 				remsg = &msg;
@@ -2668,13 +2668,11 @@ js_netmail(JSContext *cx, uintN argc, jsval *arglist)
 {
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32		mode=0;
-	const char	*def="";
-	char*		subj=(char *)def;
-	JSString*	js_to;
-	JSString*	js_subj=NULL;
+	char*		to = NULL;
+	char*		subj = NULL;
+	JSString*	js_str;
 	JSObject*	hdrobj;
 	sbbs_t*		sbbs;
-	char*		cstr;
 	smb_t*		resmb = NULL;
 	smbmsg_t*	remsg = NULL;
 	smbmsg_t	msg;
@@ -2689,20 +2687,23 @@ js_netmail(JSContext *cx, uintN argc, jsval *arglist)
 	if((sbbs=js_GetPrivate(cx, JS_THIS_OBJECT(cx, arglist)))==NULL)
 		return(JS_FALSE);
 
-	if((js_to=JS_ValueToString(cx, argv[0]))==NULL)
-		return(JS_FALSE);
-
-	for(uintN i=1;i<argc;i++) {
+	for(uintN i=0; i<argc; i++) {
 		if(JSVAL_IS_NUMBER(argv[i])) {
 			if(!JS_ValueToInt32(cx,argv[i],&mode))
 				return JS_FALSE;
 		}
-		else if(JSVAL_IS_STRING(argv[i]))
-			js_subj=JS_ValueToString(cx,argv[i]);
+		else if(JSVAL_IS_STRING(argv[i])) {
+			js_str = JS_ValueToString(cx, argv[i]);
+			if(to == NULL) {
+				JSSTRING_TO_MSTRING(cx, js_str, to, NULL);
+			} else if(subj == NULL) {
+				JSSTRING_TO_MSTRING(cx, js_str, subj, NULL);
+			}
+		}
 		else if(JSVAL_IS_OBJECT(argv[i])) {
 			if((hdrobj = JSVAL_TO_OBJECT(argv[i])) == NULL)
 				return JS_FALSE;
-			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg)) {
+			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg, /* post: */NULL)) {
 				if(!js_ParseMsgHeaderObject(cx, hdrobj, &msg))
 					return JS_FALSE;
 				remsg = &msg;
@@ -2710,24 +2711,11 @@ js_netmail(JSContext *cx, uintN argc, jsval *arglist)
 		}
 	}
 
-	if(js_subj!=NULL) {
-		JSSTRING_TO_MSTRING(cx, js_subj, subj, NULL);
-		if(subj==NULL)
-			return JS_FALSE;
-	}
-
-	JSSTRING_TO_MSTRING(cx, js_to, cstr, NULL);
-	if(cstr==NULL) {
-		if(subj != def)
-			free(subj);
-		return JS_FALSE;
-	}
 	rc=JS_SUSPENDREQUEST(cx);
-	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->netmail(cstr, subj, mode, resmb, remsg)));
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->netmail(to, subj, mode, resmb, remsg)));
 	smb_freemsgmem(&msg);
-	if(subj != def)
-		free(subj);
-	free(cstr);
+	FREE_AND_NULL(subj);
+	FREE_AND_NULL(to);
 	JS_RESUMEREQUEST(cx, rc);
 	return(JS_TRUE);
 }
@@ -3543,7 +3531,7 @@ js_post_msg(JSContext *cx, uintN argc, jsval *arglist)
 		else if(JSVAL_IS_OBJECT(argv[n])) {
 			if((hdrobj=JSVAL_TO_OBJECT(argv[n]))==NULL)
 				return JS_FALSE;
-			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg)) {
+			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &resmb, &remsg, /* post: */NULL)) {
 				if(!js_ParseMsgHeaderObject(cx, hdrobj, &msg))
 					return JS_FALSE;
 				remsg = &msg;
@@ -3559,6 +3547,82 @@ js_post_msg(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+static JSBool
+js_show_msg(JSContext *cx, uintN argc, jsval *arglist)
+{
+	jsval *argv=JS_ARGV(cx, arglist);
+	int32		p_mode = 0;
+	uintN		n;
+	JSObject*	hdrobj;
+	sbbs_t*		sbbs;
+	smb_t*		smb = NULL;
+	smbmsg_t*	msg = NULL;
+	post_t*		post = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if((sbbs=js_GetPrivate(cx, JS_THIS_OBJECT(cx, arglist)))==NULL)
+		return(JS_FALSE);
+
+	for(n=0; n<argc; n++) {
+		if(JSVAL_IS_NUMBER(argv[n])) {
+			if(!JS_ValueToInt32(cx, argv[n], &p_mode))
+				return JS_FALSE;
+		}
+		else if(JSVAL_IS_OBJECT(argv[n])) {
+			if((hdrobj=JSVAL_TO_OBJECT(argv[n]))==NULL)
+				return JS_FALSE;
+			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &smb, &msg, &post)) {
+				return JS_FALSE;
+			}
+		}
+	}
+	if(smb == NULL || msg == NULL)
+		return JS_TRUE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->show_msg(smb, msg, p_mode, post)));
+	JS_RESUMEREQUEST(cx, rc);
+
+	return(JS_TRUE);
+}
+
+static JSBool
+js_show_msg_header(JSContext *cx, uintN argc, jsval *arglist)
+{
+	jsval *argv=JS_ARGV(cx, arglist);
+	uintN		n;
+	JSObject*	hdrobj;
+	sbbs_t*		sbbs;
+	smb_t*		smb = NULL;
+	smbmsg_t*	msg = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
+
+	if((sbbs=js_GetPrivate(cx, JS_THIS_OBJECT(cx, arglist)))==NULL)
+		return(JS_FALSE);
+
+	for(n=0; n<argc; n++) {
+		if(JSVAL_IS_OBJECT(argv[n])) {
+			if((hdrobj=JSVAL_TO_OBJECT(argv[n]))==NULL)
+				return JS_FALSE;
+			if(!js_GetMsgHeaderObjectPrivates(cx, hdrobj, &smb, &msg, NULL)) {
+				return JS_FALSE;
+			}
+		}
+	}
+	if(smb == NULL || msg == NULL)
+		return JS_TRUE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	sbbs->show_msghdr(smb, msg);
+	JS_RESUMEREQUEST(cx, rc);
+
+	return(JS_TRUE);
+}
+
 static JSBool
 js_msgscan_cfg(JSContext *cx, uintN argc, jsval *arglist)
 {
@@ -4138,11 +4202,21 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 	},
 	{"post_msg",		js_post_msg,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("[sub-board=<i>current</i>] [,mode=<tt>WM_NONE</tt>] [,object reply_header]")
 	,JSDOCSTR("post a message in the specified message sub-board (number or internal code) "
-		"with optinal <i>mode</i> (bitfield)<br>"
+		"with optional <i>mode</i> (bitfield)<br>"
 		"If <i>reply_header</i> is specified (a header object returned from <i>MsgBase.get_msg_header()</i>), that header "
 		"will be used for the in-reply-to header fields.")
 	,313
 	},
+	{"show_msg",		js_show_msg,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("[object header] [,mode=<tt>P_NONE</tt>] ")
+	,JSDOCSTR("show a message's header and body (text) with optional print <i>mode</i> (bitfield)<br>"
+		"<i>header</i> must be a header object returned from <i>MsgBase.get_msg_header()</i>)")
+	,31702
+	},
+	{"show_msg_header",	js_show_msg_header,	1,	JSTYPE_VOID,	JSDOCSTR("[object header]")
+	,JSDOCSTR("show a message's header (only)<br>"
+		"<i>header</i> must be a header object returned from <i>MsgBase.get_msg_header()</i>)")
+	,31702
+	},
 	{"cfg_msg_scan",	js_msgscan_cfg,		0,	JSTYPE_VOID,	JSDOCSTR("[type=<tt>SCAN_CFG_NEW</tt>]")
 	,JSDOCSTR("configure message scan "
 		"(<i>type</i> is either <tt>SCAN_CFG_NEW</tt> or <tt>SCAN_CFG_TOYOU</tt>)")
diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp
index e15af41ccf2d164c21f119345c3778e40492f22f..83b7d319d177358b70481a31f8c0bb16ba6a4270 100644
--- a/src/sbbs3/js_console.cpp
+++ b/src/sbbs3/js_console.cpp
@@ -1156,6 +1156,7 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
 {
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32		mode=0;
+	int32		columns=0;
     JSString*	str;
 	sbbs_t*		sbbs;
 	char*		cstr;
@@ -1174,12 +1175,16 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
 		if(!JS_ValueToInt32(cx,argv[1],&mode))
 			return JS_FALSE;
 	}
+	if(argc>2 && JSVAL_IS_NUMBER(argv[2])) {
+		if(!JS_ValueToInt32(cx,argv[2],&columns))
+			return JS_FALSE;
+	}
 
 	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
 	if(cstr==NULL)
 		return JS_FALSE;
 	rc=JS_SUSPENDREQUEST(cx);
-	sbbs->putmsg(cstr,mode);
+	sbbs->putmsg(cstr, mode, columns);
 	free(cstr);
 	JS_RESUMEREQUEST(cx, rc);
     return(JS_TRUE);
@@ -1921,9 +1926,10 @@ static jsSyncMethodSpec js_console_functions[] = {
 	,JSDOCSTR("display one or more values as raw strings followed by a single carriage-return/line-feed pair (new-line)")
 	,315
 	},
-	{"putmsg",			js_putmsg,			1, JSTYPE_VOID,		JSDOCSTR("text [,mode=<tt>P_NONE</tt>]")
+	{"putmsg",			js_putmsg,			1, JSTYPE_VOID,		JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0]")
 	,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc), "
-		"see <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits")
+		"see <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits.<br>"
+		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known")
 	,310
 	},
 	{"center",			js_center,			1, JSTYPE_VOID,		JSDOCSTR("text")
diff --git a/src/sbbs3/js_msgbase.c b/src/sbbs3/js_msgbase.c
index 16039843e04316c225a887c536747ebaf96d2050..37e7d97c8c558d5a60557e4f8e2d84039e1c48a7 100644
--- a/src/sbbs3/js_msgbase.c
+++ b/src/sbbs3/js_msgbase.c
@@ -53,6 +53,7 @@ typedef struct
 	private_t	*p;
 	BOOL		expand_fields;
 	smbmsg_t	msg;
+	post_t		post;
 
 } privatemsg_t;
 
@@ -954,7 +955,7 @@ BOOL DLLCALL js_ParseMsgHeaderObject(JSContext* cx, JSObject* obj, smbmsg_t* msg
 }
 
 /* obj must've been previously returned from get_msg_header() */
-BOOL DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* obj, smb_t** smb, smbmsg_t** msg)
+BOOL DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* obj, smb_t** smb, smbmsg_t** msg, post_t** post)
 {
 	privatemsg_t*	p;
 
@@ -968,6 +969,8 @@ BOOL DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* obj, smb_t**
 	}
 	if(msg != NULL)
 		*msg = &p->msg;
+	if(post != NULL)
+		*post = &p->post;
 
 	return TRUE;
 }
@@ -1674,6 +1677,7 @@ js_get_all_msg_headers(JSContext *cx, uintN argc, jsval *arglist)
     JSObject*	retobj;
 	char		numstr[16];
 	JSBool		include_votes=JS_FALSE;
+	JSBool		expand_fields=JS_TRUE;
 	post_t*		post;
 	idxrec_t*	idx;
 
@@ -1706,8 +1710,15 @@ js_get_all_msg_headers(JSContext *cx, uintN argc, jsval *arglist)
 		return JS_FALSE;
 	}
 
-	if(argc && JSVAL_IS_BOOLEAN(argv[0]))
-		include_votes = JSVAL_TO_BOOLEAN(argv[0]);
+	uintN argn = 0;
+	if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) {
+		include_votes = JSVAL_TO_BOOLEAN(argv[argn]);
+		argn++;
+	}
+	if(argn < argc && JSVAL_IS_BOOLEAN(argv[argn])) {
+		expand_fields = JSVAL_TO_BOOLEAN(argv[argn]);
+		argn++;
+	}
 
     retobj = JS_NewObject(cx, NULL, NULL, obj);
     JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(retobj));
@@ -1782,9 +1793,10 @@ js_get_all_msg_headers(JSContext *cx, uintN argc, jsval *arglist)
 
 		/* Parse boolean arguments first */
 		p->p=priv;
-		p->expand_fields=JS_TRUE;	/* This parameter defaults to true */
+		p->expand_fields = expand_fields;
 
 		p->msg.idx = post[off].idx;
+		p->post = post[off];
 
 		rc=JS_SUSPENDREQUEST(cx);
 		priv->smb_result = smb_getmsghdr(&(priv->smb), &(p->msg));
@@ -1848,8 +1860,8 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist)
 	jsval *argv=JS_ARGV(cx, arglist);
 	uintN		n;
 	JSBool		by_offset=JS_FALSE;
-	JSBool		msg_specified=JS_FALSE;
 	smbmsg_t	msg;
+	smbmsg_t*	modmsg = &msg;
 	JSObject*	hdr=NULL;
 	private_t*	p;
 	jsrefcount	rc;
@@ -1880,9 +1892,6 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist)
 				if(!JS_ValueToInt32(cx,argv[n],(int32*)&msg.hdr.number))
 					return JS_FALSE;
 			}
-			msg_specified=JS_TRUE;
-			n++;
-			break;
 		} else if(JSVAL_IS_STRING(argv[n]))	{		/* Get by ID */
 			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(argv[n]), cstr, NULL);
 			HANDLE_PENDING(cx, cstr);
@@ -1896,59 +1905,57 @@ js_put_msg_header(JSContext *cx, uintN argc, jsval *arglist)
 			}
 			free(cstr);
 			JS_RESUMEREQUEST(cx, rc);
-			msg_specified=JS_TRUE;
-			n++;
-			break;
+		}
+		else if(JSVAL_IS_OBJECT(argv[n])) {
+			hdr = JSVAL_TO_OBJECT(argv[n++]);
 		}
 	}
 
-	if(!msg_specified)
-		return JS_TRUE;
-
-	if(n==argc || !JSVAL_IS_OBJECT(argv[n])) /* no header supplied? */
+	if(hdr == NULL)		/* no header supplied? */
 		return JS_TRUE;
 
-	hdr = JSVAL_TO_OBJECT(argv[n++]);
-
 	privatemsg_t* mp;
 	mp=(privatemsg_t*)JS_GetPrivate(cx,hdr);
-	if(mp != NULL && mp->expand_fields) {
-		JS_ReportError(cx, "Message header has 'expanded fields'", WHERE);
-		return JS_FALSE;
+	if(mp != NULL) {
+		if(mp->expand_fields) {
+			JS_ReportError(cx, "Message header has 'expanded fields'", WHERE);
+			return JS_FALSE;
+		}
+		modmsg = &mp->msg;
 	}
 
 	rc=JS_SUSPENDREQUEST(cx);
-	if((p->smb_result=smb_getmsgidx(&(p->smb), &msg))!=SMB_SUCCESS) {
+	if((p->smb_result=smb_getmsgidx(&(p->smb), modmsg))!=SMB_SUCCESS) {
 		JS_RESUMEREQUEST(cx, rc);
 		return JS_TRUE;
 	}
 
-	if((p->smb_result=smb_lockmsghdr(&(p->smb),&msg))!=SMB_SUCCESS) {
+	if((p->smb_result=smb_lockmsghdr(&(p->smb),modmsg))!=SMB_SUCCESS) {
 		JS_RESUMEREQUEST(cx, rc);
 		return JS_TRUE;
 	}
 
 	do {
-		if((p->smb_result=smb_getmsghdr(&(p->smb), &msg))!=SMB_SUCCESS)
+		if((p->smb_result=smb_getmsghdr(&(p->smb), modmsg))!=SMB_SUCCESS)
 			break;
 
-		smb_freemsghdrmem(&msg);	/* prevent duplicate header fields */
+		smb_freemsghdrmem(modmsg);	/* prevent duplicate header fields */
 
 		JS_RESUMEREQUEST(cx, rc);
-		if(!parse_header_object(cx, p, hdr, &msg, TRUE)) {
+		if(!parse_header_object(cx, p, hdr, modmsg, TRUE)) {
 			SAFECOPY(p->smb.last_error,"Header parsing failure (required field missing?)");
 			ret=JS_FALSE;
 			break;
 		}
 		rc=JS_SUSPENDREQUEST(cx);
 
-		if((p->smb_result=smb_putmsg(&(p->smb), &msg))!=SMB_SUCCESS)
+		if((p->smb_result=smb_putmsg(&(p->smb), modmsg))!=SMB_SUCCESS)
 			break;
 
 		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
 	} while(0);
 
-	smb_unlockmsghdr(&(p->smb),&msg);
+	smb_unlockmsghdr(&(p->smb),modmsg); 
 	smb_freemsgmem(&msg);
 	JS_RESUMEREQUEST(cx, rc);
 
@@ -2876,14 +2883,14 @@ static jsSyncMethodSpec js_msgbase_functions[] = {
 	"if you will be re-writing the header later with <i>put_msg_header()</i>")
 	,312
 	},
-	{"get_all_msg_headers", js_get_all_msg_headers, 1, JSTYPE_ARRAY, JSDOCSTR("[include_votes=<tt>false</tt>]")
+	{"get_all_msg_headers", js_get_all_msg_headers, 1, JSTYPE_ARRAY, JSDOCSTR("[include_votes=<tt>false</tt>] [,expand_fields=<tt>true</tt>]")
 	,JSDOCSTR("returns an object of all message headers indexed by message number.<br>"
 	"Message headers returned by this function include 2 additional properties: <tt>upvotes</tt> and <tt>downvotes</tt>.<br>"
 	"Vote messages are excluded by default.")
 	,316
 	},
-	{"put_msg_header",	js_put_msg_header,	2, JSTYPE_BOOLEAN,	JSDOCSTR("[by_offset=<tt>false</tt>,] number_or_offset, object header")
-	,JSDOCSTR("modify an existing message header")
+	{"put_msg_header",	js_put_msg_header,	2, JSTYPE_BOOLEAN,	JSDOCSTR("[by_offset=<tt>false</tt>,] [number_or_offset_or_id,] object header")
+	,JSDOCSTR("modify an existing message header (must have been 'got' without expanded fields)")
 	,310
 	},
 	{"get_msg_body",	js_get_msg_body,	2, JSTYPE_STRING,	JSDOCSTR("[by_offset=<tt>false</tt>,] number_or_offset_or_id_or_header [,strip_ctrl_a=<tt>false</tt>] "
diff --git a/src/sbbs3/putmsg.cpp b/src/sbbs3/putmsg.cpp
index 8b47738fb86c09b62ae3e0945d33c1794a63483c..01553e8b5b6e09ff07fcc864e8cf3504333a1c73 100644
--- a/src/sbbs3/putmsg.cpp
+++ b/src/sbbs3/putmsg.cpp
@@ -47,7 +47,7 @@
 /* the attributes prior to displaying the message are always restored.      */
 /* Stops parsing/displaying upon CTRL-Z (only in P_CPM_EOF mode).           */
 /****************************************************************************/
-char sbbs_t::putmsg(const char *buf, long mode)
+char sbbs_t::putmsg(const char *buf, long mode, long org_cols)
 {
 	char	tmpatr,tmp2[256],tmp3[128];
 	char	ret;
@@ -77,7 +77,9 @@ char sbbs_t::putmsg(const char *buf, long mode)
 				*wrapoff = 0;
 		}
 		char *wrapped;
-		if((wrapped=::wordwrap((char*)str+l, cols-1, 79, /* handle_quotes: */TRUE)) == NULL)
+		if(org_cols < TERM_COLS_MIN)
+			org_cols = TERM_COLS_DEFAULT;
+		if((wrapped=::wordwrap((char*)str+l, cols - 1, org_cols - 1, /* handle_quotes: */TRUE)) == NULL)
 			errormsg(WHERE,ERR_ALLOC,"wordwrap buffer",0);
 		else {
 			truncsp_lines(wrapped);
@@ -305,7 +307,7 @@ char sbbs_t::putmsg(const char *buf, long mode)
 				}
 				if(memcmp(str+l, "@WORDWRAP@", 10) == 0) {
 					l += 10;
-					putmsg(str+l, mode|P_WORDWRAP);
+					putmsg(str+l, mode|P_WORDWRAP, org_cols);
 					break;
 				}
 
diff --git a/src/sbbs3/readmail.cpp b/src/sbbs3/readmail.cpp
index fef033743cecf519aebb3683e59333c387a20c3e..543ada4a9b9f01ed4023b558ade30004e7eaf710 100644
--- a/src/sbbs3/readmail.cpp
+++ b/src/sbbs3/readmail.cpp
@@ -259,7 +259,7 @@ void sbbs_t::readmail(uint usernumber, int which, long lm_mode)
 
 		if(domsg && !(sys_status&SS_ABORT)) {
 
-			show_msg(&msg
+			show_msg(&smb, &msg
 				,msg.from_ext && msg.idx.from==1 && !msg.from_net.type
 					? 0:P_NOATCODES);
 
@@ -739,7 +739,7 @@ void sbbs_t::readmail(uint usernumber, int which, long lm_mode)
 	*/
 				bputs(text[FileToWriteTo]);
 				if(getstr(str,50,K_LINE))
-					msgtotxt(&msg,str, /* header: */true, /* mode: */GETMSGTXT_ALL);
+					msgtotxt(&smb, &msg, str, /* header: */true, /* mode: */GETMSGTXT_ALL);
 				break;
 			case 'E':
 				editmsg(&msg,INVALID_SUB);
diff --git a/src/sbbs3/readmsgs.cpp b/src/sbbs3/readmsgs.cpp
index 2f5b01fe356f5ffc859cc0aa4a25879a9df13017..1ecaffb61fb27d3690bdd75daeac6c3a24be9a1d 100644
--- a/src/sbbs3/readmsgs.cpp
+++ b/src/sbbs3/readmsgs.cpp
@@ -771,7 +771,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 			msg.upvotes = post[smb.curmsg].upvotes;
 			msg.downvotes = post[smb.curmsg].downvotes;
 			msg.total_votes = post[smb.curmsg].total_votes;
-			show_msg(&msg
+			show_msg(&smb, &msg
 				,msg.from_ext && !strcmp(msg.from_ext,"1") && !msg.from_net.type
 					? 0:P_NOATCODES
 				,&post[smb.curmsg]);
@@ -1365,7 +1365,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 	*/
 							bputs(text[FileToWriteTo]);
 							if(getstr(str,50,K_LINE))
-								msgtotxt(&msg,str, /* header: */true, /* mode: */GETMSGTXT_ALL);
+								msgtotxt(&smb, &msg, str, /* header: */true, /* mode: */GETMSGTXT_ALL);
 							break;
 						case 'U':   /* User edit */
 							useredit(cfg.sub[subnum]->misc&SUB_NAME
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index bdccfbf1f25c47ec0ed01b6d1c0aa2441a60b329..07c532f85e2623f0d71e0ec7fb4d7e957010b320 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -645,7 +645,7 @@ public:
 				,const char *to, const char* from, char** editor=NULL);
 	char*	quotes_fname(int xedit, char* buf, size_t len);
 	char*	msg_tmp_fname(int xedit, char* fname, size_t len);
-	char	putmsg(const char *str, long mode);
+	char	putmsg(const char *str, long mode, long org_cols = 0);
 	bool	msgabort(void);
 	bool	email(int usernumber, const char *top = NULL, const char *title = NULL
 				, long mode = WM_NONE, smb_t* resmb = NULL, smbmsg_t* remsg = NULL);
@@ -674,9 +674,9 @@ public:
 	/* getmsg.cpp */
 	int		loadmsg(smbmsg_t *msg, ulong number);
 	void	show_msgattr(smbmsg_t*);
-	void	show_msghdr(smbmsg_t* msg);
-	void	show_msg(smbmsg_t* msg, long mode, post_t* post = NULL);
-	void	msgtotxt(smbmsg_t* msg, char *str, bool header, ulong mode);
+	void	show_msghdr(smb_t*, smbmsg_t*);
+	bool	show_msg(smb_t*, smbmsg_t*, long p_mode = 0, post_t* post = NULL);
+	bool	msgtotxt(smb_t*, smbmsg_t*, const char *fname, bool header = true, ulong gettxt_mode = GETMSGTXT_ALL);
 	ulong	getlastmsg(uint subnum, uint32_t *ptr, time_t *t);
 	time_t	getmsgtime(uint subnum, ulong ptr);
 	ulong	getmsgnum(uint subnum, time_t t);
@@ -1317,7 +1317,7 @@ extern "C" {
 	/* js_msgbase.c */
 	DLLEXPORT JSObject* DLLCALL js_CreateMsgBaseClass(JSContext* cx, JSObject* parent, scfg_t* cfg);
 	DLLEXPORT BOOL		DLLCALL js_ParseMsgHeaderObject(JSContext* cx, JSObject* hdrobj, smbmsg_t*);
-	DLLEXPORT BOOL		DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* hdrobj, smb_t**, smbmsg_t**);
+	DLLEXPORT BOOL		DLLCALL js_GetMsgHeaderObjectPrivates(JSContext* cx, JSObject* hdrobj, smb_t**, smbmsg_t**, post_t**);
 
 	/* js_socket.c */
 	DLLEXPORT JSObject* DLLCALL js_CreateSocketClass(JSContext* cx, JSObject* parent);
diff --git a/src/sbbs3/writemsg.cpp b/src/sbbs3/writemsg.cpp
index 05bddd041f26ffff9818994306f7d9edb397e971..e2de158202a26b15f626adf9eeac6125837ecf2f 100644
--- a/src/sbbs3/writemsg.cpp
+++ b/src/sbbs3/writemsg.cpp
@@ -1370,7 +1370,7 @@ void sbbs_t::editmsg(smbmsg_t *msg, uint subnum)
 
 	msg_tmp_fname(useron.xedit, msgtmp, sizeof(msgtmp));
 	removecase(msgtmp);
-	msgtotxt(msg,msgtmp, /* header: */false, /* mode: */GETMSGTXT_ALL);
+	msgtotxt(&smb, msg, msgtmp, /* header: */false, /* mode: */GETMSGTXT_ALL);
 	if(!editfile(msgtmp, /* msg: */true))
 		return;
 	length=(long)flength(msgtmp);