diff --git a/src/smblib/smblib.c b/src/smblib/smblib.c
index a2a18f6207ff18bc2c438f7fadb932f9afbf20fe..3f3ffd75da548a55ab9d80163090209199e3c1e1 100644
--- a/src/smblib/smblib.c
+++ b/src/smblib/smblib.c
@@ -1397,7 +1397,7 @@ int SMBCALL smb_addcrc(smb_t* smb, ulong crc)
 			close(file);
 			FREE(buf);
 			safe_snprintf(smb->last_error,sizeof(smb->last_error)
-				,"duplicate message detected");
+				,"duplicate message text CRC detected");
 			return(SMB_DUPE_MSG);
 		} 
 
@@ -1497,6 +1497,225 @@ int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage)
 	return(i);
 }
 
+/****************************************************************************/
+/****************************************************************************/
+int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage
+					   ,ushort attr, ushort xlat, const uchar* body, const uchar* tail)
+{
+	uchar*		lzhbuf=NULL;
+	long		lzhlen;
+	int			retval;
+	size_t		n;
+	size_t		l,length;
+	size_t		xlatlen;
+	size_t		taillen=0;
+	size_t		bodylen=0;
+	long		offset;
+	ulong		crc=0xffffffff;
+	hash_t		found;
+	hash_t**	hashes=NULL;	/* This is a NULL-terminated list of hashes */
+	smbmsg_t	remsg;
+
+	if(!SMB_IS_OPEN(smb)) {
+		safe_snprintf(smb->last_error,sizeof(smb->last_error),"msgbase not open");
+		return(SMB_ERR_NOT_OPEN);
+	}
+
+	if(filelength(fileno(smb->shd_fp))<1) {	 /* Create it if it doesn't exist */
+		memset(&smb->status,0,sizeof(smb->status));
+		smb->status.attr=attr;
+		if((retval=smb_create(smb))!=SMB_SUCCESS) 
+			return(retval);
+	}
+
+	if(!smb->locked && smb_locksmbhdr(smb)!=SMB_SUCCESS)
+		return(SMB_ERR_LOCK);
+
+	msg->hdr.total_dfields = 0;
+
+	/* try */
+	do {
+
+		if((retval=smb_getstatus(smb))!=SMB_SUCCESS)
+			break;
+
+		msg->hdr.number=smb->status.last_msg+1;
+
+		hashes=smb_msghashes(smb,msg,body);
+
+		if(smb_findhash(smb, hashes, &found, /* update? */FALSE)==SMB_SUCCESS) {
+			safe_snprintf(smb->last_error,sizeof(smb->last_error)
+				,"duplicate %s hash found (message #%lu)"
+				,smb_hashsource(found.source), found.number);
+			retval=SMB_DUPE_MSG;
+			break;
+		}
+
+		if(tail!=NULL && (taillen=strlen(tail))>0)
+			taillen+=sizeof(xlat);	/* xlat string terminator */
+
+		if(body!=NULL && (bodylen=strlen(body))>0) {
+
+			/* Remove white-space from end of message text */
+			while(bodylen && body[bodylen-1]<=' ')
+				bodylen--;
+
+			/* Calculate CRC-32 of message text (before encoding, if any) */
+			if(smb->status.max_crcs) {
+				for(l=0;l<bodylen;l++)
+					crc=ucrc32(body[l],crc); 
+				crc=~crc;
+
+				/* Add CRC to CRC history (and check for duplicates) */
+				if((retval=smb_addcrc(smb,crc))!=SMB_SUCCESS)
+					break;
+			}
+
+			bodylen+=sizeof(xlat);	/* xlat string terminator */
+
+			/* LZH compress? */
+			if(xlat==XLAT_LZH && bodylen+taillen>=SDT_BLOCK_LEN
+				&& (lzhbuf=(uchar *)malloc(bodylen*2))!=NULL) {
+				lzhlen=lzh_encode((uchar*)body,bodylen-sizeof(xlat),lzhbuf);
+				if(lzhlen>1
+					&& smb_datblocks(lzhlen+(sizeof(xlat)*2)+taillen) 
+						< smb_datblocks(bodylen+taillen)) {
+					bodylen=lzhlen+(sizeof(xlat)*2); 	/* Compressible */
+					body=lzhbuf; 
+				} else
+					xlat=XLAT_NONE;
+			} else
+				xlat=XLAT_NONE;
+		}
+
+		length=bodylen+taillen;
+
+		if(length&0x80000000) {
+			sprintf(smb->last_error,"message length: 0x%lX",length);
+			retval=SMB_ERR_DAT_LEN;
+			break;
+		}
+
+		/* Allocate Data Blocks */
+		if(smb->status.attr&SMB_HYPERALLOC) {
+			offset=smb_hallocdat(smb);
+			storage=SMB_HYPERALLOC; 
+		} else {
+			if((retval=smb_open_da(smb))!=SMB_SUCCESS)
+				break;
+			if(storage==SMB_FASTALLOC)
+				offset=smb_fallocdat(smb,length,1);
+			else { /* SMB_SELFPACK */
+				offset=smb_allocdat(smb,length,1);
+				storage=SMB_SELFPACK; 
+			}
+			smb_close_da(smb); 
+		}
+
+		if(offset<0) {
+			retval=offset;
+			break;
+		}
+		msg->hdr.offset=offset;
+
+		smb_fseek(smb->sdt_fp,offset,SEEK_SET);
+
+		if(bodylen) {
+			smb_dfield(msg,TEXT_BODY,bodylen);
+
+			xlatlen=0;
+			if(xlat!=XLAT_NONE) {	/* e.g. XLAT_LZH */
+				smb_fwrite(smb,&xlat,sizeof(xlat),smb->sdt_fp);
+				xlatlen+=sizeof(xlat);
+			}
+			xlat=XLAT_NONE;	/* xlat string terminator */
+			smb_fwrite(smb,&xlat,sizeof(xlat),smb->sdt_fp);
+			xlatlen+=sizeof(xlat);
+
+			smb_fwrite(smb,body,bodylen-xlatlen,smb->sdt_fp);
+		}
+
+		if(taillen) {
+			smb_dfield(msg,TEXT_TAIL,taillen);
+
+			xlat=XLAT_NONE;	/* xlat string terminator */
+			smb_fwrite(smb,&xlat,sizeof(xlat),smb->sdt_fp);
+
+			smb_fwrite(smb,tail,taillen-sizeof(xlat),smb->sdt_fp);
+		}
+
+		for(l=length;l%SDT_BLOCK_LEN;l++)
+			smb_fputc(0,smb->sdt_fp);
+
+		fflush(smb->sdt_fp);
+
+		if(msg->to==NULL)	/* no recipient, don't add header (required for bulkmail) */
+			break;
+
+		msg->hdr.version=smb_ver();
+		if(msg->hdr.when_imported.time==0) {
+			msg->hdr.when_imported.time=time(NULL);
+			msg->hdr.when_imported.zone=0;	/* how do we detect system TZ? */
+		}
+		if(msg->hdr.when_written.time==0)	/* Uninitialized */
+			msg->hdr.when_written = msg->hdr.when_imported;
+		msg->idx.time=msg->hdr.when_imported.time;
+
+		/* Look-up thread_back if Reply-ID was specified */
+		if(msg->hdr.thread_back==0 && msg->reply_id!=NULL) {
+			if(smb_getmsgidx_by_msgid(smb,&remsg,msg->reply_id)==SMB_SUCCESS)
+				msg->hdr.thread_back=remsg.idx.number;	/* needed for threading backward */
+		}
+
+		/* Auto-thread linkage */
+		if(msg->hdr.thread_back) {
+			memset(&remsg,0,sizeof(remsg));
+			remsg.hdr.number=msg->hdr.thread_back;
+			if((retval=smb_getmsgidx(smb, &remsg))!=SMB_SUCCESS)	/* invalid thread origin */
+				break;
+
+			if((retval=smb_lockmsghdr(smb,&remsg))!=SMB_SUCCESS)
+				break;
+
+			if((retval=smb_getmsghdr(smb, &remsg))!=SMB_SUCCESS) {
+				smb_unlockmsghdr(smb, &remsg); 
+				break;
+			}
+
+			/* Add RFC-822 Reply-ID if original message has RFC Message-ID */
+			if(msg->reply_id==NULL && remsg.id!=NULL)
+				smb_hfield_str(msg,RFC822REPLYID,remsg.id);
+
+			/* Add FidoNet Reply if original message has FidoNet MSGID */
+			if(msg->ftn_reply==NULL && remsg.ftn_msgid!=NULL)
+				smb_hfield_str(msg,FIDOREPLYID,remsg.ftn_msgid);
+
+			retval=smb_updatethread(smb, &remsg, msg->hdr.number);
+			smb_unlockmsghdr(smb, &remsg);
+			smb_freemsgmem(&remsg);
+
+			if(retval!=SMB_SUCCESS)
+				break;
+		}
+
+		if(smb_addhashes(smb,hashes)==SMB_SUCCESS)
+			msg->flags|=MSG_FLAG_HASHED;
+
+		retval=smb_addmsghdr(smb,msg,storage); // calls smb_unlocksmbhdr() 
+
+	} while(0);
+	/* finally */
+
+	if(retval!=SMB_SUCCESS)
+		smb_freemsg_dfields(smb,msg,1);
+
+	smb_unlocksmbhdr(smb);
+	FREE_AND_NULL(lzhbuf);
+	FREE_LIST(hashes,n);
+
+	return(retval);
+}
+
 /****************************************************************************/
 /* Writes both header and index information for msg 'msg'                   */
 /****************************************************************************/
@@ -2229,7 +2448,7 @@ size_t SMBCALL smb_fread(smb_t* smb, void* buf, size_t bytes, FILE* fp)
 	return(ret);
 }
 
-size_t SMBCALL smb_fwrite(smb_t* smb, void* buf, size_t bytes, FILE* fp)
+size_t SMBCALL smb_fwrite(smb_t* smb, const void* buf, size_t bytes, FILE* fp)
 {
 	return(fwrite(buf,1,bytes,fp));
 }
@@ -2347,6 +2566,13 @@ char* SMBCALL smb_dfieldtype(ushort type)
 	return(str);
 }
 
+char* SMBCALL smb_hashsource(uchar type)
+{
+	if(type==TEXT_BODY || type==TEXT_TAIL)
+		return(smb_dfieldtype(type));
+	return(smb_hfieldtype(type));
+}
+
 int SMBCALL smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newmsgnum)
 {
 	int			retval=SMB_ERR_NOT_FOUND;
@@ -2611,8 +2837,8 @@ int SMBCALL smb_hashmsg(smb_t* smb, smbmsg_t* msg, const uchar* text, BOOL updat
 	if(smb_findhash(smb, hashes, &found, update)==SMB_SUCCESS && !update) {
 		retval=SMB_DUPE_MSG;
 		safe_snprintf(smb->last_error,sizeof(smb->last_error)
-			,"Duplicate type %u hash found for message #%lu"
-			,found.source, found.number);
+			,"duplicate %s hash found (message #%lu)"
+			,smb_hashsource(found.source), found.number);
 	} else
 		if((retval=smb_addhashes(smb,hashes))==SMB_SUCCESS)
 			msg->flags|=MSG_FLAG_HASHED;
diff --git a/src/smblib/smblib.h b/src/smblib/smblib.h
index ca4932ba405fe2e1fad1ba94ed6079a7db1cda9c..48b687e028b3ddc5f1af699a89ef3e7661b7a03f 100644
--- a/src/smblib/smblib.h
+++ b/src/smblib/smblib.h
@@ -138,7 +138,9 @@ SMBEXPORT void*		SMBCALL smb_get_hfield(smbmsg_t* msg, ushort type, hfield_t* hf
 SMBEXPORT char*		SMBCALL smb_hfieldtype(ushort type);
 SMBEXPORT ushort	SMBCALL smb_hfieldtypelookup(const char*);
 SMBEXPORT char*		SMBCALL smb_dfieldtype(ushort type);
-SMBEXPORT int 		SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg,int storage);
+SMBEXPORT int 		SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage);
+SMBEXPORT int		SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage
+						,ushort attr, ushort xlat, const uchar* body, const uchar* tail);
 SMBEXPORT int 		SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg);
 SMBEXPORT int 		SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg);
 SMBEXPORT int 		SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg);
