-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add body and attachment constructor * improve formatting * add tests * formatting * linting * rename `get_message` to `get_mime_message` * add dispatch to `get_mime_msg` only with msg * make it "attachments" (plural) * update README * mention and link rfc5322 * fix date in `get_body` to use locale current time * fix From field * fix From field * fix From field * fix Date field formatting * add comment about bcc * typos * discard extra test file
- Loading branch information
Showing
7 changed files
with
424 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,21 @@ | ||
name = "SMTPClient" | ||
uuid = "c35d69d1-b747-5018-a192-25bc5e63c83d" | ||
authors = ["Avik Sengupta <[email protected]>", "Iblis Lin <[email protected]>"] | ||
version = "0.6.0" | ||
version = "0.6.1" | ||
|
||
[deps] | ||
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" | ||
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" | ||
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" | ||
LibCURL = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" | ||
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" | ||
|
||
[extras] | ||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
|
||
[compat] | ||
julia = "1.3" | ||
LibCURL = "0.6" | ||
julia = "1.3" | ||
|
||
[extras] | ||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" | ||
|
||
[targets] | ||
test = ["Test"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
[![Pkg Eval](https://juliahub.com/docs/SMTPClient/pkgeval.svg)](https://juliahub.com/ui/Packages/SMTPClient/Bx8Fn/) | ||
[![Dependents](https://juliahub.com/docs/SMTPClient/deps.svg)](https://juliahub.com/ui/Packages/SMTPClient/Bx8Fn/?t=2) | ||
|
||
|
||
A [CURL](curl.haxx.se) based SMTP client with fairly low level API. | ||
It is useful for sending emails from within Julia code. | ||
Depends on [LibCURL.jl](https://github.com/JuliaWeb/LibCURL.jl/). | ||
|
@@ -51,7 +50,8 @@ resp = send(url, rcpt, from, body, opt) | |
``` | ||
|
||
### Example with HTML Formatting | ||
``` | ||
|
||
```julia | ||
body = "Subject: A simple test\r\n"* | ||
"Mime-Version: 1.0;\r\n"* | ||
"Content-Type: text/html;\r\n"* | ||
|
@@ -65,13 +65,64 @@ body = "Subject: A simple test\r\n"* | |
</html>\r\n""" | ||
``` | ||
|
||
### Function to construct the IOBuffer body and for adding attachments | ||
|
||
A new function `get_body()` is available to facilitate constructing the IOBuffer for the body of the message and for adding attachments. | ||
|
||
The function takes four required arguments: the `to` and `from` email addresses, a `subject` string, and a `msg` string. The `to` argument is a vector of strings, containing one or more email addresses. The `msg` string can be a regular string with the contents of the message or a string in MIME format, following the [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322) specifications. | ||
|
||
There are also the optional keyword arguments `cc`, `replyto` and `attachments`. The argument `cc` should be a vector of strings, containing one or more email addresses, while `replyto` is a string expected to contain a single argument, just like `from`. The `attachments` argument should be a list of filenames to be attached to the message. | ||
|
||
The attachments are encoded using `Base64.base64encode` and included in the IOBuffer variable returned by the function. The function `get_body()` takes care of identifying which type of attachments are to be included (from the filename extensions) and to properly add them according to the MIME specifications. | ||
|
||
In case an attachment is to be added, the `msg` argument must be formatted according to the MIME specifications. In order to help with that, another function, `get_mime_msg(message)`, is provided, which takes the provided message and returns the message with the proper MIME specifications. By default, it assumes plain text with UTF-8 encoding, but plain text with different encodings or HTML text can also be given can be given (see [src/user.jl#L36](src/user.jl#L35) for the arguments). | ||
|
||
As for blind carbon copy (Bcc), it is implicitly handled by `send()`. Every recipient in `send()` which is not included in `body` is treated as a Bcc. | ||
|
||
Here are two examples: | ||
|
||
```julia | ||
using SMTPClient | ||
opt = SendOptions( | ||
isSSL = true, | ||
username = "[email protected]", | ||
passwd = "yourgmailpassword" | ||
) | ||
url = "smtps://smtp.gmail.com:465" | ||
message = "Don't forget to check out SMTPClient.jl" | ||
subject = "SMPTClient.jl" | ||
to = ["<[email protected]>"] | ||
cc = ["<[email protected]>"] | ||
bcc = ["<[email protected]>"] | ||
from = "You <[email protected]>" | ||
replyto = "<[email protected]>" | ||
body = get_body(to, from, subject, message; cc, replyto) | ||
rcpt = vcat(to, cc, bcc) | ||
resp = send(url, rcpt, from, body, opt) | ||
``` | ||
|
||
```julia | ||
message = "Check out this cool logo!" | ||
subject = "Julia logo" | ||
attachments = ["julia_logo_color.svg"] | ||
mime_msg = get_mime_msg(message) | ||
body = get_body(to, from, subject, mime_msg; attachments) | ||
``` | ||
|
||
### Gmail Notes | ||
|
||
Due to the security policy of Gmail, | ||
you need to "allow less secure apps into your account": | ||
|
||
- https://myaccount.google.com/lesssecureapps | ||
- <https://myaccount.google.com/lesssecureapps> | ||
|
||
The URL for gmail can be either `smtps://smtp.gmail.com:465` or `smtp://smtp.gmail.com:587`. | ||
(Note the extra `s` in the former.) | ||
|
@@ -98,12 +149,12 @@ send(url, to-addresses, from-address, message-body, options) | |
``` | ||
|
||
Send an email. | ||
* `url` should be of the form `smtp://server:port` or `smtps://...`. | ||
* `to-address` is a vector of `String`. | ||
* `from-address` is a `String`. All addresses must be enclosed in angle brackets. | ||
* `message-body` must be a RFC5322 formatted message body provided via an `IO`. | ||
* `options` is an object of type `SendOptions`. It contains authentication information, as well as the option of whether the server requires TLS. | ||
|
||
* `url` should be of the form `smtp://server:port` or `smtps://...`. | ||
* `to-address` is a vector of `String`. | ||
* `from-address` is a `String`. All addresses must be enclosed in angle brackets. | ||
* `message-body` must be a RFC5322 formatted message body provided via an `IO`. | ||
* `options` is an object of type `SendOptions`. It contains authentication information, as well as the option of whether the server requires TLS. | ||
|
||
```julia | ||
SendOptions(; isSSL = false, verbose = false, username = "", passwd = "") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
mime_types = Dict( | ||
[ | ||
"abs" => "audio/x-mpeg" | ||
"ai" => "application/postscript" | ||
"aif" => "audio/x-aiff" | ||
"aifc" => "audio/x-aiff" | ||
"aiff" => "audio/x-aiff" | ||
"aim" => "application/x-aim" | ||
"art" => "image/x-jg" | ||
"asf" => "video/x-ms-asf" | ||
"asx" => "video/x-ms-asf" | ||
"au" => "audio/basic" | ||
"avi" => "video/x-msvideo" | ||
"avx" => "video/x-rad-screenplay" | ||
"bcpio" => "application/x-bcpio" | ||
"bin" => "application/octet-stream" | ||
"bmp" => "image/bmp" | ||
"body" => "text/html" | ||
"cdf" => "application/x-cdf" | ||
"cer" => "application/x-x509-ca-cert" | ||
"class" => "application/java" | ||
"cpio" => "application/x-cpio" | ||
"csh" => "application/x-csh" | ||
"css" => "text/css" | ||
"dib" => "image/bmp" | ||
"doc" => "application/msword" | ||
"dtd" => "text/plain" | ||
"dv" => "video/x-dv" | ||
"dvi" => "application/x-dvi" | ||
"eps" => "application/postscript" | ||
"etx" => "text/x-setext" | ||
"exe" => "application/octet-stream" | ||
"gif" => "image/gif" | ||
"gtar" => "application/x-gtar" | ||
"gz" => "application/x-gzip" | ||
"hdf" => "application/x-hdf" | ||
"hqx" => "application/mac-binhex40" | ||
"htc" => "text/x-component" | ||
"htm" => "text/html" | ||
"html" => "text/html" | ||
"ief" => "image/ief" | ||
"jad" => "text/vnd.sun.j2me.app-descriptor" | ||
"jar" => "application/octet-stream" | ||
"java" => "text/plain" | ||
"jnlp" => "application/x-java-jnlp-file" | ||
"jpe" => "image/jpeg" | ||
"jpeg" => "image/jpeg" | ||
"jpg" => "image/jpeg" | ||
"js" => "text/javascript" | ||
"kar" => "audio/x-midi" | ||
"latex" => "application/x-latex" | ||
"m3u" => "audio/x-mpegurl" | ||
"mac" => "image/x-macpaint" | ||
"man" => "application/x-troff-man" | ||
"me" => "application/x-troff-me" | ||
"mid" => "audio/x-midi" | ||
"midi" => "audio/x-midi" | ||
"mif" => "application/x-mif" | ||
"mov" => "video/quicktime" | ||
"movie" => "video/x-sgi-movie" | ||
"mp1" => "audio/x-mpeg" | ||
"mp2" => "audio/x-mpeg" | ||
"mp3" => "audio/x-mpeg" | ||
"mpa" => "audio/x-mpeg" | ||
"mpe" => "video/mpeg" | ||
"mpeg" => "video/mpeg" | ||
"mpega" => "audio/x-mpeg" | ||
"mpg" => "video/mpeg" | ||
"mpv2" => "video/mpeg2" | ||
"ms" => "application/x-wais-source" | ||
"nc" => "application/x-netcdf" | ||
"oda" => "application/oda" | ||
"pbm" => "image/x-portable-bitmap" | ||
"pct" => "image/pict" | ||
"pdf" => "application/pdf" | ||
"pgm" => "image/x-portable-graymap" | ||
"pic" => "image/pict" | ||
"pict" => "image/pict" | ||
"pls" => "audio/x-scpls" | ||
"png" => "image/png" | ||
"pnm" => "image/x-portable-anymap" | ||
"pnt" => "image/x-macpaint" | ||
"ppm" => "image/x-portable-pixmap" | ||
"ps" => "application/postscript" | ||
"psd" => "image/x-photoshop" | ||
"qt" => "video/quicktime" | ||
"qti" => "image/x-quicktime" | ||
"qtif" => "image/x-quicktime" | ||
"ras" => "image/x-cmu-raster" | ||
"rgb" => "image/x-rgb" | ||
"rm" => "application/vnd.rn-realmedia" | ||
"roff" => "application/x-troff" | ||
"rtf" => "application/rtf" | ||
"rtx" => "text/richtext" | ||
"sh" => "application/x-sh" | ||
"shar" => "application/x-shar" | ||
"smf" => "audio/x-midi" | ||
"snd" => "audio/basic" | ||
"src" => "application/x-wais-source" | ||
"sv4cpio" => "application/x-sv4cpio" | ||
"sv4crc" => "application/x-sv4crc" | ||
"swf" => "application/x-shockwave-flash" | ||
"t" => "application/x-troff" | ||
"tar" => "application/x-tar" | ||
"tcl" => "application/x-tcl" | ||
"tex" => "application/x-tex" | ||
"texi" => "application/x-texinfo" | ||
"texinfo" => "application/x-texinfo" | ||
"tif" => "image/tiff" | ||
"tiff" => "image/tiff" | ||
"tr" => "application/x-troff" | ||
"tsv" => "text/tab-separated-values" | ||
"txt" => "text/plain" | ||
"ulw" => "audio/basic" | ||
"ustar" => "application/x-ustar" | ||
"xbm" => "image/x-xbitmap" | ||
"xpm" => "image/x-xpixmap" | ||
"xwd" => "image/x-xwindowdump" | ||
"wav" => "audio/x-wav" | ||
"wbmp" => "image/vnd.wap.wbmp" | ||
"wml" => "text/vnd.wap.wml" | ||
"wmlc" => "application/vnd.wap.wmlc" | ||
"wmls" => "text/vnd.wap.wmlscript" | ||
"wmlscriptc" => "application/vnd.wap.wmlscriptc" | ||
"wrl" => "x-world/x-vrml" | ||
"Z" => "application/x-compress" | ||
"z" => "application/x-compress" | ||
"zip" => "application/zip" | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
function encode_attachment(filename::String, boundary::String) | ||
io = IOBuffer() | ||
iob64_encode = Base64EncodePipe(io) | ||
open(filename, "r") do f | ||
write(iob64_encode, f) | ||
end | ||
close(iob64_encode) | ||
|
||
filename_ext = split(filename, '.')[end] | ||
|
||
if haskey(mime_types, filename_ext) | ||
content_type = mime_types[filename_ext] | ||
else | ||
content_type = "application/octet-stream" | ||
end | ||
|
||
if haskey(mime_types, filename_ext) && startswith(mime_types[filename_ext], "image") | ||
content_disposition = "inline" | ||
else | ||
content_disposition = "attachment" | ||
end | ||
|
||
encoded_str = | ||
"--$boundary\r\n" * | ||
"Content-Disposition: $content_disposition;\r\n" * | ||
" filename=$(basename(filename))\r\n" * | ||
"Content-Type: $content_type;\r\n" * | ||
" name=\"$(basename(filename))\"\r\n" * | ||
"Content-Transfer-Encoding: base64\r\n" * | ||
"$(String(take!(io)))\r\n" * | ||
"--$boundary\r\n" | ||
return encoded_str | ||
end | ||
|
||
# See https://www.w3.org/Protocols/rfc1341/7_1_Text.html about charset | ||
function get_mime_msg(message::String, ::Val{:plain}, charset::String = "UTF-8") | ||
msg = | ||
"Content-Type: text/plain; charset=\"$charset\"" * | ||
"Content-Transfer-Encoding: quoted-printable\r\n\r\n" * | ||
"$message\r\n" | ||
return msg | ||
end | ||
|
||
get_mime_msg(message::String, ::Val{:utf8}) = | ||
get_mime_msg(message, Val(:plain), "UTF-8") | ||
|
||
get_mime_msg(message::String, ::Val{:usascii}) = | ||
get_mime_msg(message, Val(:plain), "US-ASCII") | ||
|
||
get_mime_msg(message::String) = get_mime_msg(message, Val(:utf8)) | ||
|
||
function get_mime_msg(message::String, ::Val{:html}) | ||
msg = | ||
"Content-Type: text/html;\r\n" * | ||
"Content-Transfer-Encoding: 7bit;\r\n\r\n" * | ||
"\r\n" * | ||
message * | ||
"\r\n" | ||
return msg | ||
end | ||
|
||
#Provide the message body as RFC5322 within an IO | ||
|
||
function get_body( | ||
to::Vector{String}, | ||
from::String, | ||
subject::String, | ||
msg::String; | ||
cc::Vector{String} = String[], | ||
replyto::String = "", | ||
attachments::Vector{String} = String[] | ||
) | ||
|
||
boundary = "Julia_SMTPClient-" * join(rand(collect(vcat('0':'9','A':'Z','a':'z')), 40)) | ||
|
||
tz = mapreduce( | ||
x -> string(x, pad=2), *, | ||
divrem( div( ( now() - now(Dates.UTC) ).value, 60000 ), 60 ) | ||
) | ||
date = join([Dates.format(now(), "e, d u yyyy HH:MM:SS", locale="english"), tz], " ") | ||
|
||
contents = | ||
"From: $from\r\n" * | ||
"Date: $date\r\n" * | ||
"Subject: $subject\r\n" * | ||
ifelse(length(cc) > 0, "Cc: $(join(cc, ", "))\r\n", "") * | ||
ifelse(length(replyto) > 0, "Reply-To: $replyto\r\n", "") * | ||
"To: $(join(to, ", "))\r\n" | ||
|
||
if length(attachments) == 0 | ||
contents *= | ||
"MIME-Version: 1.0\r\n" * | ||
"$msg\r\n\r\n" | ||
else | ||
contents *= | ||
"Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n\r\n" * | ||
"MIME-Version: 1.0\r\n" * | ||
"\r\n" * | ||
"This is a message with multiple parts in MIME format.\r\n" * | ||
"--$boundary\r\n" * | ||
"$msg\r\n" * | ||
"--$boundary\r\n" * | ||
"\r\n" * | ||
join(encode_attachment.(attachments, boundary), "\r\n") | ||
end | ||
body = IOBuffer(contents) | ||
return body | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using Test | ||
import Base64: base64decode | ||
using SMTPClient | ||
|
||
|
||
|
Oops, something went wrong.
0a9dec6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JuliaRegistrator register
0a9dec6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registration pull request created: JuliaRegistries/General/48548
After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.
This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via: