3
\$\begingroup\$

I was looking for a way to better define email recipients along the lines of the To/CC/BCC convention. Although SYSTEM/STANDARD/EMAIL contains CC and BCC fields, they are not currently functional, see:

"How to Add Addresses to the BCC Field Using Rebol's Send Function"

Therefore in this solution, I've reorganised the SEND command to do my bidding based in part on this handy little answer:

"Sending BCC emails using an SMTP Server" (by @eml)

The SEND Function

The key is collecting ALL recipient addresses from To/CC/BCC (and the ADDRESS function argument) for use with the SMTP RCPT command, then reformatting the To/CC fields and setting BCC to none before composing the header object. Preliminary tests seem to indicate this approach works.

(Also included here is the function uses SYSTEM/STANDARD/EMAIL as a base for the header object, so you only need pass a block/minimal object as an argument—this should preserve the 'copy part of the message for the subject line' feature)

send: func [
    {Send a message to an address (or block of addresses)} 
    address [email! block!] "An address or block of addresses" 
    message "Text of message. First line is subject." 
    /only "Send only one message to multiple addresses" 
    /header "Supply your own custom header" 
    header-obj [block! object!] "The header to use" 
    /attach "Attach file, files, or [.. [filename data]]" 
    files [file! block!] "The files to attach to the message" 
    /subject "Set the subject of the message" 
    subj "The subject line" 
    /show "Show all recipients in the TO field" 
    /local smtp-port boundary make-boundary tmp from
][
    make-boundary: does []

    if file? files [files: reduce [files]] 

    message: either string? message [copy message] [mold message]

    ; bases any custom object on SYSTEM/STANDARD/EMAIL
    header-obj: make system/standard/email any [
        header-obj
        [subject: any [subj copy/part message any [find message newline 50]]]
    ]

    if subject [header-obj/subject: subj]

    either none? from: any [
        header-obj/from
        header-obj/from: system/user/email
    ][
        net-error "Email header not set: no from address"
    ][
        if all [
            string? system/user/name
            not empty? system/user/name
        ][
            header-obj/from: rejoin [system/user/name " <" from ">"]
        ]
    ] 

    address: collect [
        foreach recipient compose [(address)][
            if email? recipient [keep recipient]
        ]
    ]

    if find [email! block!] type?/word header-obj/to [
        append address header-obj/to: collect [
            foreach recipient compose [(header-obj/to)][
                if email? recipient [keep recipient]
            ]
        ]
    ]

    header-obj/to: unless empty? address [
        rejoin remove collect [
            foreach recipient address: unique address [
                keep ", "
                keep recipient
            ]
        ]
    ]

    header-obj/cc: if find [email! block!] type?/word header-obj/cc [
        append address header-obj/cc: unique collect [
            foreach recipient compose [(header-obj/cc)][
                if email? recipient [keep recipient]
            ]
        ]

        unless empty? header-obj/cc [
            rejoin remove collect [
                foreach recipient unique header-obj/cc [
                    keep ", "
                    keep recipient
                ]
            ]
        ]
    ]

    ; notes on BCC here: https://stackoverflow.com/a/26611044/292969

    if find [email! block!] type?/word header-obj/bcc [
        append address collect [
            foreach recipient compose [(header-obj/bcc)][
                if email? recipient [keep recipient]
            ]
        ]
    ]

    header-obj/bcc: none

    if empty? address: unique address [
        net-error "No recipients specified."
    ]

    if none? header-obj/date [
        header-obj/date: to-idate now
    ]

    if attach [
        boundary: rejoin ["--__REBOL--" system/product "--" system/version "--" checksum form now/precise "__"]
        header-obj/MIME-Version: "1.0"
        header-obj/content-type: join "multipart/mixed; boundary=" [{"} skip boundary 2 {"}]
        message: build-attach-body message files boundary
    ]

    smtp-port: open [scheme: 'esmtp]

    ; The use of BCC makes it so SHOW is somewhat redundant. Possibly ONLY too.

    ; either only [
        ; address: copy address
        ; remove-each value address [not email? :value]
    message: head insert insert tail net-utils/export header-obj newline message
    insert smtp-port reduce [from address message]
        ; probe reduce [from address message]

    ; ][
    ;   foreach addr address [
    ;       if email? addr [
    ;           if not show [insert clear header-obj/to addr]
    ;           tmp: head insert insert tail net-utils/export header-obj newline message
    ;           insert smtp-port reduce [from reduce [addr] tmp]
    ;       ]
    ;   ]
    ; ]

    close smtp-port
]

Testing

Testing requires credentials to access an ESMTP server per the first lines below:

; originator credentials
system/user/name: "Foo Bert"
system/user/email: foo@bar.com
system/schemes/default/host: "mail.server"
system/schemes/esmtp/user: "mail.server.user"
system/schemes/esmtp/pass: "mail.server.pass"
system/standard/email/content-type: "text/plain"

; trace/net on

send/header bar@foo.com "Message Content" [
    To: bar@foo.com ; redundant, but here for completeness
    CC: baz@foo.com
    BCC: baz@bar.com
    Subject: "A Test Email"
]

Replace addresses as necessary!

Review

I'm looking to ascertain if:

  • The function works as advertised
  • Whether this is the right approach (seems to be a more efficient use of an SMTP instance)
  • I am overlooking any other design considerations
  • As a bonus, this would make the /ONLY and /SHOW refinements redundant
\$\endgroup\$
4
\$\begingroup\$

As a sort of coarse suggestion I would say that you're over-refining and not dialecting enough. Imagine if you said it was always arity-2...header and body. But if the header is just an email assume it's the To address.

So these would be equivalent:

send bar@foo.com "Message content"

send [To: bar@foo.com] "Message content"

/attach makes sense as a refinement that might add a third section, because when I think of parameterization that does seem appropriate and it's also "in the right position".

send/attach bar@foo.com "See attached files" [%world-takeover.pdf %kittens.gif]

I would suggest though that the other refinements are too much, and should be perhaps slipstreamed into the header or body as <tag> instructions...something out-of-band.

This feels a bit like a "port scheme" for email when you frame it this way.

\$\endgroup\$
  • \$\begingroup\$ I have a dialect in mind, however this was just a case of cleaning up the original function. \$\endgroup\$ – rgchris Nov 21 '15 at 18:49
  • \$\begingroup\$ I'd previously put thought of how to approach SEND in this question. \$\endgroup\$ – rgchris Nov 21 '15 at 21:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.