Erlang SMTP client WITH attachments - smtp

I'm looking for one of two things:
An Erlang library that supports sending of emails with attachments
An example of using gen_smtp to send an email with an attachment, since I have already successfully sent emails with this library
It seems there is very little in the way of SMTP client support with attachments out there, although there are plenty of modules that can send plain text emails via SMTP. Anyone have suggestions?
Also, using Windows - I'm not sure the sendmail route is available?
Agus, when I try that exact code, as well as similar code, I keep getting the following error, any ideas?:
** exception error: {badmap,[]}
in function maps:get/3
called as maps:get(content_type_params,[],[])
in call from mimemail:ensure_content_headers/7 (c:/temp/erlang/myapp/_build/default/lib/gen_smtp/src/mimemail.erl, line 667)
in call from mimemail:encode/2 (c:/temp/erlang/myapp/_build/default/lib/gen_smtp/src/mimemail.erl, line 161)
in call from email_test:send_email_with_attachment/0 (c:/temp/erlang/myapp/src/email_test.erl, line 14)
The version of gen_smtp I'm using in rebar.config:
{gen_smtp, ".*", {git, "git://github.com/gen-smtp/gen_smtp.git", {branch, master}}}

Short answer: you can use gen_smtp to send an email with attachment.
If you have used gen_smtp_client:send(Email, Options) or gen_smtp_client:send_blocking(Email, Options), then you can actually generate the Body's Email variable using mimemail:encode/2.
%% #doc Encode a MIME tuple to a binary.
encode({Type, Subtype, Headers, ContentTypeParams, Parts}, Options) ->
...
Below code shows how to send message with email inline body and 2 attachments (test1.txt and erlang.png respectively). The key here is to use multipart/mixed MIME type and construct the email body accordingly.
send_email_with_attachment() ->
From = "noreply#mydomain.com",
ToList = ["target_email#mydomain.com"],
Part2Filename = "/tmp/test1.txt",
{ok, Part2Binary} = file:read_file(Part2Filename),
Part3Filename = "/tmp/erlang.png",
{ok, Part3Binary} = file:read_file(Part3Filename),
Email = mimemail:encode(
{
<<"multipart">>, %%Type,
<<"mixed">>, %%Subtype,
%%Headers,
[
{<<"From">>, <<"No-Reply <noreply#mydomain.com>">>},
{<<"To">>, <<"target_email#mydomain.com">>},
{<<"Subject">>, <<"Mail Subject">>}
],
#{}, %%[], %%ContentTypeParams,
%%(Multi)Parts
[
%%Part 1: this is the inline mail body, note the {<<"disposition">>, <<"inline">>} tag
{
<<"text">>, %%Type,
<<"plain">>, %%Subtype,
%%Headers
[],
%%ContentTypeParams
#{
disposition => <<"inline">>
},
%%Part
<<"Email body (inline) is here blah blah..">>
},
%%Part 2: this is the text file as attachment, note the {<<"disposition">>, <<"attachment">>} tag
{
<<"text">>, %%Type,
<<"plain">>, %%Subtype,
%%Headers
[],
%%ContentTypeParams
#{
disposition => <<"attachment">>,
disposition_params => [{<<"filename">>, <<"test1.txt">>}]
},
%%Part
Part2Binary
},
%%Part 3: this is the PNG file as attachment, note the {<<"disposition">>, <<"attachment">>} tag
{
<<"image">>, %%Type,
<<"png">>, %%Subtype,
%%Headers
[],
%%ContentTypeParams
#{
disposition => <<"attachment">>,
disposition_params => [{<<"filename">>, <<"erlang.png">>}]
},
%%Part
Part3Binary
}
]
},
[] %%Options
),
Opts = [{relay, "smtp.mydomain.com"},
{tls, never}
],
gen_smtp_client:send({From, ToList, Email}, Opts).
This is what you will see in the mailbox:

Related

How can I add another prefix to the bot run by the following code?

