diff --git a/lib/api/submit.js b/lib/api/submit.js index 86561551..47af61c9 100644 --- a/lib/api/submit.js +++ b/lib/api/submit.js @@ -12,8 +12,11 @@ const tools = require('../tools'); const Maildropper = require('../maildropper'); const roles = require('../roles'); const Transform = require('stream').Transform; -const { sessSchema, sessIPSchema, booleanSchema } = require('../schemas'); +const { sessSchema, sessIPSchema, booleanSchema, metaDataSchema } = require('../schemas'); const { preprocessAttachments } = require('../data-url'); +const { userId, mailboxId } = require('../schemas/request/general-schemas'); +const { AddressOptionalName, AddressOptionalNameArray, Header, Attachment, ReferenceWithAttachments } = require('../schemas/request/messages-schemas'); +const { successRes } = require('../schemas/response/general-schemas'); class StreamCollect extends Transform { constructor() { @@ -620,113 +623,111 @@ module.exports = (db, server, messageHandler, userHandler, settingsHandler) => { const submitMessageWrapper = util.promisify(submitMessage); server.post( - { name: 'send', path: '/users/:user/submit' }, + { + name: 'send', + path: '/users/:user/submit', + tags: ['Submission'], + summary: 'Submit a Message for Delivery', + description: 'Use this method to send emails from a user account', + validationObjs: { + requestBody: { + mailbox: mailboxId, + from: AddressOptionalName.description('Addres for the From: header'), + replyTo: AddressOptionalName.description('Address for the Reply-To: header'), + to: Joi.array() + .items( + Joi.object({ + name: Joi.string().empty('').max(255).description('Name of the sender'), + address: Joi.string().email({ tlds: false }).failover('').required().description('Address of the sender') + }).$_setFlag('objectName', 'AddressOptionalName') + ) + .description('Addresses for the To: header'), + + cc: AddressOptionalNameArray.description('Addresses for the Cc: header'), + + bcc: AddressOptionalNameArray.description('Addresses for the Bcc: header'), + + headers: Joi.array() + .items(Header) + .description( + 'Custom headers for the message. If reference message is set then In-Reply-To and References headers are set automatically' + ), + subject: Joi.string() + .empty('') + .max(2 * 1024) + .description('Message subject. If not then resolved from Reference message'), + text: Joi.string() + .empty('') + .max(1024 * 1024) + .description('Plaintext message'), + html: Joi.string() + .empty('') + .max(1024 * 1024) + .description('HTML formatted message'), + attachments: Joi.array().items(Attachment).description('Attachments for the message'), + + meta: metaDataSchema.label('metaData').description('Optional metadata, must be an object or JSON formatted string'), + sess: sessSchema, + ip: sessIPSchema, + reference: ReferenceWithAttachments.description( + 'Optional referenced email. If uploaded message is a reply draft and relevant fields are not provided then these are resolved from the message to be replied to' + ), + // if true then treat this message as a draft + isDraft: booleanSchema.default(false).description('Is the message a draft or not'), + // if set then this message is based on a draft that should be deleted after processing + draft: Joi.object() + .keys({ + mailbox: mailboxId, + id: Joi.number().required().description('Message ID') + }) + .description('Draft message to base this one on'), + sendTime: Joi.date().description('Send time'), + uploadOnly: booleanSchema.default(false).description('If true only uploads the message but does not send it'), + envelope: Joi.object() + .keys({ + from: AddressOptionalName.description('Addres for the From: header'), + to: Joi.array().items( + Joi.object() + .keys({ + name: Joi.string().empty('').max(255).description('Name of the sender'), + address: Joi.string().email({ tlds: false }).required().description('Address of the sender') + }) + .description('Addresses for the To: header') + ) + }) + .description('Optional envelope') + }, + queryParams: {}, + pathParams: { + user: userId + }, + response: { + 200: { + description: 'Success', + model: Joi.object({ + success: successRes, + message: Joi.object({ + mailbox: Joi.string().required().description('Mailbox ID the message was stored to'), + id: Joi.number().description('Message ID in the Mailbox').required(), + queueId: Joi.string().required().description('Queue ID in MTA') + }) + .required() + .description('Information about submitted Message') + .$_setFlag('objectName', 'MessageWithQueueId') + }) + } + } + } + }, tools.responseWrapper(async (req, res) => { res.charSet('utf-8'); - const schema = Joi.object().keys({ - user: Joi.string().hex().lowercase().length(24).required(), - - mailbox: Joi.string().hex().lowercase().length(24), - - reference: Joi.object().keys({ - mailbox: Joi.string().hex().lowercase().length(24).required(), - id: Joi.number().required(), - action: Joi.string().valid('reply', 'replyAll', 'forward').required() - }), - - // if true then treat this message as a draft - isDraft: booleanSchema.default(false), - - // if set then this message is based on a draft that should be deleted after processing - draft: Joi.object().keys({ - mailbox: Joi.string().hex().lowercase().length(24).required(), - id: Joi.number().required() - }), - - uploadOnly: booleanSchema.default(false), + const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs; - sendTime: Joi.date(), - - envelope: Joi.object().keys({ - from: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - to: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ) - }), - - from: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - - replyTo: Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }), - - to: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - cc: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - bcc: Joi.array().items( - Joi.object().keys({ - name: Joi.string().empty('').max(255), - address: Joi.string().email({ tlds: false }).required() - }) - ), - - headers: Joi.array().items( - Joi.object().keys({ - key: Joi.string().empty('').max(255), - value: Joi.string() - .empty('') - .max(100 * 1024) - }) - ), - - subject: Joi.string() - .empty('') - .max(2 * 1024), - text: Joi.string() - .empty('') - .max(1024 * 1024), - html: Joi.string() - .empty('') - .max(1024 * 1024), - - attachments: Joi.array().items( - Joi.object().keys({ - filename: Joi.string().empty('').max(255), - - contentType: Joi.string().empty('').max(255), - contentTransferEncoding: Joi.string().empty('').trim().lowercase(), - contentDisposition: Joi.string().empty('').trim().lowercase().valid('inline', 'attachment'), - cid: Joi.string().empty('').max(255), - - encoding: Joi.string().empty('').default('base64'), - content: Joi.string().required() - }) - ), - meta: Joi.object().unknown(true), - sess: sessSchema, - ip: sessIPSchema + const schema = Joi.object({ + ...pathParams, + ...requestBody, + ...queryParams }); // extract embedded attachments from HTML