@novacraft-engineering/mailbox 0.4.27 → 0.4.29

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/lib/receive.ts CHANGED
@@ -2,8 +2,9 @@ import { BRAND, ADDRESS_DOMAINS } from '@/lib/brand'
2
2
  import { sendPush } from '@/lib/push'
3
3
  import { stripOwnPixel } from '@/lib/email-html'
4
4
  import { FORWARDING_ENABLED, FORWARD_RECIPIENTS, MAIL_DOMAIN } from '@/lib/dev-auth'
5
- import { ADDRESS_ALIASES, appendInbound, isDmarcAggregateReport, judgeMessage, noteSender, senderStanding, senderDomainOf, getAccountByAddress, inboundExists, recordContact, recordSentMeta, repairInbound } from '@/lib/mailbox'
6
- import { sendMail } from '@/lib/mail-provider'
5
+ import { ADDRESS_ALIASES, appendInbound, classifyAddressed, isDmarcAggregateReport, judgeMessage, noteSender, senderStanding, senderDomainOf, getAccountByAddress, inboundExists, inboxFiltersFor, recordContact, recordSentMessage, recordSentMeta, repairInbound } from '@/lib/mailbox'
6
+ import { matchesInboxFilters } from '@/lib/inbox-filters'
7
+ import { archiveAddress, sendMail } from '@/lib/mail-provider'
7
8
 
8
9
  /**
9
10
  * Where the receiving API lives. The Resend SDK reads RESEND_BASE_URL for sending, so a
@@ -379,11 +380,29 @@ export async function ingestReceived(
379
380
  return { owner: inbound.owner, subject: inbound.subject, from: inbound.from }
380
381
  }
381
382
 
383
+ if (isArchiveCopy(inbound.owner, inbound.to, inbound.cc)) {
384
+ await recordSentMessage({
385
+ id: emailId,
386
+ from: inbound.from,
387
+ to: inbound.to,
388
+ cc: inbound.cc,
389
+ bcc: inbound.bcc,
390
+ replyTo: inbound.replyTo,
391
+ subject: inbound.subject,
392
+ html: inbound.html,
393
+ text: inbound.text,
394
+ createdAt: inbound.receivedAt,
395
+ attachments: inbound.attachments,
396
+ })
397
+ await recordSentMeta(emailId, inbound.owner, true)
398
+ return { owner: inbound.owner, subject: inbound.subject, from: inbound.from }
399
+ }
400
+
382
401
  await appendInbound(inbound)
383
402
  await noteSender(owner, senderDomain, 'received').catch(() => {})
384
403
  const sender = parseSender(inbound.from)
385
404
  await recordContact(sender.email, sender.name)
386
- if (!isDmarcAggregateReport(inbound.subject)) {
405
+ if (!isDmarcAggregateReport(inbound.subject) && !(await filteredOut(inbound))) {
387
406
  await sendPush(inbound.owner, {
388
407
  title: sender.name || sender.email || 'New mail',
389
408
  body: inbound.subject,
@@ -411,6 +430,19 @@ export async function ingestReceived(
411
430
  return { owner: inbound.owner, subject: inbound.subject, from: inbound.from }
412
431
  }
413
432
 
433
+ async function filteredOut(inbound: { owner: string | null; from: string; subject: string; text: string | null }): Promise<boolean> {
434
+ const login = inbound.owner ? (await getAccountByAddress(inbound.owner).catch(() => null))?.email : null
435
+ if (!login) return false
436
+ const filters = await inboxFiltersFor(login).catch(() => [])
437
+ return matchesInboxFilters(filters, { senders: [inbound.from], subject: inbound.subject, text: inbound.text })
438
+ }
439
+
440
+ /** A copy of our own outgoing mail, BCC'd to the archive seat: it belongs in that seat's Sent, not its inbox. */
441
+ export function isArchiveCopy(owner: string | null | undefined, to: string[], cc: string[]): boolean {
442
+ const archive = archiveAddress()
443
+ return Boolean(archive) && (owner ?? '').trim().toLowerCase() === archive && classifyAddressed(owner, to, cc) === 'other'
444
+ }
445
+
414
446
  export type ReceivedSummary = { id: string; from: string; to: string[]; subject: string; createdAt: string }
415
447
 
416
448
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novacraft-engineering/mailbox",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
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",