Local Storage
The SDK stores mail locally so the app reads from disk, not from the relays. Four stores are involved, and you own all of them.
Native
import 'dart:io';
import 'package:drift/native.dart';
import 'package:sembast/sembast_io.dart' hide Filter;
final database = NostrMailDatabase(NativeDatabase(File('nostr_mail.sqlite')));
final db = await databaseFactoryIo.openDatabase('emails.db');
final blossomCache = await IdbBlossomCache.open(factory: idbFactorySembastIo);
In Flutter, put both files under getApplicationDocumentsDirectory().
Web
Copy sqlite3.wasm (from the sqlite3.dart
release matching your resolved sqlite3 version) and drift_worker.js (from
the drift release matching
drift) into web/, then open the store through a worker:
import 'package:drift/wasm.dart';
final result = await WasmDatabase.open(
databaseName: 'nostr_mail',
sqlite3Uri: Uri.parse('sqlite3.wasm'),
driftWorkerUri: Uri.parse('drift_worker.js'),
);
final database = NostrMailDatabase(result.resolvedExecutor);
Use idbFactoryBrowser for the Blossom cache on web.
Check result.chosenImplementation. Drift stores the file in OPFS when the
browser allows it: opfsShared needs no headers on Chrome and Firefox,
opfsLocks needs the page to be cross-origin isolated, which is what Safari
falls back to. Otherwise it falls back to IndexedDB, which works but keeps the
file image in memory.
Where the memory actually goes
A SembastCacheManager given to ndk holds every raw event in memory. With a
large mailbox, that cache, not the mail store, is the one to watch.
Full-text search
The drift store indexes emails with SQLite FTS5, which is what makes
client.search(...) and the search: filter of getSummaries local and
instant.
Clearing
// One account: its emails, labels, wraps, settings, and its pending work.
await client.clearLocalAccountData(pubkey: pubkey);
// Every account.
await client.clearAllLocalData();
Both leave the ndk cache alone, so a later sync rebuilds the mail from it. Clear that cache too to forget an account entirely.
A queue you passed to create stays yours: clear it with
OfflineBroadcast.clearLocalAccountData(pubkey:) and
OfflineBlossomUpload.clearLocalAccountData(pubkey:).
Do not clear an account that is sending
A broadcast overlapping the clear can re-create its record.
Shutting down
await client.dispose();
await database.close();
await db.close();
dispose stops the background workers and disposes the queues it owns, waiting
for any attempt in flight. Queues you passed in are yours to dispose, and so are
the Blossom cache and the sync engine.