-
Notifications
You must be signed in to change notification settings - Fork 84
/
Copy pathsmtp_send.R
301 lines (274 loc) · 10.7 KB
/
smtp_send.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#' Send an email message through SMTP
#'
#' Send an email message to one or more recipients via an SMTP server. The email
#' message required as input to `smtp_send()` has to be created by using the
#' [compose_email()] function. The `email_message` object can be previewed by
#' printing the object, where the HTML preview will show how the message should
#' appear in recipients' email clients. We can avoid re-entering SMTP
#' configuration and credentials information by retrieving this information
#' either from disk (with the file generated by use of the
#' [create_smtp_creds_file()] function), or, from the system's key-value store
#' (with the key set by the [create_smtp_creds_key()] function).
#'
#' To send messages, we need access to the **mailsend-go** binary, which is
#' cross-platform and works on Windows, macOS (via Homebrew), and Linux (Debian
#' and RPM packages). Instructions for installation can be found at
#' [https://github.com/muquit/mailsend-go#downloading-and-installing].
#'
#' @param email The email message object, as created by the [compose_email()]
#' function. The object's class is `email_message`.
#' @param to A vector of email addresses serving as primary recipients for the
#' message. For secondary recipients, use the `cc` and `bcc` arguments.
#' @param from The email address of the sender. Often this needs to be the same
#' email address that is associated with the account actually sending the
#' message.
#' @param subject The subject of the message, which is usually a brief summary
#' of the topic of the message.
#' @param cc A vector of email addresses for sending the message as a carbon
#' copy. This list of for those who are to receive a copy of a message
#' addressed primarily to another. The list of recipients in the CC list is
#' visible to all other recipients of the message.
#' @param bcc A vector of email addresses for sending the message as blind
#' carbon copies. Any email addresses provided here will receive the message
#' and these email addresses will be concealed from other recipients
#' (including others on the BCC list).
#' @param credentials One of three credential helper functions must be used
#' here: (1) [creds()], (2) [creds_key()], or (3) [creds_file()]. The first,
#' [creds()], allows for a manual specification of SMTP configuration and
#' credentials within that helper function. This is the most secure method for
#' supplying credentials as they aren't written to disk. The [creds_key()]
#' function is used if credentials are stored in the system-wide key-value
#' store, through use of the [create_smtp_creds_key()] function. The
#' [creds_file()] helper function relies on a credentials file stored on disk.
#' Such a file is created using the [create_smtp_creds_file()] function.
#' @param binary_loc An option to supply the location of the `mailsend-go`
#' binary file should it not be on the system path or in the working
#' directory.
#' @param echo If set to `TRUE`, the command for sending the message via
#' `mailsend-go` will be printed to the console. By default, this is `FALSE`.
#' @param dry_run Setting `dry_run` to `TRUE` will return information on the
#' SMTP sending options. Furthermore, the function will stop short of actually
#' sending the email message out. By default, however, this is set to `FALSE`.
#' @param creds_file An option to specify a credentials file. As this argument
#' is deprecated, please consider using `credentials = creds_file(<file>)`
#' instead.
#' @examples
#' \dontrun{
#' # Before sending out an email through
#' # SMTP, we need an `email_message`
#' # object; for the purpose of a simple
#' # example, we can use the function
#' # `prepare_test_message()` to create
#' # a test version of an email (although
#' # we'd normally use `compose_email()`)
#' email <- prepare_test_message()
#'
#' # The `email` message can be sent
#' # through the `smtp_send()` function
#' # so long as we supply the appropriate
#' # credentials; The following three
#' # examples provide scenarios for both
#' # the creation of credentials and their
#' # retrieval within the `credentials`
#' # argument of `smtp_send()`
#'
#' # (1) Providing the credentials info
#' # directly via the `creds()` helper
#' # (the most secure means of supplying
#' # credentials information)
#'
#' email %>%
#' smtp_send(
#' from = "[email protected]",
#' to = "[email protected]",
#' credentials = creds(
#' provider = "gmail",
#' user = "[email protected]")
#' )
#'
#' # (2) Using a credentials key (with
#' # the `create_smtp_creds_key()` and
#' # `creds_key()` functions)
#'
#' create_smtp_creds_key(
#' id = "gmail",
#' user = "[email protected]",
#' provider = "gmail"
#' )
#'
#' email %>%
#' smtp_send(
#' from = "[email protected]",
#' to = "[email protected]",
#' credentials = creds_key(
#' "gmail"
#' )
#' )
#'
#' # (3) Using a credentials file (with
#' # the `create_smtp_creds_file()` and
#' # `creds_file()` functions)
#'
#' create_smtp_creds_file(
#' file = "gmail_secret",
#' user = "[email protected]",
#' provider = "gmail"
#' )
#'
#' email %>%
#' smtp_send(
#' from = "[email protected]",
#' to = "[email protected]",
#' credentials = creds_file(
#' "gmail_secret")
#' )
#' }
#' @export
smtp_send <- function(email,
to,
from,
subject = NULL,
cc = NULL,
bcc = NULL,
credentials = NULL,
binary_loc = NULL,
echo = FALSE,
dry_run = FALSE,
creds_file = "deprecated") {
# Verify that the `message` object
# is of the class `email_message`
if (!inherits(email, "email_message")) {
stop("The object provided in `email` must be an ",
"`email_message` object.\n",
" * This can be created with the `compose_email()` function.",
call. = FALSE)
}
# Establish the location of the `mailsend-go` binary
if (is.null(binary_loc)) {
binary_loc <- find_binary("mailsend-go")
if (is.null(binary_loc)) {
stop("The binary file `mailsend-go` is not in the system path or \n",
"in the working directory:\n",
" * install `mailsend-go` using the instructions at ",
"https://github.com/muquit/mailsend-go#downloading-and-installing",
call. = FALSE)
}
}
# If the user provides a path to a creds file in the `creds_file`
# argument, upgrade that through the `creds_file()` helper function
# and provide a warning about soft deprecation
if (!missing(creds_file)) {
credentials <- creds_file(creds_file)
warning("The `creds_file` argument is deprecated:\n",
" * please consider using `credentials = creds_file(\"", creds_file,
"\")` instead")
}
# If nothing is provided in `credentials`, stop the function
# and include a message about which credential helpers could
# be used
if (is.null(credentials)) {
stop("SMTP credentials must be supplied to the `credentials` argument.\n",
"We can use either of these three helper functions for this:\n",
" * `creds_key()`: uses information stored in the system's key-value ",
"store (have a look at `?creds_key`)\n",
" * `creds_file()`: takes credentials stored in an on-disk file ",
"(use `?creds_file` for further info)\n",
" * `creds()`: allows for manual specification of SMTP credentials",
call. = FALSE)
}
# If whatever is provided to `credentials` does not have a
# `blastula_creds` class, determine whether that value is a
# single-length character vector (which is upgraded through
# the `creds_file()` function); if it's anything else, stop
# the function with a message
if (!inherits(credentials, "blastula_creds")) {
if (is.character(credentials) && length(credentials) == 1) {
credentials <- creds_file(file = credentials)
} else {
stop("The value for `credentials` must be a `blastula_creds` object\n",
"* see the article in `?creds` for information on this",
call. = FALSE)
}
}
# Create a temporary file with the `html` extension
tempfile_ <- tempfile(fileext = ".html")
# Reverse slashes on Windows filesystems
tempfile_ <-
tempfile_ %>%
tidy_gsub("\\\\", "/")
# Write the inlined HTML message out to a file
email$html_str %>% writeLines(con = tempfile_, useBytes = TRUE)
# Remove the file after the function exits
on.exit(file.remove(tempfile_))
# Normalize `subject` so that a `NULL` value becomes an empty string
subject <- subject %||% ""
# Create comma-separated addresses for
# `to`, `cc`, and `bcc`
to <- make_address_list(to)
cc <- make_address_list(cc)
bcc <- make_address_list(bcc)
# Set the `ssl` flag depending on the options provided
if (credentials$use_ssl) {
ssl_opt <- no_options()
} else {
ssl_opt <- no_arg()
}
# Set the `sender_name` to `no_arg()` if not provided
sender_name_opt <- credentials$sender_name %||% no_arg()
# Collect arguments and options for for `processx::run()`
# as a list
run_args <-
list(
`-sub` = subject,
`-smtp` = credentials$host,
`-port` = credentials$port %>% as.character(),
`-ssl` = ssl_opt,
`auth` = no_options(),
`-user` = credentials$user,
`-pass` = credentials$password,
`-fname` = sender_name_opt,
`-from` = from,
`-to` = to,
`-cc` = cc,
`-bcc` = bcc,
`attach` = no_options(),
`-file` = tempfile_,
`-mime-type` = "text/html",
`-inline` = no_options()
)
# Create the vector of arguments related to file attachments
attachment_args_vec <- create_attachment_args_vec(email = email)
# Clean up arguments and options; create the vector that's
# needed for `processx::run()`
run_args <-
run_args %>%
prune_args() %>%
create_args_opts_vec() %>%
append_attachment_args_vec(attachment_args_vec = attachment_args_vec)
if (echo) {
cmd_str <- run_args
cmd_str[which(run_args == "-pass")[1] + 1] <- "*****"
cmd_str <- paste(binary_loc, paste0(cmd_str, collapse = " "))
message(
"The command for sending the email message is:\n\n",
cmd_str, "\n"
)
}
if (dry_run) {
message("This was a dry run, the email message was NOT sent.")
return(invisible())
} else {
# Send out email via `processx::run()` and
# assign the result
send_result <-
processx::run(
command = binary_loc,
args = run_args
)
if (send_result$status == 0) {
message("The email message was sent successfully.\n")
} else {
message("The email message was NOT successfully sent.\n")
}
}
}