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.

Store What it holds
NostrMailDatabase (drift/SQLite) Emails, summaries, labels, gift wraps, settings, tombstones
sembast database The broadcast queue, the Blossom upload queue, the scheduler, the sync engine
BlossomCache Large-email blobs and attachment bytes
ndk cache Raw events, owned by ndk itself

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.


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:).


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.