nostr_mail 2.0.2
nostr_mail: ^2.0.2 copied to clipboard
A Dart SDK for sending and receiving emails over the Nostr protocol using NIP-59 gift-wrapped messages.
2.0.2 #
- Fix:
NostrMailClient.create()now primescachedPrivateSettingsfrom local storage when a pubkey is configured. Previously the sync getter stayednulluntil a caller awaitedgetCachedPrivateSettings()orgetPrivateSettings(), which made user-facing settings (signature, bridges, identities) appear reset after sign-out/sign-in: auth-state listeners fired before the new client was constructed, and nothing re-triggered the load once it was.
2.0.1 #
- Fix: Marking an email as unread no longer reverts to read after a refresh. A NIP-09 deletion tombstone store records every deleted label event id and
onLabelAdditionskips any event that has been tombstoned, so a stale label event re-served by a relay that does not honor NIP-09 (or by NDK's in-memory cache, which never acts on deletions) is dropped instead of re-applied. Works the same way for star/unstar and folder restores. - Breaking:
Email.isBridgedis now afinalfield instead of a getter. The previous getter parsed the MIMEFrom:header heuristically; per the nostr-mail-core spec, bridge detection must come from themail-fromtag on the rumor. The parser (parseEmailEvent) andEmailSender._saveSelfCopynow derive the value fromevent.getFirstTag('mail-from') != null, andEmailRecord.toEmail()round-trips it through storage. Callers that constructEmaildirectly must now passisBridged:. - Fix: Inbound nostr-native emails whose sender did not set a MIME
From:header are no longer falsely classified as bridged. The old heuristic returnedtruewhenever the From address was missing or unparseable; the spec-compliant tag check makes the classification deterministic.
2.0.0 #
- Breaking: Attachments no longer live in Sembast. Each attachment is extracted at sync time and stored in
BlossomCachekeyed by its content sha256 (unpinned, LRU-evictable). The original encrypted Blossom blob remains pinned as the source of truth, so any evicted attachment can be regenerated locally without going back to the relays. - Breaking:
Emailno longer exposesrawContent. It now carrieslightMimeText(the RFC 2822 envelope with attachment bodies emptied) andattachmentRefs({ filename, contentType, size, sha256, contentId }). Theemail.mimegetter still returns a parsedMimeMessage, but its attachment parts have empty bodies. - Breaking:
EmailRecordmirrors the same shape change. Existing rows are wiped on upgrade via thekSchemaVersionbump (full resync from relays + Blossom servers). - Breaking:
EmailParser.parseMimeis removed. ConstructEmaildirectly, or useMimeMessage.parseFromTextif all you need is a parsed MIME. - Breaking:
parseEmailEventandSyncEnginenow require a non-nullBlossomCache. - New:
NostrMailClient.getAttachmentBytes(email, ref)- lazy load attachment bytes. Cache hit is instant; cache miss decrypts the source-of-truth blob and re-extracts every attachment, then serves the requested one. - New:
NostrMailClient.getRawMimeText(email)andgetRawMime(email)- reconstruct the original byte-exact RFC 2822 MIME on demand (for.emlexport, reply with full quote, etc.). - Fix: Opening a folder containing emails with large attachments no longer triggers a multi-second MIME parse on every list load (previous behaviour pulled the full base64 attachment off disk for every row, just to display the list).
1.16.0 #
- New: Durable outbound queues -
OfflineBroadcastfor Nostr events andOfflineBlossomUploadfor blob uploads. Both are exposed onNostrMailClient(broadcastQueue,blossomUploadQueue). - New: Local-first
send()/sendMime()/delete()- persisted locally before any network attempt; enqueued for automatic retry until fully delivered. - New:
filters.dart- single source of truth for all 7 Nostr query filters. Eliminates duplication betweenSyncEngineandWatchManager. - Improvement:
SyncEnginenow syncs all filters defined infilters.md(gift wraps, public emails, labels, reposts, settings, metadata/relay lists). Label and deletion filters are now more precise (#L: mail, unified#ktag). - Improvement: Parallelized
fetchRecent()- fetches all 7 filter categories concurrently, then processes events in parallel viaGapSync.processBatch()andFuture.wait. Leverages NDK PR #632 signer concurrency queue to protect remote signers. - Improvement: Downloaded Blossom blobs are now cached locally. Subsequent reparses (e.g. after schema migration) reuse the cached encrypted blob instead of re-downloading.
1.15.0 #
- New: Automatic schema migration on client construction
- Local stores (
emails,labels,gift_wraps,private_settings) and ndk fetched ranges are wiped and rebuilt on every schema version mismatch — the client re-syncs from relays and Blossom on next sync. kSchemaVersionconstant: bump it whenever the shape of any locally stored record changes.migrateSchemaIfNeeded(db:, ndk:)exposed for advanced cases (manual force-resync, tests). Returnstruewhen a migration ran.
- Local stores (
- API change:
NostrMailClient(...)→await NostrMailClient.create(...)- The factory is now async so the migration runs before any repository touches the DB. Update call sites accordingly.
1.14.2 #
- Fix: Removed manual MIME header unfolding - delegate to
enough_mail_pluswhich correctly handles RFC 2822 folding
1.14.1 #
- Improvement: split client.dart in multiple files
1.14.0 #
- New: NIP-18 repost support
repost(Nip01Event emailEvent)— Repost an email to followers using kind 16 generic repost
1.13.0 #
- New: Added
getTrashedEmailsOlderThanmethod to easily query old deleted emails. - Improvement:
saveLabelnow properly stores the original Nostr event'screatedAttimestamp, allowing duration-based queries on labels. - Fix: Resolved state bleeding in tests by ensuring isolated in-memory database filenames for Sembast.
1.12.1 #
- Fix: BCC visibility rules now properly applied for email privacy
removeBccHeaders()— Utility function that removesBccandResent-Bccheaders from MIME messagessendMime()now correctly applies BCC visibility rules:- Sender's copy (keepCopy): sees TO + CC + BCC (all recipients visible)
- TO/CC recipients: sees TO + CC only (BCC hidden)
- BCC recipients: sees TO + CC only (other BCC hidden for privacy)
- BCC recipients are now hidden from TO/CC recipients per email standards
1.12.0 #
- New: Access to technical NIP-59 details (Gift Wrap, Seal, Rumor)
getGiftWrap(emailId)— Retrieve the original kind 1059 eventgetSeal(emailId)— Retrieve the decrypted kind 13 seal eventgetRumor(emailId)— Retrieve the decrypted kind 1301 rumor event
- Improvement: Enhanced Gift Wrap storage
GiftWrapStorenow persists decrypted seals and rumors- Faster retrieval of technical details without re-decryption
- New:
UnwrappedGiftWrapmodel for handling NIP-59 event pairs
1.11.0 #
- New:
identitiesfield inPrivateSettings— a list of RFC 5322MailAddressentries for multi-identity supportidentitiesreplaces the singledefaultAddressconcept with a flexible listdefaultAddressis now a convenience getter returningidentities?.firstupdatePrivateSettings()acceptsidentitiesandclearIdentitiesparameterssend()now uses the first identity as the default "From" address whenfromis not provided
- Breaking:
defaultAddressremoved fromPrivateSettingsconstructor,fromJson,toJson, andcopyWith - Breaking:
updatePrivateSettings()no longer acceptsdefaultAddressorclearDefaultAddressparameters
1.10.0 #
- New: NIP-78 private settings with NIP-44 encryption for cross-device synchronization
PrivateSettingsmodel:signature,defaultAddress,bridges,sourceEventgetPrivateSettings()— fetch and decrypt from relays (write relays, kind 30078)setPrivateSettings()— encrypt and broadcast to relaysupdatePrivateSettings()— update a single field with read-modify-writegetCachedPrivateSettings()— read local decrypted cache (no signer required)cachedPrivateSettings— synchronous getter for in-memory cache (multi-pubkey Map)PrivateSettingsStore— local decrypted JSON cache keyed by pubkey- Settings cleared on
clearAll() - Comprehensive unit and integration tests
1.9.1 #
- Fix: Reduce Blossom threshold from 60KB to 32KB to prevent NIP-44 plaintext limit overflow. NIP-59 double wrapping (Rumor → Seal → Gift Wrap) expands payload size via Base64 + padding, making 60KB unsafe.
1.9.0 #
- Breaking: Refactored
Emailmodel to useMimeMessageinternally for RFC 2822 compliance- Removed direct fields:
from,to,subject,body,date - New API: access parsed data via
email.mime.fromEmail,email.mime.to,email.mime.decodeSubject(),email.mime.decodeTextPlainPart(),email.mime.decodeTextHtmlPart() - Added
htmlBodyandtextBodygetters for direct access datenow uses MIMEDateheader with fallback to Nostr event creation time
- Removed direct fields:
- New:
createdAtfield onEmailto preserve original Nostr event timestamp - New:
rawMimegetter as alias forrawContent - Performance:
EmailReceivedevent now contains fullEmailobject instead of justemailId,from,subject- eliminates redundant database lookups - Performance:
onEmailstream no longer callsgetEmail()for each event - Fix: JSON serialization handles nullable
fromandsubjectfields for malformed emails - Fix: Tests updated to use new MIME-based API throughout
1.8.1 #
- Fix: Switch to
enough_mail_plusto fix critical email header folding issues. This resolves problems where long email addresses inFromheaders were being incorrectly folded, causing SpamAssassin flags and delivery issues. - Improvement: Enhanced RFC-compliance for email rendering.
1.8.0 #
- New: Global email search functionality. Search by subject, body, or sender across all local emails using Sembast regex filters. Search is case-insensitive and handles special characters safely.
1.7.0 #
- New: Support for large emails (> 60KB) via Blossom storage
1.6.1 #
- Fix: Folder labels are now mutually exclusive. When adding a
folder:label (inbox, sent, trash, archive), any existingfolder:label is automatically removed. This prevents emails from appearing in multiple folder views simultaneously when moved between folders.
1.6.0 #
- New: Support for formatted email addresses with display names (e.g.,
"Alice" <alice@uid.ovh>) - New:
resolveRecipient()function extracted toutils/recipient_resolver.dartfor better testability - Improvement: Use
enough_mail'sMailAddress.parse()andencode()for RFC-compliant address formatting - Fix: Domain extraction now works correctly when
fromaddress contains display name
1.5.0 #
- New: Archives helper functions
1.4.5 #
- Fix: html content is encoded in base64
1.4.4 #
- Refactor: improve gift wrap processing and simplify API
1.4.3 #
- Fix: save giftwraps events outside of NDK cache
1.4.2 #
- New: add fetchRecent() for simple parallel sync without fetchedRanges
1.4.0 #
- New:
resync()method to clear fetchedRanges and sync from scratch (useful for recovering late-arriving events) - Improvement: Refactored filter creation into reusable private methods
1.3.1 #
- Bug fix:
recipientPubkeynow correctly extracted from theptag of the email event instead of using the gift wrap recipient - Bug fix: Fallback to HTML body for single-part HTML emails
- Breaking: Emails without a
ptag are now skipped (malformed emails)
1.3.0 #
- New: NIP-32 labels system (
addLabel,removeLabel,moveToTrash,markAsRead,star, etc.) - New: Unified
watch()stream withMailEventsealed class (EmailReceived,EmailDeleted,LabelAdded,LabelRemoved) - New: Convenience stream getters (
onEmail,onTrash,onRead,onStarred,onLabel) - New:
getInboxEmails()andgetSentEmails()with pagination andincludeTrashedoption - New:
htmlBodygetter onEmail(parsed on demand from rawContent) - New:
stopWatching()method to close stream and cleanup resources - Improvement: Local-first labels (save and notify immediately, broadcast in background)
- Improvement: Shared broadcast stream for
watch()(multiple listeners share same subscriptions)
1.2.2 #
- Use the new ndk version
1.2.1 #
- Use the new ndk version
1.2.0 #
- Performance fix:
watchInbox()now useslimit: 0to only receive new real-time events, avoiding re-processing of historical gift wraps at startup - New:
sync()now accepts optionallimit,since, anduntilparameters for incremental sync
1.1.1 #
- Security fix: Added
recipientPubkeyfield to Email model to properly filter emails by recipient - Performance fix: Mark all gift wraps as processed after decryption to avoid re-decrypting DMs and other non-email content on each sync
1.1.0 #
- RFC 2822 compatibility: addresses without domain now get
@nostrsuffix - Standardized on npub format for all Nostr addresses (hex pubkeys auto-converted)
- Fixed: "To" field was empty when sending to npub addresses
1.0.0 #
- Initial version.