Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ worker-configuration.d.ts

# devcontainer
.pnpm-store
tsx-0/
tsx-0/

tools.json
5 changes: 3 additions & 2 deletions apps/mail/components/create/create-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ export function CreateEmail({
subject: string;
message: string;
attachments: File[];
fromEmail?: string;
}) => {
// Use the selected from email or the first alias (or default user email)
const fromEmail = aliases?.[0]?.email ?? userEmail;
const fromEmail = data.fromEmail || aliases?.[0]?.email || userEmail;

const zeroSignature = settings?.settings.zeroSignature
? '<p style="color: #666; font-size: 12px;">Sent via <a href="https://0.email/" style="color: #0066cc; text-decoration: none;">Zero</a></p>'
Expand Down Expand Up @@ -185,6 +185,7 @@ export function CreateEmail({
<EmailComposer
key={typedDraft?.id || 'composer'}
className="mb-12 rounded-2xl border"
aliases={aliases}
onSendEmail={handleSendEmail}
initialMessage={typedDraft?.content || initialBody}
initialTo={
Expand Down
54 changes: 52 additions & 2 deletions apps/mail/components/create/email-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import {
X,
Sparkles,
} from '../icons/icons';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { TextEffect } from '@/components/motion-primitives/text-effect';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
Expand Down Expand Up @@ -47,13 +54,20 @@ interface EmailComposerProps {
initialSubject?: string;
initialMessage?: string;
initialAttachments?: File[];
replyingTo?: string;
aliases?: {
email: string;
name?: string;
primary?: boolean;
}[];
onSendEmail: (data: {
to: string[];
cc?: string[];
bcc?: string[];
subject: string;
message: string;
attachments: File[];
fromEmail?: string;
}) => Promise<void>;
onClose?: () => void;
className?: string;
Expand Down Expand Up @@ -92,6 +106,8 @@ export function EmailComposer({
className,
autofocus = false,
settingsLoading = false,
replyingTo,
aliases = [],
editorClassName,
}: EmailComposerProps) {
const [showCc, setShowCc] = useState(initialCc.length > 0);
Expand Down Expand Up @@ -164,6 +180,7 @@ export function EmailComposer({
subject: initialSubject,
message: initialMessage,
attachments: initialAttachments,
fromEmail: aliases?.find((alias) => alias.primary)?.email || aliases?.[0]?.email || '',
},
});

Expand Down Expand Up @@ -247,6 +264,7 @@ export function EmailComposer({
const bccEmails = watch('bcc');
const subjectInput = watch('subject');
const attachments = watch('attachments');
const fromEmail = watch('fromEmail');

const handleAttachment = (files: File[]) => {
if (files && files.length > 0) {
Expand Down Expand Up @@ -304,6 +322,7 @@ export function EmailComposer({
subject: values.subject,
message: editor.getHTML(),
attachments: values.attachments || [],
fromEmail: values.fromEmail,
});
setHasUnsavedChanges(false);
editor.commands.clearContent(true);
Expand Down Expand Up @@ -908,7 +927,7 @@ export function EmailComposer({
</div>

{/* Subject */}
<div className="flex items-center gap-2 p-3">
<div className="flex items-center gap-2 border-b p-3">
<p className="text-sm font-medium text-[#8C8C8C]">Subject:</p>
<input
className="h-4 w-full bg-transparent text-sm font-normal leading-normal text-black placeholder:text-[#797979] focus:outline-none dark:text-white/90"
Expand All @@ -932,14 +951,45 @@ export function EmailComposer({
</button>
</div>

{/* From */}
{aliases.length > 0 && !replyingTo && (
<div className="flex items-center gap-2 border-b p-3">
<p className="text-sm font-medium text-[#8C8C8C]">From:</p>
<Select
value={fromEmail || ''}
onValueChange={(value) => {
setValue('fromEmail', value);
setHasUnsavedChanges(true);
}}
>
<SelectTrigger className="h-6 flex-1 border-0 bg-transparent p-0 text-sm font-normal text-black placeholder:text-[#797979] focus:outline-none focus:ring-0 dark:text-white/90">
<SelectValue placeholder="Select an email address" />
</SelectTrigger>
<SelectContent>
{aliases.map((alias) => (
<SelectItem key={alias.email} value={alias.email}>
<div className="flex flex-row items-center gap-1">
<span className="text-sm">
{alias.name ? `${alias.name} <${alias.email}>` : alias.email}
</span>
{alias.primary && <span className="text-xs text-[#8C8C8C]">Primary</span>}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}

{/* Message Content */}
<div className="grow self-stretch overflow-y-auto border-t bg-[#FFFFFF] px-3 py-3 outline-white/5 dark:bg-[#202020]">
<div
onClick={() => {
editor.commands.focus();
}}
className={cn(
`max-h-[300px] min-h-[200px] w-full ${editorClassName}`,
`max-h-[300px] min-h-[200px] w-full`,
editorClassName,
aiGeneratedMessage !== null ? 'blur-sm' : '',
)}
>
Expand Down
27 changes: 25 additions & 2 deletions apps/mail/components/mail/reply-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,29 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
try {
const userEmail = activeConnection.email.toLowerCase();

// Convert email strings to Sender objects
let fromEmail = userEmail;

if (aliases && aliases.length > 0 && replyToMessage) {
const allRecipients = [
...(replyToMessage.to || []),
...(replyToMessage.cc || []),
...(replyToMessage.bcc || []),
];

const matchingAlias = aliases.find((alias) =>
allRecipients.some(
(recipient) => recipient.email.toLowerCase() === alias.email.toLowerCase(),
),
);

if (matchingAlias) {
fromEmail = matchingAlias.email;
} else {
fromEmail =
aliases.find((alias) => alias.primary)?.email || aliases[0]?.email || userEmail;
}
}

const toRecipients: Sender[] = data.to.map((email) => ({
email,
name: email.split('@')[0] || 'User',
Expand Down Expand Up @@ -159,7 +181,7 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
subject: data.subject,
message: emailBody,
attachments: await serializeFiles(data.attachments),
fromEmail: aliases?.[0]?.email || userEmail,
fromEmail: fromEmail,
headers: {
'In-Reply-To': replyToMessage?.messageId ?? '',
References: [
Expand Down Expand Up @@ -246,6 +268,7 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
})}
autofocus={shouldFocus}
settingsLoading={settingsLoading}
replyingTo={replyToMessage?.sender.email}
/>
</div>
);
Expand Down