'use server'; // Knox Mail API TypeScript Library for Next.js Server Actions // Based on Knox Mail API Guide v2.0 export interface KnoxMailConfig { baseUrl: string; systemId: string; userId: string; } // 공통 타입 정의 export interface KnoxMailResponse { result: 'success' | 'error'; mailId?: string; data?: T; error?: { code: string; message: string; }; } // 발신인 정보 export interface Sender { emailAddress: string; } // 수신인 정보 export interface Recipient { emailAddress: string; recipientType: 'TO' | 'CC' | 'BCC'; } // 일반 메일 발신 요청 export interface SendMailRequest { subject: string; contents: string; contentType: 'TEXT' | 'HTML' | 'MIME'; docSecuType: 'PERSONAL' | 'OFFICIAL'; sender: Sender; recipients: Recipient[]; attachments?: File[]; reservedTime?: string; // yyyy-MM-dd HH:mm 형식 } // 대외비 메일 DRM 옵션 export interface DrmOption { validDays: string; useCount: string; canView: string; canPrint: string; notifyExternalUser: string; notifyInternalUser: string; } // 대외비 메일 발신 요청 export interface SendSecuMailRequest extends Omit { docSecuType: 'CONFIDENTIAL' | 'CONFIDENTIAL_STRICT'; drmOption: DrmOption; } // 메일 발신 취소 요청 export interface CancelMailRequest { recipients: string[]; } // 메일 별 수신상태 조회 요청 export interface DeliveryStatusCountRequest { mailIds: string[]; } // 수신상태 카운트 응답 export interface DeliveryStatusCount { mailId: string; totalStatusCount: number; sendingCount: number; openCount: number; unopenCount: number; etcCount: number; sendCancelCount: number; sendCancelReqCount: number; sendBlockCount: number; sendFailCount: number; drmProcessCount: number; drmFailCount: number; reserveCount: number; reserveCancelCount: number; unknownCount: number; } // 수신인 별 수신상태 응답 export interface RecipientDeliveryStatus { name: string; position: string; enName: string; enPosition: string; company: string; department: string; enDepartment: string; email: string; userID: string; recipientType: 'TO' | 'CC' | 'BCC'; updateTime: { month: number; year: number; dayOfMonth: number; hourOfDay: number; minute: number; second: number; }; status: string; needOpenNotify: boolean; isDLAddr: boolean; } // 내부 유틸리티 함수들 function getHeaders(systemId: string): Record { return { 'System-ID': systemId, 'Content-Type': 'application/json', }; } async function makeRequest( config: KnoxMailConfig, endpoint: string, method: 'GET' | 'POST', data?: unknown, isMultipart = false ): Promise> { const url = `${config.baseUrl}${endpoint}?userId=${config.userId}`; const headers = getHeaders(config.systemId); if (isMultipart) { delete headers['Content-Type']; // multipart/form-data는 브라우저가 자동으로 설정 } try { const response = await fetch(url, { method, headers, body: data ? (isMultipart ? data as FormData : JSON.stringify(data)) : undefined, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); return { result: 'error', error: { code: response.status.toString(), message: errorData.message || response.statusText, }, }; } const result = await response.json(); return { result: 'success', ...result, }; } catch (error) { return { result: 'error', error: { code: 'NETWORK_ERROR', message: error instanceof Error ? error.message : 'Unknown error', }, }; } } function createFormData(mailData: Record, attachments?: File[]): FormData { const formData = new FormData(); // 메일 데이터를 JSON으로 추가 formData.append('mail', JSON.stringify(mailData)); // 첨부파일이 있으면 추가 if (attachments && attachments.length > 0) { attachments.forEach(file => { formData.append('attachments', file); }); } return formData; } // Next.js 서버 액션 함수들 /** * 일반 메일 발신 서버 액션 * @param config Knox Mail API 설정 * @param request 메일 발신 요청 데이터 * @returns 발신 결과 (mailId 포함) */ export async function sendMail( config: KnoxMailConfig, request: SendMailRequest ): Promise> { const { attachments, ...mailData } = request; if (attachments && attachments.length > 0) { const formData = createFormData(mailData, attachments); return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/send', 'POST', formData, true); } else { return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/send', 'POST', mailData); } } /** * 대외비 메일 발신 서버 액션 * @param config Knox Mail API 설정 * @param request 대외비 메일 발신 요청 데이터 * @returns 발신 결과 (mailId 포함) */ export async function sendSecuMail( config: KnoxMailConfig, request: SendSecuMailRequest ): Promise> { const { attachments, ...mailData } = request; if (attachments && attachments.length > 0) { const formData = createFormData(mailData, attachments); return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/secu-send', 'POST', formData, true); } else { return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/secu-send', 'POST', mailData); } } /** * 메일 발신 취소 서버 액션 * @param config Knox Mail API 설정 * @param mailId 취소할 메일 ID * @param request 취소 요청 데이터 (수신인 목록) * @returns 취소 결과 */ export async function cancelMail( config: KnoxMailConfig, mailId: string, request: CancelMailRequest ): Promise { return makeRequest(config, `/mail/api/v2.0/mails/${mailId}/cancel`, 'POST', request); } /** * 메일 별 수신상태 조회 서버 액션 * @param config Knox Mail API 설정 * @param request 조회할 메일 ID 목록 * @returns 메일별 수신상태 카운트 목록 */ export async function getDeliveryStatusCount( config: KnoxMailConfig, request: DeliveryStatusCountRequest ): Promise> { return makeRequest(config, '/mail/api/v2.0/mails/deliverystatus/count', 'POST', request); } /** * 수신인 별 수신상태 조회 서버 액션 * @param config Knox Mail API 설정 * @param mailId 조회할 메일 ID * @returns 수신인별 수신상태 목록 */ export async function getRecipientDeliveryStatus( config: KnoxMailConfig, mailId: string ): Promise> { return makeRequest(config, `/mail/api/v2.0/mails/${mailId}/deliverystatus`, 'GET'); } // 편의 함수들 export async function createKnoxMailConfig( baseUrl: string, systemId: string, userId: string ): Promise { return { baseUrl, systemId, userId, }; } // 에러 코드 상수 export const KNOX_MAIL_ERROR_CODES = { ML1001: '필수값 중 입력되지 않은 값이 있습니다.', ML1002: '사용자 정보가 존재하지 않습니다.', ML1003: '요청하신 데이터가 존재하지 않습니다.', ML1101: '본문 (content) 값이 입력되지 않았습니다.', ML1102: '첨부 파일 처리 중 에러가 발생했습니다.', ML1104: '일반 메일 발신 중 docSecuType 값이 유효하지 않습니다.', ML1107: '대외비/극비 보안 옵션 값이 유효하지 않습니다.', ML1108: '대외비/극비 메일 contentType 값이 유효하지 않습니다.', ML1109: '대외비/극비 메일 docSecuType 값이 유효하지 않습니다.', ML1110: '극비 메일일 경우에는 내부 수신인에게만 메일 발신이 가능합니다.', ML1111: '대외비/극비 메일에서 허용되지 않는 첨부 파일의 확장자가 있습니다.', ML1112: '메일 제목 크기가 300 bytes를 초과하였습니다.', ML1114: '메일 발신 API에서 허용되는 content 크기를 초과하였습니다.', ML1115: '일반 메일 contentType 값이 유효하지 않습니다.', ML1117: '메일 발신 API에서 발신가능한 최대 수신인을 초과하였습니다.', ML1118: '수신자의 수신 타입이 잘못 설정되었습니다.', CO400: '잘못된 요청입니다.', } as const; // 타입 가드 함수들 export function isKnoxMailError(response: KnoxMailResponse): response is KnoxMailResponse & { error: { code: string; message: string } } { return response.result === 'error' && !!response.error; } export function isKnoxMailSuccess(response: KnoxMailResponse): response is KnoxMailResponse & { result: 'success' } { return response.result === 'success'; }