@novacraft-engineering/mailbox 0.4.20 → 0.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/mail/page.tsx CHANGED
@@ -663,6 +663,12 @@ function htmlToQuoteText(html: string | null, text: string | null): string {
663
663
  * imported without ids fall back to the names mail clients give such parts. Anything
664
664
  * larger, or not an image, is a real file.
665
665
  */
666
+ // Some senders put the plain-text body straight into the HTML part. Rendered as markup its
667
+ // line breaks are just whitespace and the message collapses into one paragraph, so a part
668
+ // carrying no tags has to be treated as the text it actually is.
669
+ const hasMarkup = (html: string | null | undefined): html is string =>
670
+ typeof html === 'string' && /<[a-z!/][^>]*>/i.test(html)
671
+
666
672
  const INLINE_IMAGE_MAX_BYTES = 150_000
667
673
  const INLINE_IMAGE_NAME = /^(image\d*|outlook-[\w-]+|blocked|[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.(png|gif|jpe?g)$/i
668
674
  function inlineAttachmentNames(message: InboundEmail): Set<string> {
@@ -4978,7 +4984,7 @@ export default function DevMailPage() {
4978
4984
  img { max-width: 100%; }
4979
4985
  </style>`
4980
4986
  doc.open()
4981
- doc.write(`<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(subject)}</title>${printCss}</head><body>${meta}${html ? stripOwnPixel(html) : `<pre style="font-family:Arial,sans-serif;white-space:pre-wrap;">${escapeHtml(text ?? '')}</pre>`}</body></html>`)
4987
+ doc.write(`<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(subject)}</title>${printCss}</head><body>${meta}${hasMarkup(html) ? stripOwnPixel(html) : `<pre style="font-family:Arial,sans-serif;white-space:pre-wrap;">${escapeHtml(text?.trim() || html || '')}</pre>`}</body></html>`)
4982
4988
  doc.close()
4983
4989
  frame.contentWindow?.focus()
4984
4990
  window.setTimeout(() => {
@@ -5881,13 +5887,14 @@ export default function DevMailPage() {
5881
5887
  if (readerMode === 'html') return <pre className={styles.readerSource}>{formatHtmlSource(message.html ?? '')}</pre>
5882
5888
  if (readerMode === 'raw') return <pre className={styles.readerSource}>{rawInboundMessage(message)}</pre>
5883
5889
  // preview
5884
- if (!message.html) {
5885
- const plain = splitQuotedText(message.text ?? '')
5890
+ if (!hasMarkup(message.html)) {
5891
+ const source = message.text?.trim() ? message.text : (message.html ?? '')
5892
+ const plain = splitQuotedText(source)
5886
5893
  const textShown = quoteOpen.has(message.id)
5887
5894
  return (
5888
5895
  <>
5889
- {(textShown ? message.text : plain.head)
5890
- ? <pre className={styles.readerText}>{textShown ? message.text : plain.head}</pre>
5896
+ {(textShown ? source : plain.head)
5897
+ ? <pre className={styles.readerText}>{textShown ? source : plain.head}</pre>
5891
5898
  : renderBodylessAttachment(inboundAttachments[message.id] ?? []) ?? <pre className={styles.readerText}>(no content)</pre>}
5892
5899
  {plain.tail && renderQuoteLine(message.id, textShown)}
5893
5900
  </>
@@ -5936,7 +5943,7 @@ export default function DevMailPage() {
5936
5943
  const renderSentBody = (sent: SentEmail) => {
5937
5944
  const detail = detailCache[sent.id]
5938
5945
  if (!detail) return <div className={styles.threadPending}>Loading message…</div>
5939
- if (detail.html && detail.html.trim()) {
5946
+ if (hasMarkup(detail.html)) {
5940
5947
  return (
5941
5948
  <iframe
5942
5949
  className={styles.threadFrame}
@@ -5952,8 +5959,9 @@ export default function DevMailPage() {
5952
5959
  />
5953
5960
  )
5954
5961
  }
5955
- return detail.text?.trim()
5956
- ? <pre className={styles.readerText}>{detail.text}</pre>
5962
+ const plainSent = detail.text?.trim() ? detail.text : (detail.html ?? '').trim()
5963
+ return plainSent
5964
+ ? <pre className={styles.readerText}>{plainSent}</pre>
5957
5965
  : renderBodylessAttachment(detail.attachments ?? []) ?? <pre className={styles.readerText}>(no content)</pre>
5958
5966
  }
5959
5967
 
package/lib/mailbox.ts CHANGED
@@ -538,11 +538,16 @@ const THROWAWAY_TLDS_DEFAULT = new Set([
538
538
  ])
539
539
 
540
540
  /** The shape of an advance-fee approach. Counted, never single-word: one alone is innocent. */
541
+ // Only wording that is odd in ordinary business correspondence belongs here. A single
542
+ // generic term is not evidence of anything: an insurance broker writes "beneficiary" and
543
+ // "bank draft" all day, a logistics firm writes "consignment", and every sales team sends
544
+ // a "business proposal". Add them per deployment through MAIL_SCAM_PHRASES if a mailbox
545
+ // genuinely never sees them.
541
546
  const SCAM_PHRASES_DEFAULT = [
542
- 'next of kin', 'sole beneficiary', 'beneficiary', 'late client', 'deceased client',
543
- 'unclaimed', 'inheritance', 'died without', 'without a will', 'fund transfer',
544
- 'business proposal', 'strictly confidential', 'bank draft', 'consignment',
545
- 'compensation fund', 'lottery', 'winning notification', 'atm card', 'western union',
547
+ 'next of kin', 'sole beneficiary', 'late client', 'deceased client',
548
+ 'inheritance', 'died without', 'without a will', 'unclaimed inheritance',
549
+ 'winning notification', 'lottery winner', 'western union', 'atm card',
550
+ 'transfer to your account immediately', 'strictly confidential and urgent',
546
551
  ]
547
552
 
548
553
  /** The registrable domain behind an address, for reputation to be keyed on. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novacraft-engineering/mailbox",
3
- "version": "0.4.20",
3
+ "version": "0.4.22",
4
4
  "description": "A shared webmail app: one codebase, one deployment per mailbox. Threads, a rich composer with signatures, attachments on S3-compatible storage, sharing links, full-text search, web push and PWA install.",
5
5
  "keywords": [
6
6
  "webmail",