Files
oneuptime/App/FeatureSet/Telemetry/API/ProbeIngest/IncomingEmail.ts
Nawaz Dhandala 5f398bdb31 Add utility classes for telemetry: Monitor, StackTrace, and Syslog parsing
- Implemented MonitorUtil for managing monitor secrets and populating them in monitor steps and tests.
- Created StackTraceParser to parse and structure stack traces from various programming languages.
- Developed SyslogParser to handle and parse syslog messages in both RFC 5424 and RFC 3164 formats.
2026-04-02 14:04:13 +01:00

124 lines
4.1 KiB
TypeScript

import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from "Common/Server/Utils/Express";
import Response from "Common/Server/Utils/Response";
import BadDataException from "Common/Types/Exception/BadDataException";
import logger from "Common/Server/Utils/Logger";
import InboundEmailProviderFactory from "Common/Server/Services/InboundEmail/InboundEmailProviderFactory";
import InboundEmailProvider, {
ParsedInboundEmail,
} from "Common/Server/Services/InboundEmail/InboundEmailProvider";
import { JSONObject } from "Common/Types/JSON";
import TelemetryQueueService from "../../Services/Queue/TelemetryQueueService";
import MultipartFormDataMiddleware from "Common/Server/Middleware/MultipartFormData";
const router: ExpressRouter = Express.getRouter();
/*
* Webhook endpoint for SendGrid inbound emails
* SendGrid sends data as multipart/form-data
* The webhook secret must be passed as the last path segment: /incoming-email/sendgrid/:secret
*/
router.post(
"/incoming-email/sendgrid/:secret",
MultipartFormDataMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
try {
logger.debug("Received incoming email webhook");
// Log raw body for debugging
logger.debug(
`Request body keys: ${Object.keys(req.body || {}).join(", ")}`,
);
// Check if inbound email is configured
if (!InboundEmailProviderFactory.isConfigured()) {
logger.error("Inbound email is not configured");
throw new BadDataException(
"Inbound email is not configured. Please set the INBOUND_EMAIL_DOMAIN environment variable.",
);
}
const provider: InboundEmailProvider =
InboundEmailProviderFactory.getProvider();
// Get the secret from the URL path if provided
const pathSecret: string = req.params["secret"] || "";
// Validate the webhook request (secret from path takes precedence)
const isValid: boolean = await provider.validateWebhook({
headers: req.headers as Record<string, string>,
body: req.body as JSONObject,
pathSecret: pathSecret,
});
if (!isValid) {
logger.error("Invalid webhook signature");
throw new BadDataException("Invalid webhook signature");
}
// Parse the inbound email
const parsedEmail: ParsedInboundEmail = await provider.parseInboundEmail(
req.body as JSONObject,
);
logger.debug(`Parsed email from: ${parsedEmail.from}`);
logger.debug(`Parsed email to: ${parsedEmail.to}`);
logger.debug(`Parsed email subject: ${parsedEmail.subject}`);
// Extract secret key from the "to" address
const secretKey: string | null = provider.extractSecretKeyFromEmail(
parsedEmail.to,
);
if (!secretKey) {
logger.error(
`Could not extract secret key from email: ${parsedEmail.to}`,
);
throw new BadDataException(
"Invalid monitor email address. Could not extract secret key.",
);
}
logger.debug(`Extracted secret key: ${secretKey}`);
// Queue the email for async processing using the unified Telemetry queue
await TelemetryQueueService.addIncomingEmailJob({
secretKey: secretKey,
emailFrom: parsedEmail.from,
emailTo: parsedEmail.to,
emailSubject: parsedEmail.subject,
emailBody: parsedEmail.body,
emailBodyHtml: parsedEmail.bodyHtml,
emailHeaders: parsedEmail.headers,
attachments: parsedEmail.attachments,
});
logger.debug("Email queued for processing");
// Return 202 Accepted immediately
return Response.sendJsonObjectResponse(req, res, {
status: "accepted",
message: "Email queued for processing",
});
} catch (error) {
logger.error("Error processing incoming email webhook:");
logger.error(error);
if (error instanceof BadDataException) {
return Response.sendErrorResponse(req, res, error);
}
return Response.sendErrorResponse(
req,
res,
new BadDataException("Failed to process incoming email"),
);
}
},
);
export default router;