@@ -175,6 +177,7 @@ SMBEXPORT hash_t*	SMBCALL	smb_hash(ulong msgnum, ulong time, unsigned source
 								,unsigned flags, const void* data, size_t length);
 SMBEXPORT hash_t*	SMBCALL	smb_hashstr(ulong msgnum, ulong time, unsigned source
 								,unsigned flags, const char* str);
+SMBEXPORT char*		SMBCALL smb_hashsource(uchar type);
 SMBEXPORT hash_t**	SMBCALL smb_msghashes(smb_t* smb, smbmsg_t* msg, const uchar* text);
 SMBEXPORT int		SMBCALL smb_addhashes(smb_t* smb, hash_t** hash_list);
 
@@ -216,7 +219,7 @@ SMBEXPORT int 		SMBCALL smb_fputc(int ch, FILE* fp);
 SMBEXPORT int 		SMBCALL smb_fseek(FILE* fp, long offset, int whence);
 SMBEXPORT long		SMBCALL smb_ftell(FILE* fp);
 SMBEXPORT size_t	SMBCALL smb_fread(smb_t*, void* buf, size_t bytes, FILE* fp);
-SMBEXPORT size_t	SMBCALL smb_fwrite(smb_t*, void* buf, size_t bytes, FILE* fp);
+SMBEXPORT size_t	SMBCALL smb_fwrite(smb_t*, const void* buf, size_t bytes, FILE* fp);
 SMBEXPORT long		SMBCALL smb_fgetlength(FILE* fp);
 SMBEXPORT int 		SMBCALL smb_fsetlength(FILE* fp, long length);
 SMBEXPORT void		SMBCALL smb_rewind(FILE* fp);