mailblock
Version:
Reserved package name for the Mailblock email SDK.
346 lines (307 loc) • 9.23 kB
JavaScript
class EmailBuilder {
constructor(client) {
this.client = client;
this.emailData = {};
}
to(email) {
if (!this._isValidEmail(email)) {
throw new Error(`Invalid 'to' email address: ${email}`);
}
this.emailData.to = email;
return this;
}
from(email) {
if (!this._isValidEmail(email)) {
throw new Error(`Invalid 'from' email address: ${email}`);
}
this.emailData.from = email;
return this;
}
subject(subject) {
if (
!subject ||
typeof subject !== "string" ||
subject.trim().length === 0
) {
throw new Error("Subject must be a non-empty string");
}
this.emailData.subject = subject.trim();
return this;
}
text(content) {
if (!content || typeof content !== "string") {
throw new Error("Text content must be a non-empty string");
}
this.emailData.text = content;
return this;
}
html(content) {
if (!content || typeof content !== "string") {
throw new Error("HTML content must be a non-empty string");
}
this.emailData.html = content;
return this;
}
scheduleAt(date) {
if (date instanceof Date) {
if (date <= new Date()) {
throw new Error("Scheduled date must be in the future");
}
this.emailData.scheduledAt = date;
} else if (typeof date === "string") {
const parsedDate = new Date(date);
if (isNaN(parsedDate.getTime())) {
throw new Error("Invalid date format for scheduling");
}
if (parsedDate <= new Date()) {
throw new Error("Scheduled date must be in the future");
}
this.emailData.scheduledAt = parsedDate;
} else {
throw new Error(
"Scheduled date must be a Date object or valid date string"
);
}
return this;
}
async send() {
return this.client.sendEmail(this.emailData);
}
_isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
class Mailblock {
constructor(apiKey, options = {}) {
if (!apiKey) {
throw new Error("API key is required");
}
if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
throw new Error("API key must be a non-empty string");
}
this.apiKey = apiKey.trim();
this.baseUrl = "https://sdk-backend-production-20e1.up.railway.app";
this.debug = options.debug || false;
this.logger = options.logger || console;
}
_log(level, message, data = null) {
if (!this.debug) return;
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] Mailblock ${level.toUpperCase()}: ${message}`;
if (data) {
this.logger[level](logMessage, data);
} else {
this.logger[level](logMessage);
}
}
_generateRequestId() {
return 'req_' + Math.random().toString(36).substring(2, 11) + Date.now().toString(36);
}
email() {
return new EmailBuilder(this);
}
async sendEmail({ to, from, subject, text, html, scheduledAt }) {
const requestId = this._generateRequestId();
const startTime = Date.now();
const timestamp = new Date().toISOString();
this._log('info', `Initiating email send request`, { requestId, to, from, subject: subject?.substring(0, 50) + '...' });
// Validation errors
if (!to) {
return {
success: false,
error: "Recipient email address (to) is required",
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
if (!from) {
return {
success: false,
error: "Sender email address (from) is required",
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
if (!subject) {
return {
success: false,
error: "Email subject is required",
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
if (!text && !html) {
return {
success: false,
error: "Either text or html content is required",
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(to)) {
return {
success: false,
error: `Invalid recipient email address: ${to}`,
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
if (!emailRegex.test(from)) {
return {
success: false,
error: `Invalid sender email address: ${from}`,
errorType: "VALIDATION_ERROR",
statusCode: null,
requestId,
timestamp,
duration: Date.now() - startTime
};
}
const payload = {
to,
from,
subject,
...(text && { text }),
...(html && { html }),
};
if (scheduledAt) {
payload.scheduled_at =
scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt;
}
this._log('debug', 'Sending API request', {
requestId,
endpoint: `${this.baseUrl}/v1/send-email`,
payload: { ...payload, text: text ? '[REDACTED]' : undefined, html: html ? '[REDACTED]' : undefined }
});
try {
const response = await fetch(`${this.baseUrl}/v1/send-email`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
"X-Request-ID": requestId,
},
body: JSON.stringify(payload),
});
const result = await response.json();
const duration = Date.now() - startTime;
this._log('debug', `API response received`, {
requestId,
statusCode: response.status,
duration: `${duration}ms`,
success: response.ok
});
if (!response.ok) {
const errorType = this._categorizeError(response.status);
const errorMessage = result.error || `HTTP error! status: ${response.status}`;
const suggestion = this._getErrorSuggestion(response.status);
this._log('error', `API request failed`, {
requestId,
error: errorMessage,
statusCode: response.status,
errorType,
suggestion
});
return {
success: false,
error: errorMessage,
errorType,
suggestion,
statusCode: response.status,
requestId,
timestamp,
duration,
endpoint: `${this.baseUrl}/v1/send-email`
};
}
this._log('info', `Email ${scheduledAt ? 'scheduled' : 'sent'} successfully`, {
requestId,
duration: `${duration}ms`,
emailId: result.id
});
return {
success: true,
data: result,
message: scheduledAt
? "Email scheduled successfully"
: "Email sent successfully",
requestId,
timestamp,
duration
};
} catch (error) {
const duration = Date.now() - startTime;
const errorType = error.name === 'TypeError' && error.message.includes('fetch')
? 'NETWORK_ERROR'
: 'UNKNOWN_ERROR';
this._log('error', `Request failed with exception`, {
requestId,
error: error.message,
errorType,
duration: `${duration}ms`
});
return {
success: false,
error: `Failed to send email: ${error.message}`,
errorType,
suggestion: errorType === 'NETWORK_ERROR'
? 'Check your internet connection and try again'
: 'Please try again or contact support if the issue persists',
statusCode: null,
requestId,
timestamp,
duration,
endpoint: `${this.baseUrl}/v1/send-email`
};
}
}
_categorizeError(statusCode) {
if (statusCode >= 400 && statusCode < 500) {
return 'CLIENT_ERROR';
} else if (statusCode >= 500) {
return 'SERVER_ERROR';
} else if (statusCode === 429) {
return 'RATE_LIMIT_ERROR';
}
return 'UNKNOWN_ERROR';
}
_getErrorSuggestion(statusCode) {
switch (statusCode) {
case 400:
return 'Check your request parameters and try again';
case 401:
return 'Verify your API key is correct and has proper permissions';
case 403:
return 'Your API key may not have permission for this operation';
case 404:
return 'The API endpoint was not found. Check the base URL';
case 429:
return 'You are being rate limited. Wait a moment and try again';
case 500:
return 'Server error occurred. Try again in a few moments';
case 503:
return 'Service temporarily unavailable. Please try again later';
default:
return 'Please try again or contact support if the issue persists';
}
}
}
export default Mailblock;
export { EmailBuilder };