smtp: allow cc/bcc-only messages

This commit is contained in:
Zack Schuster 2020-05-26 22:47:24 -07:00
parent 2bb5832be4
commit 10a850159c
4 changed files with 100 additions and 14 deletions

View File

@ -189,19 +189,18 @@ const options = {
## new message.Message(headers)
```js
// headers is an object with the following recognized keys:
// headers is an object with the following recognized schema:
const headers = {
// required
from, // sender of the format (address or name <address> or "name" <address>)
to, // recipients (same format as above), multiple recipients are separated by a comma
// optional
text, // text of the email
cc, // carbon copied recipients (same format as above)
bcc, // blind carbon copied recipients (same format as above)
text, // text of the email
subject, // string subject of the email
attachment, // one attachment or array of attachments
};
// the `from` field is required.
// at least one `to`, `cc`, or `bcc` header is also required.
// you can also add whatever other headers you want.
```

View File

@ -42,7 +42,10 @@ export class Client {
* @param {function(err: Error, msg: Message): void} callback sss
* @returns {void}
*/
public send(msg: Message, callback: (err: Error, msg: Message) => void) {
public send(
msg: Message,
callback: (err: Error | null, msg: Message) => void
) {
const message: Message | null =
msg instanceof Message
? msg
@ -59,7 +62,7 @@ export class Client {
if (valid) {
const stack = {
message,
to: addressparser(message.header.to),
to: [] as ReturnType<typeof addressparser>,
from: addressparser(message.header.from)[0].address,
callback: (
callback ||
@ -69,7 +72,11 @@ export class Client {
).bind(this),
} as MessageStack;
if (message.header.cc) {
if (typeof message.header.to === 'string') {
stack.to = addressparser(message.header.to);
}
if (typeof message.header.cc === 'string') {
stack.to = stack.to.concat(
addressparser(message.header.cc).filter(
(x) => stack.to.some((y) => y.address === x.address) === false
@ -77,7 +84,7 @@ export class Client {
);
}
if (message.header.bcc) {
if (typeof message.header.bcc === 'string') {
stack.to = stack.to.concat(
addressparser(message.header.bcc).filter(
(x) => stack.to.some((y) => y.address === x.address) === false
@ -85,6 +92,10 @@ export class Client {
);
}
if (stack.to.length === 0) {
return callback(new Error('No recipients found in message'), msg);
}
if (
message.header['return-path'] &&
addressparser(message.header['return-path']).length

View File

@ -116,6 +116,17 @@ export class Message {
public readonly text?: string;
public alternative: AlternateMessageAttachment | null = null;
/**
* Construct an rfc2822-compliant message object.
*
* Special notes:
* - The `from` field is required.
* - At least one `to`, `cc`, or `bcc` header is also required.
* - You can also add whatever other headers you want.
*
* @see https://tools.ietf.org/html/rfc2822
* @param {Partial<MessageHeaders>} headers Message headers
*/
constructor(headers: Partial<MessageHeaders>) {
for (const header in headers) {
// allow user to override default content-type to override charset or send a single non-text message
@ -143,7 +154,6 @@ export class Message {
);
} else {
// allow any headers the user wants to set??
// if(/cc|bcc|to|from|reply-to|sender|subject|date|message-id/i.test(header))
this.header[header.toLowerCase()] = headers[header];
}
}
@ -196,12 +206,19 @@ export class Message {
* @returns {void}
*/
public valid(callback: (arg0: boolean, arg1?: string) => void) {
if (!this.header.from) {
callback(false, 'message does not have a valid sender');
if (typeof this.header.from !== 'string') {
callback(false, 'Message must have a `from` header');
}
if (!(this.header.to || this.header.cc || this.header.bcc)) {
callback(false, 'message does not have a valid recipient');
if (
typeof this.header.to !== 'string' &&
typeof this.header.cc !== 'string' &&
typeof this.header.bcc !== 'string'
) {
callback(
false,
'Message must have at least one `to`, `cc`, or `bcc` header'
);
} else if (this.attachments.length === 0) {
callback(true, undefined);
} else {

View File

@ -69,6 +69,65 @@ test.cb('authorize plain', (t) => {
);
});
test.cb(
'Client refuses to send message without `to`, `cc`, or `bcc` header',
(t) => {
const msg = {
subject: 'this is a test TEXT message from emailjs',
from: 'piglet@gmail.com',
text: "It is hard to be brave when you're only a Very Small Animal.",
};
client.send(new m.Message(msg), (err) => {
t.true(err instanceof Error);
t.is(
err?.message,
'Message must have at least one `to`, `cc`, or `bcc` header'
);
t.end();
});
}
);
test.cb('Client allows message with only `cc` header', (t) => {
const msg = {
subject: 'this is a test TEXT message from emailjs',
from: 'piglet@gmail.com',
cc: 'pooh@gmail.com',
text: "It is hard to be brave when you're only a Very Small Animal.",
};
send(
new m.Message(msg),
(mail) => {
t.is(mail.text, msg.text + '\n\n\n');
t.is(mail.subject, msg.subject);
t.is(mail.from?.text, msg.from);
t.is(mail.cc?.text, msg.cc);
},
t.end
);
});
test.cb('Client allows message with only `bcc` header', (t) => {
const msg = {
subject: 'this is a test TEXT message from emailjs',
from: 'piglet@gmail.com',
bcc: 'pooh@gmail.com',
text: "It is hard to be brave when you're only a Very Small Animal.",
};
send(
new m.Message(msg),
(mail) => {
t.is(mail.text, msg.text + '\n\n\n');
t.is(mail.subject, msg.subject);
t.is(mail.from?.text, msg.from);
t.is(mail.bcc?.text, undefined);
},
t.end
);
});
test('Client constructor throws if `password` supplied without `user`', (t) => {
t.notThrows(() => new c.Client({ user: 'anything', password: 'anything' }));
t.throws(() => new c.Client({ password: 'anything' }));