send , receive , detail email

This commit is contained in:
hamid zarghami
2025-07-09 17:00:21 +03:30
parent 72a6eaa156
commit 4258f57662
14 changed files with 926 additions and 97 deletions
+99 -22
View File
@@ -1,40 +1,117 @@
import { FC } from 'react'
import LogoImage from '../../assets/images/logo.svg'
import LogoSmallImage from '../../assets/images/logo-small.svg'
import { useAuthStore } from './store/AuthStore'
import LoginStep1 from './components/LoginStep1'
import LoginStep2 from './components/LoginStep2'
import LoginStep3 from './components/LoginStep3'
import Input from '../../components/Input'
import { useTranslation } from 'react-i18next'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import Error from '../../components/Error'
import Button from '../../components/Button'
import { useLoginWithPassword } from './hooks/useAuthData'
import { ErrorType } from '../../helpers/types'
import { setRefreshToken, setToken, getRedirectUrl } from '../../config/func'
import { toast } from '../../components/Toast'
import { Paths } from '../../utils/Paths'
type LoginFormType = {
email: string;
password: string;
}
const Login: FC = () => {
const { t } = useTranslation('global')
const loginWithPassword = useLoginWithPassword()
const { stepLogin, phone, email } = useAuthStore()
const formik = useFormik<LoginFormType>({
initialValues: {
email: '',
password: ''
},
validationSchema: Yup.object({
email: Yup.string()
.email(t('errors.invalid_email'))
.required(t('errors.required')),
password: Yup.string()
.required(t('errors.required'))
}),
onSubmit(values) {
loginWithPassword.mutate(values, {
onSuccess(data) {
setToken(data?.data?.accessToken?.token)
setRefreshToken(data?.data?.refreshToken?.token)
const redirectUrl = getRedirectUrl();
if (redirectUrl) {
window.location.href = redirectUrl;
return;
}
window.location.href = Paths.received
},
onError(error: ErrorType) {
toast(error?.response?.data?.error?.message[0], 'error')
},
})
},
})
return (
<div className='w-full h-full flex justify-center lg:py-[75px] py-4 lg:items-center lg:px-10 px-4'>
<div className='flex w-full max-h-[812px] max-w-[1200px] bg-secondary h-full rounded-3xl overflow-hidden'>
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
<div className='flex-1 min-w-[50%] overflow-y-auto bg-white lg:px-9 px-4 py-7'>
<div className='flex-1 h-full flex flex-col'>
<div className='flex relative flex-1 flex-col h-full justify-center'>
<img src={LogoSmallImage} className='w-8 hidden xl:block' />
<img src={LogoImage} className='w-44 right-0 left-0 mx-auto absolute top-10 xl:hidden' />
{
stepLogin === 1 ?
<LoginStep1 />
:
stepLogin === 2 && !!phone ?
<LoginStep2 />
: stepLogin === 2 && !!email ?
<LoginStep3 />
: stepLogin === 3 ?
<LoginStep3 />
: null
}
<div>
<div className='mt-5'>
<h2 className='text-2xl font-bold'>
{t('auth.welcome')}
</h2>
<p className='text-description text-sm mt-2'>
{t('auth.enter_info_login')}
</p>
</div>
<div className='mt-16 space-y-6'>
<div>
<Input
label={t('auth.email')}
placeholder={t('auth.enter_email')}
type='email'
className='text-right'
name='email'
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email && (
<Error errorText={formik.errors.email} />
)}
</div>
<div>
<Input
label={t('auth.password')}
placeholder={t('auth.enter_password')}
type='password'
className='text-right'
name='password'
onChange={formik.handleChange}
value={formik.values.password}
/>
{formik.touched.password && formik.errors.password && (
<Error errorText={formik.errors.password} />
)}
</div>
</div>
<Button
label={t('auth.login')}
className='mt-8'
onClick={() => formik.handleSubmit()}
loading={loginWithPassword.isPending}
/>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ import {
} from "../types/AuthTypes";
export const loginWithPassword = async (params: LoginWithPasswordType) => {
const { data } = await axios.post(`/auth/login/password`, params);
const { data } = await axios.post(`/auth/login`, params);
return data;
};
+145 -14
View File
@@ -1,6 +1,7 @@
import Button from '@/components/Button'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import AvatarImage from '@/assets/images/avatar.svg'
import { DocumentDownload, EmojiHappy } from 'iconsax-react'
import ForwardIcon from '@/assets/images/forward.svg'
@@ -8,12 +9,125 @@ import AnswerIcon from '@/assets/images/answer.svg'
import Header from './Components/Header'
import ReactQuill from 'react-quill-new';
import Select from '@/components/Select'
import { useGetMessageDetail, useSendEmail, useSaveDraft } from './hooks/useEmailData'
import { useAuthStore } from '../auth/store/AuthStore'
import { SendEmailDto } from './types/Types'
import { toast } from '@/components/Toast'
import { formatDate } from '@/config/func'
const DetailEmail: FC = () => {
const { t } = useTranslation()
const { id } = useParams<{ id: string }>()
const { email: userEmail } = useAuthStore()
const [showAnswer, setShowAnswer] = useState<boolean>(false)
const [value, setValue] = useState<string>('')
const [priority, setPriority] = useState<string>('')
// API hooks
const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', !!id)
const sendEmailMutation = useSendEmail()
const saveDraftMutation = useSaveDraft()
const priorityOptions = [
{ value: 'high', label: 'بالا' },
{ value: 'medium', label: 'متوسط' },
{ value: 'low', label: 'پایین' },
]
const handleSendReply = async () => {
if (!value.trim()) {
toast('لطفا متن پیام را وارد کنید', 'error')
return
}
if (!messageDetail) {
toast('اطلاعات پیام یافت نشد', 'error')
return
}
const replyData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
subject: `Re: ${messageDetail.subject}`,
html: value,
text: value.replace(/<[^>]*>/g, ''),
reference: {
inReplyTo: messageDetail.messageId,
originalMessageId: messageDetail.id.toString()
}
}
try {
await sendEmailMutation.mutateAsync(replyData)
toast('پاسخ با موفقیت ارسال شد', 'success')
setShowAnswer(false)
setValue('')
setPriority('')
} catch {
toast('خطا در ارسال پاسخ', 'error')
}
}
const handleSaveDraft = async () => {
if (!value.trim()) {
toast('متن پیام خالی است', 'error')
return
}
if (!messageDetail) {
toast('اطلاعات پیام یافت نشد', 'error')
return
}
const draftData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
subject: `Re: ${messageDetail.subject}`,
html: value,
text: value.replace(/<[^>]*>/g, ''),
isDraft: true,
reference: {
inReplyTo: messageDetail.messageId,
originalMessageId: messageDetail.id.toString()
}
}
try {
await saveDraftMutation.mutateAsync(draftData)
toast('پیش‌نویس ذخیره شد', 'success')
} catch {
toast('خطا در ذخیره پیش‌نویس', 'error')
}
}
const handleForward = () => {
// TODO: Implement forward functionality
toast('قابلیت ارسال به دیگران به زودی اضافه خواهد شد', 'info')
}
if (isLoading) {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='animate-pulse'>
<div className='h-6 bg-gray-200 rounded mb-4'></div>
<div className='h-4 bg-gray-200 rounded mb-2'></div>
<div className='h-4 bg-gray-200 rounded mb-2'></div>
<div className='h-20 bg-gray-200 rounded'></div>
</div>
</div>
)
}
if (error || !messageDetail) {
return (
<div className='bg-white rounded-4xl p-8'>
<div className='text-center text-red-500'>
خطا در بارگذاری پیام
</div>
</div>
)
}
return (
<div className='bg-white rounded-4xl p-8'>
@@ -23,7 +137,7 @@ const DetailEmail: FC = () => {
<div className='flex xl:flex-row flex-col-reverse gap-4 xl:items-center'>
<div className='flex gap-1 items-center'>
<div className='font-extralight hidden xl:block'>{t('mail.subject')}</div>
<div className='leading-7 text-sm'>لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ</div>
<div className='leading-7 text-sm'>{messageDetail.subject || 'بدون موضوع'}</div>
</div>
<div className='flex gap-2'>
<Button
@@ -38,28 +152,41 @@ const DetailEmail: FC = () => {
</div>
<div className='flex justify-end text-description xl:text-sm text-[10px]'>
۵ اسفندماه ۱۴۰۳، ساعت ۱۲:۰۰
{formatDate(messageDetail.date)}
</div>
</div>
<div className='mt-6 flex gap-4 items-center'>
<img src={AvatarImage} className='size-10' />
<div className='text-sm'>مهرداد مظفری</div>
<div className='text-sm'>{messageDetail.from.name || messageDetail.from.address}</div>
</div>
<p className='xl:mt-8 mt-3 text-[13px] leading-7 font-light'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آیندهلورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است و برای شرایط فعلی تکنولوژی مورد نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد کتابهای زیادی در شصت و سه درصد گذشته حال و آینده
</p>
<div className='xl:mt-8 mt-3 text-[13px] leading-7 font-light'>
{messageDetail.html && messageDetail.html.length > 0 ? (
<div dangerouslySetInnerHTML={{ __html: messageDetail.html.join('') }} />
) : (
<p>{messageDetail.text || 'محتوای پیام'}</p>
)}
</div>
<div className='mt-10 text-sm font-bold'>
{t('mail.attachments')}
</div>
<div className='flex border-b border-border pb-10 mt-4 flex-wrap gap-2.5 items-center'>
<div className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<span>Lorem Ipsum.pdf</span>
<DocumentDownload size={20} color='#0038FF' />
</div>
{messageDetail.attachments && messageDetail.attachments.length > 0 ? (
messageDetail.attachments.map((attachment, index) => (
<div key={index} className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<span>{attachment.filename || `attachment-${index + 1}`}</span>
<DocumentDownload size={20} color='#0038FF' className='cursor-pointer' />
</div>
))
) : (
<div className='bg-[#EBEDF5] text-sm flex gap-2 h-10 rounded-full items-center px-4'>
<span>Lorem Ipsum.pdf</span>
<DocumentDownload size={20} color='#0038FF' />
</div>
)}
</div>
{
@@ -73,7 +200,7 @@ const DetailEmail: FC = () => {
<div>{t('mail.answer')}</div>
</div>
<div className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
<div onClick={handleForward} className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
<img src={ForwardIcon} className='w-[17px]' />
<div>{t('mail.forward')}</div>
</div>
@@ -108,19 +235,25 @@ const DetailEmail: FC = () => {
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
<div>
<Select
items={[]}
items={priorityOptions}
placeholder={t('new_message.select_priority')}
className='xl:w-[190px]'
value={priority}
onChange={(e) => setPriority(e.target.value)}
/>
</div>
<div className='flex-1 justify-end flex gap-3'>
<Button
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={handleSaveDraft}
loading={saveDraftMutation.isPending}
/>
<Button
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
label={t('new_message.send')}
onClick={handleSendReply}
loading={sendEmailMutation.isPending}
/>
</div>
</div>
@@ -128,8 +261,6 @@ const DetailEmail: FC = () => {
</div>
</div>
}
</div>
)
}
+56 -46
View File
@@ -3,46 +3,43 @@ import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2, Trash, Edit, Archive, ArchiveSlash } from 'iconsax-react';
import { InfoCircle, More, Refresh2, Trash, Edit, Archive } from 'iconsax-react';
import { RowActionItem } from '../../components/RowActionsDropdown';
import { useNavigate } from 'react-router-dom';
interface Message extends Record<string, unknown> {
id: string;
title: string;
sender: string;
date: string;
read: boolean;
}
import { useGetInbox } from './hooks/useEmailData';
import { InboxMessage } from './types/Types';
import { formatDate } from '@/config/func';
const List: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const [isLoading] = useState(false);
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({});
const messageData: Message[] = [
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
];
const { data: inboxData, isLoading, refetch } = useGetInbox({
page: currentPage,
limit: 10,
...filters
});
const columns: ColumnType<Message>[] = [
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
const columns: ColumnType<InboxMessage>[] = [
{
key: 'title',
key: 'subject',
title: 'عنوان پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
<span className="truncate">{item.title}</span>
{/* {!item.seen && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />} */}
<span className="truncate">{item.subject || 'بدون موضوع'}</span>
</div>
)
},
{
key: 'sender',
key: 'from',
title: 'فرستنده',
render: (item) => (
<span className="truncate">{item.sender}</span>
<span className="truncate">{item.from.name || item.from.address}</span>
)
},
{
@@ -50,14 +47,19 @@ const List: FC = () => {
title: 'تاریخ',
align: 'center',
render: (item) => (
<span className="text-xs md:text-sm">{item.date}</span>
<span>{formatDate(item.date)}</span>
)
},
];
const tableActions = (
<div className='flex items-center gap-2 md:gap-4'>
<Refresh2 size={18} color='black' className="cursor-pointer" />
<Refresh2
size={18}
color='black'
className="cursor-pointer"
onClick={() => refetch()}
/>
<More size={18} color='black' className='rotate-90 cursor-pointer' />
</div>
);
@@ -71,10 +73,11 @@ const List: FC = () => {
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
setFilters(newFilters);
setCurrentPage(1); // Reset to first page when filters change
};
const handleSelectionChange = (selectedRows: Message[]) => {
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
setSelectedMessages(selectedRows);
};
@@ -93,16 +96,16 @@ const List: FC = () => {
// اینجا منطق علامت‌گذاری را پیاده‌سازی کنید
};
const getRowActions = (message: Message): RowActionItem[] => [
const getRowActions = (message: InboxMessage): RowActionItem[] => [
{
label: 'خروج از آرشیو',
icon: <ArchiveSlash size={16} color="black" />,
onClick: () => console.log('خروج از آرشیو', message.id),
label: message.seen ? 'علامت‌گذاری به عنوان خوانده نشده' : 'علامت‌گذاری به عنوان خوانده شده',
icon: <Edit size={16} color="black" />,
onClick: () => console.log('تغییر وضعیت خوانده شده', message.id),
},
{
label: 'خوانده شده',
icon: <Edit size={16} color="black" />,
onClick: () => console.log('علامت‌گذاری به عنوان خوانده شده', message.id),
label: 'بایگانی',
icon: <Archive size={16} color="black" />,
onClick: () => console.log('بایگانی پیام', message.id),
},
{
label: 'حذف',
@@ -111,22 +114,29 @@ const List: FC = () => {
},
];
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const messages = inboxData?.data?.results || [];
const pager = inboxData?.data?.pager;
return (
<div className='mt-2 md:mt-4 px-2 md:px-0'>
<h1 className="text-lg mb-4 md:mb-0">{t('received.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
{ type: 'date', name: 'dateFrom', placeholder: t('received.from_date') },
{ type: 'date', name: 'dateTo', placeholder: t('received.to_date') },
{
type: 'select',
name: 'status',
name: 'isRead',
placeholder: t('received.all'),
options: [
{ value: 'all', label: t('received.all') },
{ value: 'read', label: t('received.read') },
{ value: 'unread', label: t('received.unread') }
{ value: '', label: t('received.all') },
{ value: 'true', label: t('received.read') },
{ value: 'false', label: t('received.unread') }
]
},
{ type: 'input', name: 'search', placeholder: t('received.search') }
@@ -135,9 +145,9 @@ const List: FC = () => {
searchField="search"
/>
<Table<Message>
<Table<InboxMessage>
columns={columns}
data={messageData}
data={messages}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
@@ -152,11 +162,11 @@ const List: FC = () => {
}
selectable={true}
rowActions={getRowActions}
pagination={{
totalPages: 10,
currentPage: 1,
onPageChange: () => { }
}}
pagination={pager ? {
totalPages: pager.totalPages,
currentPage: pager.page,
onPageChange: handlePageChange
} : undefined}
/>
</div>
+45
View File
@@ -0,0 +1,45 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as api from '../service/EmailService';
import { SendEmailDto, MessageListQueryDto } from '../types/Types';
export const useSendEmail = () => {
return useMutation({
mutationFn: (variables: SendEmailDto) => api.sendEmail(variables),
});
};
export const useSaveDraft = () => {
return useMutation({
mutationFn: (variables: SendEmailDto) => api.saveDraft(variables),
});
};
export const useGetInbox = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['inbox', query],
queryFn: () => api.getInbox(query),
staleTime: 1000 * 60 * 5, // 5 minutes
});
};
export const useGetMessageDetail = (messageId: string, enabled: boolean = true) => {
return useQuery({
queryKey: ['message', messageId],
queryFn: () => api.getMessageDetail(messageId),
enabled: !!messageId && enabled,
staleTime: 1000 * 60 * 10, // 10 minutes
});
};
export const useDeleteMessage = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (messageId: string) => api.deleteMessage(messageId),
onSuccess: () => {
// Invalidate inbox queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['inbox'] });
},
});
};
@@ -0,0 +1,42 @@
import axios from "../../../config/axios";
import {
SendEmailDto,
MessageListQueryDto,
InboxResponse,
MessageDetailResponse,
MessageDetail,
} from "../types/Types";
export const sendEmail = async (params: SendEmailDto) => {
const { data } = await axios.post(`/email/send`, params);
return data;
};
export const saveDraft = async (params: SendEmailDto) => {
const { data } = await axios.post(`/email/draft`, {
...params,
isDraft: true,
});
return data;
};
export const getInbox = async (
query: MessageListQueryDto
): Promise<InboxResponse> => {
const { data } = await axios.get(`/email/messages/inbox`, { params: query });
return data;
};
export const getMessageDetail = async (
messageId: string
): Promise<MessageDetail> => {
const { data }: { data: MessageDetailResponse } = await axios.get(
`/email/messages/inbox/${messageId}`
);
return data.data.message;
};
export const deleteMessage = async (messageId: string): Promise<void> => {
const { data } = await axios.delete(`/email/messages/inbox/${messageId}`);
return data;
};
+288
View File
@@ -0,0 +1,288 @@
export interface EmailRecipientDto {
name?: string;
address: string;
}
export interface EmailHeaderDto {
key: string;
value: string;
}
export interface EmailAttachmentDto {
filename?: string;
contentType?: string;
encoding?: string;
contentTransferEncoding?: string;
contentDisposition?: "inline" | "attachment";
content: string;
cid?: string;
}
export interface DraftReferenceDto {
mailbox: string;
id: number;
}
export interface EmailEnvelopeDto {
from: EmailRecipientDto;
to: EmailRecipientDto[];
}
export interface SendEmailDto {
mailbox?: string;
from: EmailRecipientDto;
replyTo?: EmailRecipientDto;
to: EmailRecipientDto[];
cc?: EmailRecipientDto[];
bcc?: EmailRecipientDto[];
headers?: EmailHeaderDto[];
subject?: string;
text?: string;
html?: string;
attachments?: EmailAttachmentDto[];
meta?: Record<string, unknown>;
sess?: string;
ip?: string;
reference?: Record<string, unknown>;
isDraft?: boolean;
draft?: DraftReferenceDto;
sendTime?: string;
uploadOnly?: boolean;
envelope?: EmailEnvelopeDto;
}
export interface MessageListQueryDto {
page?: number;
limit?: number;
order?: string;
search?: string;
from?: string;
subject?: string;
dateFrom?: string;
dateTo?: string;
hasAttachment?: boolean;
isRead?: boolean;
isFlagged?: boolean;
}
// Updated types based on actual API response
export interface InboxMessage extends Record<string, unknown> {
id: number;
mailbox: string;
thread: string;
from: {
address: string;
name: string;
};
to: Array<{
address: string;
name: string;
}>;
cc: Array<{
address: string;
name: string;
}>;
bcc: Array<{
address: string;
name: string;
}>;
messageId: string;
subject: string;
date: string;
idate: string;
intro: string;
attachments: boolean;
attachmentsList: EmailAttachmentDto[];
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
references: string[];
contentType: {
value: string;
params: {
boundary: string;
};
};
}
export interface InboxPager {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: boolean;
}
export interface InboxResponse {
statusCode: number;
success: boolean;
data: {
pager: InboxPager;
message: string;
results: InboxMessage[];
};
}
// Legacy types (keeping for backward compatibility)
export interface MessageDto {
id: string;
mailbox: string;
uid: number;
flags: string[];
envelope: {
date: string;
subject: string;
from: EmailRecipientDto[];
to: EmailRecipientDto[];
cc?: EmailRecipientDto[];
bcc?: EmailRecipientDto[];
messageId: string;
inReplyTo?: string;
references?: string[];
};
bodyStructure: Record<string, unknown>;
source?: string;
text?: string;
html?: string;
attachments?: EmailAttachmentDto[];
headers?: EmailHeaderDto[];
size: number;
created: string;
updated: string;
}
export interface MessageListResponse {
messages: MessageDto[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface UserMailboxMessageParamDto {
messageId: string;
}
// New interface based on actual API response
export interface MessageDetailResponse {
statusCode: number;
success: boolean;
data: {
message: {
success: boolean;
id: number;
mailbox: string;
thread: string;
user: string;
envelope: {
from: string;
rcpt: Array<{
value: string;
formatted: string;
}>;
};
from: {
address: string;
name: string;
};
to: Array<{
address: string;
name: string;
}>;
subject: string;
messageId: string;
date: string;
idate: string;
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
html: string[];
text: string;
attachments: EmailAttachmentDto[];
references: string[];
metaData: Record<string, unknown>;
verificationResults: {
tls: {
name: string;
standardName: string;
version: string;
};
spf: string;
dkim: string;
arc: boolean;
bimi: boolean;
};
contentType: {
value: string;
params: {
boundary: string;
};
};
};
};
}
export interface MessageDetail {
success: boolean;
id: number;
mailbox: string;
thread: string;
user: string;
envelope: {
from: string;
rcpt: Array<{
value: string;
formatted: string;
}>;
};
from: {
address: string;
name: string;
};
to: Array<{
address: string;
name: string;
}>;
subject: string;
messageId: string;
date: string;
idate: string;
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
html: string[];
text: string;
attachments: EmailAttachmentDto[];
references: string[];
metaData: Record<string, unknown>;
verificationResults: {
tls: {
name: string;
standardName: string;
version: string;
};
spf: string;
dkim: string;
arc: boolean;
bimi: boolean;
};
contentType: {
value: string;
params: {
boundary: string;
};
};
}