diff --git a/src/lib/letter.ts b/src/lib/letter.ts
index 05c9baa..8a512cc 100644
--- a/src/lib/letter.ts
+++ b/src/lib/letter.ts
@@ -2,6 +2,66 @@ import NDK, { NDKEvent, type NDKEventId, NDKKind, type NDKUser } from '@nostr-de
 import { isValidNip05 } from '$lib/utils.svelte';
 import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils';
 
+function parseTextPlainContent(mimeMessage: string) {
+	const boundary = mimeMessage.split('\n')[0];
+
+	// --7408fffb1a494ae39c2dde2c60878849
+	// Content-Type: text/plain
+	// Content-Transfer-Encoding: 7bit
+	//
+	// test
+	// --7408fffb1a494ae39c2dde2c60878849
+	// Content-Type: text/html
+	// Content-Transfer-Encoding: 7bit
+
+	const boundaries = mimeMessage.split(boundary + '\n');
+	boundaries.shift();
+
+	const bodies = [];
+
+	for (const body of boundaries) {
+		const headers: Record<string, string> = {};
+		const lines = body.split('\r\n');
+		let isLookingForHeaders = true;
+		let content = '';
+		for (const line of lines) {
+			if (line.trim() === '') {
+				isLookingForHeaders = false;
+				content += line + '\n';
+			} else {
+				if (!isLookingForHeaders) {
+					content += line + '\n';
+					continue;
+				}
+				const [key, value] = line.split(': ');
+				headers[key.toLowerCase()] = value;
+			}
+		}
+		bodies.push({ headers, content: content.trim() });
+	}
+
+	if (bodies.length === 0) return mimeMessage;
+
+	let chosenBody = bodies.find((body) => body.headers['Content-Type'] === 'text/plain');
+	if (!chosenBody) chosenBody = bodies[0];
+
+	const encoding = chosenBody.headers['content-transfer-encoding'];
+	if (!encoding) return chosenBody.content; // assume plain text
+
+	switch (encoding.toLowerCase()) {
+		case '7bit':
+			return chosenBody.content;
+		case 'base64':
+			return atob(chosenBody.content.replaceAll('\n', ''));
+		case 'quoted-printable':
+			return chosenBody.content
+				.replace(/=\r\n/g, '')
+				.replace(/=([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)));
+		default:
+			return `Cannot decode ${encoding} encoded message. This is a bug, please report it.`;
+	}
+}
+
 export class Letter {
 	id: NDKEventId;
 	subject: string;
@@ -28,13 +88,19 @@ export class Letter {
 	): Promise<Letter> {
 		if (decryptedMessage.kind !== NDKKind.Article) throw new Error('Not a letter');
 
+		let rawContent = decryptedMessage.content;
+		let content = '';
+		let email = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1];
+		if (rawContent.startsWith('--') && email) content = parseTextPlainContent(rawContent);
+		else content = rawContent;
+
 		const letter = new Letter();
 		letter.id = encryptedMessage.id;
 		letter.subject = decryptedMessage.tags.find((t) => t[0] === 'subject')?.[1] ?? 'No subject';
 		letter.from = decryptedMessage.author;
-		letter.content = decryptedMessage.content;
+		letter.content = content;
 		letter.date = new Date(decryptedMessage.created_at! * 1000);
-		letter.emailAddress = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1];
+		letter.emailAddress = email;
 		let stampToken = decryptedMessage.tags.find((t) => t[0] === 'stamp')?.[1];
 		if (stampToken) {
 			try {
diff --git a/src/routes/letters/[id]/+page.svelte b/src/routes/letters/[id]/+page.svelte
index b5abb1c..e5be463 100644
--- a/src/routes/letters/[id]/+page.svelte
+++ b/src/routes/letters/[id]/+page.svelte
@@ -70,13 +70,13 @@
 					<div class="sender-recipient">
 						<div class="user-row">
 							<span class="label">From:</span>
-							<NostrIdentifier user={letter.from.npub} />
+							<NostrIdentifier user={letter.from.npub} emailAddress={letter.emailAddress} />
 						</div>
 						<div class="user-row">
 							<span class="label">To:</span>
 							<div class="recipients">
 								{#each letter.recipients as recipient}
-									<NostrIdentifier user={recipient.npub} />
+									<NostrIdentifier user={recipient.npub} emailAddress={recipient.emailAddress} />
 								{/each}
 							</div>
 						</div>