diff --git a/src/sbbs3/getmsg.cpp b/src/sbbs3/getmsg.cpp
index 0837a6f7a4e0b82cfb2ee98834e0c57c3e4cdb8a..9ba56172a2cffc420da08e514cc0fc0c5414e71b 100644
--- a/src/sbbs3/getmsg.cpp
+++ b/src/sbbs3/getmsg.cpp
@@ -277,6 +277,119 @@ bool sbbs_t::show_msg(smb_t* smb, smbmsg_t* msg, long p_mode, post_t* post)
 	return true;
 }
 
+void sbbs_t::download_msg_attachments(smb_t* smb, smbmsg_t* msg, bool del)
+{
+	char str[256];
+	char fpath[MAX_PATH+1];
+	char* txt;
+	int attachment_index = 0;
+	bool found = true;
+	while((txt=smb_getmsgtxt(smb, msg, 0)) != NULL && found) {
+		char filename[MAX_PATH+1] = {0};
+		uint32_t filelen = 0;
+		uint8_t* filedata;
+		if((filedata = smb_getattachment(msg, txt, filename, &filelen, attachment_index++)) != NULL 
+			&& filename[0] != 0 && filelen > 0) {
+			char tmp[32];
+			SAFEPRINTF2(str, text[DownloadAttachedFileQ], filename, ultoac(filelen,tmp));
+			if(!noyes(str)) {
+				SAFEPRINTF2(fpath, "%s%s", cfg.temp_dir, filename);
+				FILE* fp = fopen(fpath, "wb");
+				if(fp == NULL)
+					errormsg(WHERE, ERR_OPEN, fpath, 0);
+				else {
+					int result = fwrite(filedata, filelen, 1, fp);
+					fclose(fp);
+					if(!result)
+						errormsg(WHERE, ERR_WRITE, fpath, filelen);
+					else
+						sendfile(fpath, useron.prot, "attachment");
+				}
+			}
+		} else
+			found = false;
+		smb_freemsgtxt(txt);
+	}
+
+	if(msg->hdr.auxattr&MSG_FILEATTACH) {  /* Attached file */
+		smb_getmsgidx(smb, msg);
+		SAFECOPY(str, msg->subj);					/* filenames (multiple?) in title */
+		char *p,*tp,*sp,ch;
+		tp=str;
+		while(online) {
+			p=strchr(tp,' ');
+			if(p) *p=0;
+			sp=strrchr(tp,'/');              /* sp is slash pointer */
+			if(!sp) sp=strrchr(tp,'\\');
+			if(sp) tp=sp+1;
+			file_t	fd;
+			padfname(tp,fd.name);
+			SAFEPRINTF3(fpath,"%sfile/%04u.in/%s"  /* path is path/fname */
+				,cfg.data_dir, msg->idx.to, tp);
+			if(!fexistcase(fpath) && msg->idx.from)
+				SAFEPRINTF3(fpath,"%sfile/%04u.out/%s"  /* path is path/fname */
+					,cfg.data_dir, msg->idx.from,tp);
+			long length=(long)flength(fpath);
+			if(length<1)
+				bprintf(text[FileDoesNotExist], tp);
+			else if(!(useron.exempt&FLAG('T')) && cur_cps && !SYSOP
+				&& length/(long)cur_cps>(time_t)timeleft)
+				bputs(text[NotEnoughTimeToDl]);
+			else {
+				char 	tmp[512];
+				int		i;
+				SAFEPRINTF2(str, text[DownloadAttachedFileQ]
+					,tp,ultoac(length,tmp));
+				if(length>0L && text[DownloadAttachedFileQ][0] && yesno(str)) {
+					{	/* Remote User */
+						xfer_prot_menu(XFER_DOWNLOAD);
+						mnemonics(text[ProtocolOrQuit]);
+						strcpy(str,"Q");
+						for(i=0;i<cfg.total_prots;i++)
+							if(cfg.prot[i]->dlcmd[0]
+								&& chk_ar(cfg.prot[i]->ar,&useron,&client)) {
+								sprintf(tmp,"%c",cfg.prot[i]->mnemonic);
+								strcat(str,tmp); 
+							}
+						ch=(char)getkeys(str,0);
+						for(i=0;i<cfg.total_prots;i++)
+							if(cfg.prot[i]->dlcmd[0] && ch==cfg.prot[i]->mnemonic
+								&& chk_ar(cfg.prot[i]->ar,&useron,&client))
+								break;
+						if(i<cfg.total_prots) {
+							int error = protocol(cfg.prot[i], XFER_DOWNLOAD, fpath, nulstr, false);
+							if(checkprotresult(cfg.prot[i],error,&fd)) {
+								if(del)
+									remove(fpath);
+								logon_dlb+=length;	/* Update stats */
+								logon_dls++;
+								useron.dls=(ushort)adjustuserrec(&cfg,useron.number
+									,U_DLS,5,1);
+								useron.dlb=adjustuserrec(&cfg,useron.number
+									,U_DLB,10,length);
+								bprintf(text[FileNBytesSent]
+									,fd.name,ultoac(length,tmp));
+								SAFEPRINTF(str
+									,"downloaded attached file: %s"
+									,fd.name);
+								logline("D-",str); 
+							}
+							autohangup(); 
+						} 
+					} 
+				} 
+			}
+			if(!p)
+				break;
+			tp=p+1;
+			while(*tp==' ') tp++; 
+		}
+		// Remove the *.in directory, only if its empty
+		SAFEPRINTF2(fpath, "%sfile/%04u.in", cfg.data_dir, msg->idx.to);
+		rmdir(fpath); 
+	}
+}
+
 /****************************************************************************/
 /* Writes message header and text data to a text file						*/
 /****************************************************************************/
diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp
index 03ffafc8e7e27790dc05d1c5e2e6a5883f9252f7..e809d532c02fada06a3504be49b6f965fb78d12e 100644
--- a/src/sbbs3/js_bbs.cpp
+++ b/src/sbbs3/js_bbs.cpp
@@ -3623,6 +3623,44 @@ js_show_msg_header(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+static JSBool
+js_download_msg_attachments(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;
+	bool		del = true;
+	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;
+			}
+		} else if(JSVAL_IS_BOOLEAN(argv[n])) {
+			del = JSVAL_TO_BOOLEAN(argv[n]);
+		}
+	}
+	if(smb == NULL || msg == NULL)
+		return JS_TRUE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	sbbs->download_msg_attachments(smb, msg, del);
+	JS_RESUMEREQUEST(cx, rc);
+
+	return(JS_TRUE);
+}
+
 static JSBool
 js_msgscan_cfg(JSContext *cx, uintN argc, jsval *arglist)
 {
@@ -4217,6 +4255,11 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 		"<i>header</i> must be a header object returned from <i>MsgBase.get_msg_header()</i>)")
 	,31702
 	},
+	{"download_msg_attachments", js_download_msg_attachments, 1, JSTYPE_VOID, JSDOCSTR("[object header]")
+	,JSDOCSTR("prompt the user to download each of the message's file attachments (if there are any)<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/readmail.cpp b/src/sbbs3/readmail.cpp
index 2fdd6cff7c9c043cd1241bc2b2a28c967118e8b6..88804324b3d7f56f7200f02ae1f685e6b7732bc7 100644
--- a/src/sbbs3/readmail.cpp
+++ b/src/sbbs3/readmail.cpp
@@ -57,16 +57,14 @@ static char mail_listing_flag(smbmsg_t* msg)
 /****************************************************************************/
 void sbbs_t::readmail(uint usernumber, int which, long lm_mode)
 {
-	char	str[256],str2[256],str3[256],done=0,domsg=1
-			,*p,*tp,*sp,ch;
-	char	path[MAX_PATH+1];
+	char	str[256],str2[256],done=0,domsg=1
+			,*p;
 	char 	tmp[512];
 	int		i;
 	uint32_t u,v;
-	int		error;
 	int		mismatches=0,act;
 	uint	unum;
-    long    length,l,last_mode;
+    long    l,last_mode;
 	ulong	last;
 	bool	replied;
 	file_t	fd;
@@ -262,110 +260,7 @@ void sbbs_t::readmail(uint usernumber, int which, long lm_mode)
 			show_msg(&smb, &msg
 				,msg.from_ext && msg.idx.from==1 && !msg.from_net.type
 					? 0:P_NOATCODES);
-
-			char* txt;
-			int attachment_index = 0;
-			bool found = true;
-			while((txt=smb_getmsgtxt(&smb,&msg, 0)) != NULL && found) {
-				char filename[MAX_PATH+1] = {0};
-				uint32_t filelen = 0;
-				uint8_t* filedata;
-				if((filedata = smb_getattachment(&msg, txt, filename, &filelen, attachment_index++)) != NULL 
-					&& filename[0] != 0 && filelen > 0) {
-					char tmp[32];
-					SAFEPRINTF2(str3, text[DownloadAttachedFileQ], filename, ultoac(filelen,tmp));
-					if(!noyes(str3)) {
-						char fpath[MAX_PATH+1];
-						SAFEPRINTF2(fpath, "%s%s", cfg.temp_dir, filename);
-						FILE* fp = fopen(fpath, "wb");
-						if(fp == NULL)
-							errormsg(WHERE, ERR_OPEN, fpath, 0);
-						else {
-							int result = fwrite(filedata, filelen, 1, fp);
-							fclose(fp);
-							if(!result)
-								errormsg(WHERE, ERR_WRITE, fpath, filelen);
-							else
-								sendfile(fpath, useron.prot, "attachment");
-						}
-					}
-				} else
-					found = false;
-				smb_freemsgtxt(txt);
-			}
-
-			if(msg.hdr.auxattr&MSG_FILEATTACH) {  /* Attached file */
-				smb_getmsgidx(&smb,&msg);
-				SAFECOPY(str, msg.subj);					/* filenames (multiple?) in title */
-				tp=str;
-				while(online) {
-					p=strchr(tp,' ');
-					if(p) *p=0;
-					sp=strrchr(tp,'/');              /* sp is slash pointer */
-					if(!sp) sp=strrchr(tp,'\\');
-					if(sp) tp=sp+1;
-					padfname(tp,fd.name);
-					SAFEPRINTF3(path,"%sfile/%04u.in/%s"  /* path is path/fname */
-						,cfg.data_dir,msg.idx.to,tp);
-					if(!fexistcase(path) && msg.idx.from)
-						SAFEPRINTF3(path,"%sfile/%04u.out/%s"  /* path is path/fname */
-							,cfg.data_dir,msg.idx.from,tp);
-					length=(long)flength(path);
-					if(length<1)
-						bprintf(text[FileDoesNotExist], tp);
-					else if(!(useron.exempt&FLAG('T')) && cur_cps && !SYSOP
-						&& length/(long)cur_cps>(time_t)timeleft)
-						bputs(text[NotEnoughTimeToDl]);
-					else {
-						SAFEPRINTF2(str3,text[DownloadAttachedFileQ]
-							,tp,ultoac(length,tmp));
-						if(length>0L && text[DownloadAttachedFileQ][0] && yesno(str3)) {
-							{	/* Remote User */
-								xfer_prot_menu(XFER_DOWNLOAD);
-								mnemonics(text[ProtocolOrQuit]);
-								strcpy(str3,"Q");
-								for(i=0;i<cfg.total_prots;i++)
-									if(cfg.prot[i]->dlcmd[0]
-										&& chk_ar(cfg.prot[i]->ar,&useron,&client)) {
-										sprintf(tmp,"%c",cfg.prot[i]->mnemonic);
-										strcat(str3,tmp); 
-									}
-								ch=(char)getkeys(str3,0);
-								for(i=0;i<cfg.total_prots;i++)
-									if(cfg.prot[i]->dlcmd[0] && ch==cfg.prot[i]->mnemonic
-										&& chk_ar(cfg.prot[i]->ar,&useron,&client))
-										break;
-								if(i<cfg.total_prots) {
-									error=protocol(cfg.prot[i],XFER_DOWNLOAD,path,nulstr,false);
-									if(checkprotresult(cfg.prot[i],error,&fd)) {
-										if(which==MAIL_YOUR)
-											remove(path);
-										logon_dlb+=length;	/* Update stats */
-										logon_dls++;
-										useron.dls=(ushort)adjustuserrec(&cfg,useron.number
-											,U_DLS,5,1);
-										useron.dlb=adjustuserrec(&cfg,useron.number
-											,U_DLB,10,length);
-										bprintf(text[FileNBytesSent]
-											,fd.name,ultoac(length,tmp));
-										SAFEPRINTF(str3
-											,"downloaded attached file: %s"
-											,fd.name);
-										logline("D-",str3); 
-									}
-									autohangup(); 
-								} 
-							} 
-						} 
-					}
-					if(!p)
-						break;
-					tp=p+1;
-					while(*tp==' ') tp++; 
-				}
-				SAFEPRINTF2(str,"%sfile/%04u.in",cfg.data_dir,usernumber);
-				rmdir(str); 
-			}
+			download_msg_attachments(&smb, &msg, which == MAIL_YOUR);
 			if(which==MAIL_YOUR && !(msg.hdr.attr&MSG_READ)) {
 				mail[smb.curmsg].attr|=MSG_READ;
 				if(thisnode.status==NODE_INUSE)
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 8678956e0451506550db47d537594b15bb819735..1b79a910edea48452d53d90c2f08ee8ab3a768de 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -685,6 +685,7 @@ public:
 	ulong	getlastmsg(uint subnum, uint32_t *ptr, time_t *t);
 	time_t	getmsgtime(uint subnum, ulong ptr);
 	ulong	getmsgnum(uint subnum, time_t t);
+	void	download_msg_attachments(smb_t*, smbmsg_t*, bool del);
 
 	/* readmail.cpp */
 	void	readmail(uint usernumber, int which, long lm_mode = 0);