chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,34 @@
import type { Transporter } from 'nodemailer'
import type {
MailProviderName,
ProcessedEmailData,
SendEmailResult,
} from '@/lib/messaging/email/types'
export async function sendViaNodemailer(
transporter: Transporter,
data: ProcessedEmailData,
provider: MailProviderName
): Promise<SendEmailResult> {
const info = await transporter.sendMail({
from: data.senderEmail,
to: data.to,
subject: data.subject,
html: data.html,
text: data.text,
replyTo: data.replyTo,
headers: Object.keys(data.headers).length > 0 ? data.headers : undefined,
attachments: data.attachments?.map((att) => ({
filename: att.filename,
content: att.content,
contentType: att.contentType,
contentDisposition: att.disposition || 'attachment',
})),
})
return {
success: true,
message: `Email sent successfully via ${provider}`,
data: { id: info.messageId },
}
}
@@ -0,0 +1,45 @@
import { EmailClient, type EmailMessage } from '@azure/communication-email'
import { env } from '@/lib/core/config/env'
import type { MailProvider, ProcessedEmailData, SendEmailResult } from '@/lib/messaging/email/types'
function extractBareAddress(addressOrFormatted: string): string {
if (!addressOrFormatted.includes('<')) return addressOrFormatted
return addressOrFormatted.match(/<(.+)>/)?.[1] ?? addressOrFormatted
}
export function createAzureProvider(): MailProvider | null {
const connectionString = env.AZURE_ACS_CONNECTION_STRING
if (!connectionString || connectionString.trim() === '') return null
const client = new EmailClient(connectionString)
return {
name: 'azure',
async send(data: ProcessedEmailData): Promise<SendEmailResult> {
if (!data.html && !data.text) {
throw new Error('Azure Communication Services requires either HTML or text content')
}
const message: EmailMessage = {
senderAddress: extractBareAddress(data.senderEmail),
content: data.html
? { subject: data.subject, html: data.html }
: { subject: data.subject, plainText: data.text as string },
recipients: {
to: (Array.isArray(data.to) ? data.to : [data.to]).map((address) => ({ address })),
},
headers: data.headers,
}
const poller = await client.beginSend(message)
const result = await poller.pollUntilDone()
if (result.status !== 'Succeeded') {
throw new Error(`Azure Communication Services failed with status: ${result.status}`)
}
return {
success: true,
message: 'Email sent successfully via Azure Communication Services',
data: { id: result.id },
}
},
}
}
@@ -0,0 +1,28 @@
import { createLogger } from '@sim/logger'
import { createAzureProvider } from '@/lib/messaging/email/providers/azure'
import { createResendProvider } from '@/lib/messaging/email/providers/resend'
import { createSesProvider } from '@/lib/messaging/email/providers/ses'
import { createSmtpProvider } from '@/lib/messaging/email/providers/smtp'
import type { MailProvider } from '@/lib/messaging/email/types'
const logger = createLogger('MailProviders')
const factories = [
createResendProvider,
createSesProvider,
createSmtpProvider,
createAzureProvider,
] as const
function safeCreate(factory: () => MailProvider | null): MailProvider | null {
try {
return factory()
} catch (error) {
logger.error('Mail provider factory threw at startup; skipping', error)
return null
}
}
export const activeProviders: readonly MailProvider[] = factories
.map((factory) => safeCreate(factory))
.filter((provider): provider is MailProvider => provider !== null)
@@ -0,0 +1,71 @@
import { Resend } from 'resend'
import { env } from '@/lib/core/config/env'
import type {
BatchSendEmailResult,
MailProvider,
ProcessedEmailData,
SendEmailResult,
} from '@/lib/messaging/email/types'
function isConfigured(key: string | undefined): key is string {
return !!key && key !== 'placeholder' && key.trim() !== ''
}
function toResendPayload(data: ProcessedEmailData) {
return {
from: data.senderEmail,
to: data.to,
subject: data.subject,
html: data.html,
text: data.text,
replyTo: data.replyTo,
headers: Object.keys(data.headers).length > 0 ? data.headers : undefined,
attachments: data.attachments?.map((att) => ({
filename: att.filename,
content: typeof att.content === 'string' ? att.content : att.content.toString('base64'),
contentType: att.contentType,
disposition: att.disposition || 'attachment',
})),
}
}
export function createResendProvider(): MailProvider | null {
if (!isConfigured(env.RESEND_API_KEY)) return null
const client = new Resend(env.RESEND_API_KEY)
return {
name: 'resend',
async send(data: ProcessedEmailData): Promise<SendEmailResult> {
const payload = toResendPayload(data)
const { data: responseData, error } = await client.emails.send(payload as never)
if (error) {
throw new Error(error.message || 'Failed to send email via Resend')
}
return {
success: true,
message: 'Email sent successfully via Resend',
data: responseData,
}
},
async sendBatch(emails: ProcessedEmailData[]): Promise<BatchSendEmailResult> {
const payloads = emails.map(toResendPayload)
const response = await client.batch.send(payloads as never)
if (response.error) {
throw new Error(response.error.message || 'Resend batch API error')
}
const results: SendEmailResult[] = emails.map((_, index) => ({
success: true,
message: 'Email sent successfully via Resend batch',
data: { id: `batch-${index}` },
}))
return {
success: true,
message: 'All batch emails sent successfully via Resend',
results,
data: { count: emails.length },
}
},
}
}
@@ -0,0 +1,28 @@
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'
import nodemailer from 'nodemailer'
import type SESTransport from 'nodemailer/lib/ses-transport'
import { env } from '@/lib/core/config/env'
import { sendViaNodemailer } from '@/lib/messaging/email/providers/_nodemailer'
import type { MailProvider } from '@/lib/messaging/email/types'
/**
* AWS SES via nodemailer's SES transport using the AWS SDK v3 client.
* Credentials resolve through the SDK's default provider chain (env vars,
* shared config, ECS/EKS task role, EC2 instance profile, SSO).
*/
export function createSesProvider(): MailProvider | null {
const region = env.AWS_SES_REGION
if (!region) return null
const sesClient = new SESv2Client({ region })
const sesOptions: SESTransport.Options = {
// double-cast-allowed: @types/nodemailer bundles a nested @aws-sdk/client-sesv2 whose nominal class types do not unify with the top-level install
SES: { sesClient, SendEmailCommand } as unknown as SESTransport.Options['SES'],
}
const transporter = nodemailer.createTransport(sesOptions)
return {
name: 'ses',
send: (data) => sendViaNodemailer(transporter, data, 'ses'),
}
}
@@ -0,0 +1,40 @@
import { createLogger } from '@sim/logger'
import nodemailer from 'nodemailer'
import { env, envBoolean, envNumber } from '@/lib/core/config/env'
import { sendViaNodemailer } from '@/lib/messaging/email/providers/_nodemailer'
import type { MailProvider } from '@/lib/messaging/email/types'
const logger = createLogger('SmtpMailProvider')
export function createSmtpProvider(): MailProvider | null {
const host = env.SMTP_HOST
if (!host) return null
const port = envNumber(env.SMTP_PORT, 0, { min: 1 })
if (port === 0) {
logger.warn(
'SMTP_HOST is set but SMTP_PORT is missing or invalid; skipping SMTP provider. Set SMTP_PORT to 465 (TLS), 587 (STARTTLS), or 25 (plain).'
)
return null
}
const user = env.SMTP_USER
const pass = env.SMTP_PASS
if ((user && !pass) || (!user && pass)) {
logger.warn(
'SMTP_USER and SMTP_PASS must both be set for authenticated relays; proceeding without auth.'
)
}
const transporter = nodemailer.createTransport({
host,
port,
secure: envBoolean(env.SMTP_SECURE) ?? port === 465,
auth: user && pass ? { user, pass } : undefined,
})
return {
name: 'smtp',
send: (data) => sendViaNodemailer(transporter, data, 'smtp'),
}
}