I'm making a discord bot but I'm trying to use 2 prefix's (G! and g!) as its case sensitive but I cant figure out how to make a 2nd prefix for it to work.
I have tried to separate strings with a comma between 2 strings but I got error codes.
DETECTS WETHER MESSAGE HAS PREFIX (IN DIFFERENT FILE)
module.exports = (client, message) => {
// Ignore all bots
if (message.author.bot) return;
// Ignore messages not starting with the prefix (in config.json)
if (message.content.indexOf(client.config.prefix) !== 0) return;
CONFIG FILE
{
"token": "xxx",
"prefix": "G!",
"prefix2": "g!",
"everyoneMention": true,
"hostedBy": true
}

Added spaces and LRs in JSON via Appmaker

I'm working in AppMaker to create a new employee/user provisioning workflow. I'm at the point where I am creating a new G Suite user, and I have a very weird problem with spaces in my JSON. It's leading to an error: GoogleJsonResponseException: API call to directory.users.insert failed with error:
Invalid Input at provisionUser (AdminDirectory:5)
I've been troubleshooting this for a while, and it seems that somewhere between AppMaker and the AdminDirectory call, some extra spaces and hard returns are inserted into my user data. Below I'm going to show:
The client-side code for gathering user information
The server-side user creation script
The console output of the user JSON
The output of the user JSON that comes to my email for troubleshooting
The emailed output as seen in Notepad++
Client-Side Code
...a bunch of data gathering from a form, then...
var user = {
primaryEmail: email,
name: {
givenName: firstName,
familyName: lastName
},
addresses: [{
type: 'work',
formatted: address
}],
organizations: [{
title: title,
department: department,
fullTimeEquivalent: ftpt
}],
phones: [{
type: 'work',
value: phone
}],
locations: [{
buildingId: building,
type: 'desk',
area: 'desk'
}],
password: 'xxxxxxxx',
changePasswordAtNextLogin: true,
orgUnitPath: orgUnit,
relations: [{
type: 'manager',
value: supervisor
}],
customSchemas: {
sclsnj: {
startDate: effective,
mls: mls,
location: location
}
}
};
google.script.run.provisionUser(user, grouparray);
Server-Side Code
function provisionUser(user, grouparray) {
user = JSON.stringify(user);
MailApp.sendEmail('lhoffman#xxxxxxxx.org', 'New Google User', user);
console.log(user);
user = AdminDirectory.Users.insert(user);
for (var g = 0; g < grouparray.length; g++) {
var groupEmail = grouparray[g] + '#xxxxxxxx.org';
var member = {
email: user,
role: 'MEMBER'
};
Logger.log(groupEmail);
AdminDirectory.Members.insert(member, groupEmail);
}
}
Console Output
{"primaryEmail":"jsmith#xxxxxxxx.org","name":{"givenName":"John","familyName":"Smith"},"addresses":[{"type":"work","formatted":"Bound Brook branch\n402 E High Street, Bound Brook, NJ 08805"}],"organizations":[{"title":"Library Technician","department":"Adult Services","fullTimeEquivalent":100000}],"phones":[{"type":"work","value":"908-458-8410"}],"locations":[{"buildingId":"BBROOK","type":"desk","area":"desk"}],"password":"xxxxxxxx","changePasswordAtNextLogin":true,"orgUnitPath":"/Branches/Bound Brook branch","relations":[{"type":"manager","value":"msmith#xxxxxxxx.org"}],"customSchemas":{"sclsnj":{"startDate":"2019-05-20T04:00:00.000Z","mls":false,"location":"Bound Brook branch"}}}
Email Output
{"primaryEmail":"jsmith#xxxxxxxx.org","name":{"givenName":"John","familyName":"Smith"},"addresses":[{"type":"work","formatted":"Bound
Brook
branch\n402 E High Street, Bound Brook, NJ
08805"}],"organizations":[{"title":"Library Technician","department":"Adult
Services","fullTimeEquivalent":100000}],"phones":[{"type":"work","value":"908-458-8410"}],"locations":[{"buildingId":"BBROOK","type":"desk","area":"desk"}],"password":"xxxxxxxx","changePasswordAtNextLogin":true,"orgUnitPath":"/Branches/Bound
Brook
branch","relations":[{"type":"manager","value":"msmith#xxxxxxxx.org"}],"customSchemas":{"sclsnj":{"startDate":"2019-05-20T04:00:00.000Z","mls":false,"location":"Bound
Brook branch"}}}
Email Output in Notepad++ (with control characters)
NOTE: I realize the JSON is actually represented in the image twice, but you can definitely see the extra space and left feed characters there.
I've confirmed that I can provision a user with the console output by using the Google API Explorer and copying and pasting it into the request body for the Directory API Users.insert.
I've also confirmed that the emailed version of the output does not work using the same method. When I paste that version into the request body in the API Explorer, I get an error alert and it's clear from the color-coding in the body field that it is not correct. Even when I don't email the output to myself, I get the same error behavior, so I'm pretty sure that the act of emailing isn't messing things up.
Help!!
The invalid input is a result of a specific field not correclty formatted. In your case, I see that you are using custom schemas. There is a specific filed in the custom schema that is not properly formatted, that is startDate. Instead of providing the value of 2019-05-20T04:00:00.000Z, just use 2019-05-20.
The reason for that is because the field is expecting a complete date only value instead of a complete date plus hours and minutes. As you can see on the ISO-8601 date format, the complete date format is YYYY-MM-DD.
Reference: https://developers.google.com/admin-sdk/directory/v1/reference/schemas/insert
What happens if you do it this way?
function provisionUser(user, grouparray) {
var user = JSON.stringify(user);
MailApp.sendEmail('lhoffman#xxxxxxxx.org', 'New Google User', user);
console.log(user);
AdminDirectory.Users.insert(user);
for (var g = 0; g < grouparray.length; g++) {
var groupEmail = grouparray[g] + '#xxxxxxxx.org';
var member = {
email: user,
role: 'MEMBER'
};
Logger.log(groupEmail);
AdminDirectory.Members.insert(member, groupEmail);
}
}

How to parse encoded HTML

I'm working on a digest email to send to users of my companies app. For this I'm going through each users emails and trying to find some basic information about each email (from, subject, timestamp, and, the aspect that's causing me difficulty, an image).
I assumed Nokogiri's search('img') function would be fine to pull out images. Unfortunately it looks like most emails have a lot of garbage embedded in the URLs of those images, like newlines ("\n"), escape characters ("\"), and the string "3D" for some reason. For example:
<img src=3D\"https://=\r\nd3ui957tjb5bqd.cloudfront.net/images/emails/1/logo.png\"
This is causing the search to only pull out pieces of the actual URLs/src's:
#(Element:0x3fd0c8e83b80 {
name = "img",
attributes = [
#(Attr:0x3fd0c8e82a28 { name = "src", value = "3D%22https://=" }),
#(Attr:0x3fd0c8e82a14 { name = "d3ui957tjb5bqd.cloudfront.net", value = "" }),
#(Attr:0x3fd0c8e82a00 { name = "width", value = "3D\"223\"" }),
#(Attr:0x3fd0c8e829ec { name = "heigh", value = "t=3D\"84\"" }),
#(Attr:0x3fd0c8e829d8 { name = "alt", value = "3D\"Creative" }),
#(Attr:0x3fd0c8e829c4 { name = "market", value = "" }),
#(Attr:0x3fd0c8e829b0 { name = "border", value = "3D\"0\"" })]
})
Does anyone have an idea why this is happening, and how to remove all this junk?
I'm getting decent results from lots of gsub's and safety checks but it feels pretty tacky.
I've also tried Sanitize.clean which doesn't work and the PermitScrubber mentioned in "How to sanitize html string except image url?".
The mail body is encoded as quoted printable. You will need to decode the body before you parse it with Nokogiri. You can do this fairly easily with Ruby using unpack:
decoded = encoded.unpack('M').first
You should check what the encoding is by looking at the mail headers before trying to decode, not all mail is encoded this way, and there are other types of encoding.
I am not a master in scraping, but you are able to get it through the CSS attribute
.at_css("img")['src']
For example:
require "open-uri"
require "nokogiri"
doc = open(url_link)
page = Nokogiri::HTML(doc)
page.css("div.col-xs-12.visible-xs.visible-sm div.school-image").each do |pic|
img = pic.at_css("img")['src'].downcase if pic.at_css("img")
end

Querying JSON with JSONPath or SelectTokens? With JSON.NET in C#

I am trying to use the Newtonsoft.Json.Net in c#. The following is part of JSON file that I need to retrieve data out of:
{
"video":{
"local_recording_device":{
"codecs":null
},
"preferred_string":"___PREFERRED___",
"streams":{
"99176901":{
"id":"99176901",
"name":"PTZ Camera",
"site":"someone",
"email":"someone#awebsite.com",
"codec":"VP8 HD1 (720p)",
"local":true,
"screen":false,
"fit_to_window":true,
"stay_on_top":false,
"layout":0,
"native_width":1280,
"native_height":720,
"window_width":456,
"window_height":254,
"preferred":false,
"local_recording":false,
"device_id":"MJPEG Camera",
"normalized_device_id":"MJPEGCamera",
"shared_window_id":"MJPEG Camera",
"enable":true,
"big_location":"2",
"x":347,
"y":737,
"window_id":"197302",
"camera_id":null
},
"3091494011":{
"id":"3091494011",
"name":"Logitech Webcam C930e",
"site":"Joe Smith",
"email":"joe#awebsite.com",
"codec":"VP8 Medium (CIF)",
"local":false,
"screen":false,
"fit_to_window":true,
"stay_on_top":false,
"layout":0,
"native_width":352,
"native_height":288,
"window_width":864,
"window_height":702,
"preferred":true,
"local_recording":false,
"enable":true,
"big_location":"1",
"x":204,
"y":0,
"window_id":"197296",
"camera_id":null
},
"3798287599":{
"id":"3798287599",
"name":"Drive Camera",
"site":"ASiteName",
"email":"asitesame#awebsite.com",
"codec":"VP8 HD1 (720p)",
"local":true,
"screen":false,
"fit_to_window":true,
"stay_on_top":false,
"layout":0,
"native_width":1280,
"native_height":720,
"window_width":456,
"window_height":254,
"preferred":true,
"local_recording":false,
"device_id":"Logitech Webcam C930e",
"normalized_device_id":"LogitechWebcamC930e",
"shared_window_id":"Logitech Webcam C930e",
"enable":true,
"big_location":"3",
"x":814,
"y":737,
"window_id":"262822",
"camera_id":null
}
}
}
}
So, inside the JSON data is: "video", "streams" inside streams can be x amount of different streams (stream id's). The streams in "streams" (the long numbers) can change at anytime. In my example here there are three. I need to search through all streams in "streams" and see if any of them has a "email" that matches a particular email address. Each of the streams has a "email". If a email matches my supplied email address I need to check that streams "enable" to see if it's true or false.
Any help is appreciated in leading me in the right direction. I have not worked with a JSON data before.
You can use LINQ to JSON and SelectTokens to do the required query:
string json = GetJson();
var obj = JObject.Parse(json);
var testEmail = "someone#awebsite.com";
var streamQuery = obj.SelectTokens("video.streams.*").Where(s => (string)s["email"] == testEmail);
var firstEnabled = streamQuery.Select(s => (bool?)s["enable"]).FirstOrDefault();
The query returns a nullable bool that is true or false if the first stream for the desired email is enabled, or null if there is no stream for that email address.
Note that this returns the enabled state of the first stream matching the given email address. If you want to know if any are enabled, do:
var anyEnabled = streamQuery.Any(s => (bool)s["enable"]);

How to user erlang gen_smtp to send html-formatted email?

gen_smtp can be found here
What I want is to let the content of email supports HTML tag, such as <strong>Hello</strong>
Will display as Hello.
Look at https://github.com/selectel/pat. It's an easy to use SMTP client and you can use any text, including html tags as body of the message.
See the gen_smtp mimemail tests for an example of multipart/alternative messages:
Email = {<<"text">>, <<"html">>, [
{<<"From">>, <<"me#example.com">>},
{<<"To">>, <<"you#example.com">>},
{<<"Subject">>, <<"This is a test">>}],
#{content_type_params => [
{<<"charset">>, <<"US-ASCII">>}],
disposition => <<"inline">>
},
<<"This is a <strong>HTML</strong> message with some non-ascii characters øÿ\r\nso there">>},
Encoded = mimemail:encode(Email)
The answer given by #Ward Bekker is fundamentally correct but it took me a while to make it work as the mimemail:encode/1 expects a proplist not a map which the example shows.
I used Erlang Erlang/OTP 23 [erts-11.0.3] and it failed with:
** exception error: no function clause matching proplists:get_value(<<"content-type-params">>, #{disposition => <<"inline">>,<<"content-type-params">> => [{<<"charset">>,<<"US-ASCII">>}]},[]) (proplists.erl, line 215)
in function mimemail:ensure_content_headers/7 (/Users/sean/Documents/code/erlang/scofblog/_build/default/lib/gen_smtp/src/mimemail.erl, line 661)
The following is the modified code and the encoded output:
Email = {
<<"text">>,
<<"html">>,
[
{<<"From">>, <<"me#example.com">>},
{<<"To">>, <<"you#example.com">>},
{<<"Subject">>, <<"This is a test">>}
],
[{<<"content-type-params">>, [{<<"charset">>, <<"US-ASCII">>}]},
{<<"disposition">>, <<"inline">>}
],
<<"This is a <strong>HTML</strong> øÿ\r\nso there">>
}.
62> mimemail:encode(Email).
<<"From: me#example.com\r\nTo: you#example.com\r\nSubject: This is a test\r\nContent-Type: text/html;\r\n\tcharset=US-ASCII\r\nCon"...>>
Hope that saves some head scratching.