Send email at a later time - microsoft-graph-api

I am wondering if the /v1.0/me/sendMail has the ability to delay sending an email. In the Outlook client, you can specify that you want your email sent at a later date and time. I've snooped around to see if there is a property that can be set on the message object to indicate this.
Did anyone find a way to get this working? Of course, I could implement something in my software to handle the delayed sending, but why re-create something if it is already there.

You can achieve delayed sending of emails using extended properties. These can be set on the Graph API request payload using the "singleValueExtendedProperties" attribute.
The property to use is PidTagDeferredSendTime which has the ID 0x3FEF and type SystemTime.
The id attribute of "singleValueExtendedProperties" takes different formats depending on the property you are setting.
For the deferred send time you would use SystemTime 0x3FEF.
Example using a HTTP JSON POST Payload:
{
"message": {
"subject": "Meet for lunch?",
"body": {
"contentType": "Text",
"content": "The new cafeteria is open."
},
"toRecipients": [
{
"emailAddress": {
"address": "bob#contoso.com"
}
}
],
"singleValueExtendedProperties":
[
{
"id":"SystemTime 0x3FEF",
"value":"2019-01-29T20:00:00"
}
]
}
}
Example using the Microsoft Graph API client library:
var client = /* Create and configure GraphServiceClient */;
var msg = new Message();
msg.ToRecipients = List<Recipient>();
msg.ToRecipients.Add(new Recipient() {
EmailAddress = new EmailAddress() { Address ="bob#contoso.com" }
};
msg.Subject = "Meet for lunch?";
msg.Body = new ItemBody()
{
Content = "The new cafeteria is open.",
ContentType = BodyType.Text,
};
msg.SingleValueExtendedProperties = new MessageSingleValueExtendedPropertiesCollectionPage();
msg.SingleValueExtendedProperties.Add(new SingleValueLegacyExtendedProperty()
{
Id = "SystemTime 0x3FEF",
Value = DateTime.UtcNow.AddMinutes(5).ToString("o")
});
await client.Me.SendMail(msg, true).Request().PostAsync();

https://gallery.technet.microsoft.com/office/Send-Emails-until-a-9cee20cf
You set the deferred send time extended prop when creating the item.

Related

How to send a chat message of type 'announcement' to a Teams Channel via Microsoft Graph?

I'm trying to send a chatMessage of type 'announcement' to a Teams channel. However, I receive the following error message:
Attachment Id missing from the body.
Since the attachment id is read-only, I first tried to omit it. When I received the error, I tried to set a random id in the attachment and add this also to the body's content. In both cases I do receive the same error.
I have not found any example that shows how to send an announcement to a channel, so I hope to find help here.
I'm using PowerShell and the Microsoft.Graph module, but if you are able to provide help or even a solution using any other SDK or pure JSON/REST, I'm happy about that as well.
This is my code:
Select-MgProfile beta
Connect-MgGraph -Scopes "ChannelMessage.Send"
$attachments = #{
contentType = "application/vnd.microsoft.teams.messaging-announcementBanner"
content = #{
#id = "<Insert random id>"
title = "This is an important announcement"
cardImageType = "colorTheme"
cardImageDetails = '{"colorTheme":"periwinkleBlue"}'
}
}
$bodyContent = #'
<attachment id="<Insert random id>"></attachment>
<div>Hi all, ...</div>
'#
$params = #{
ChannelId = "..."
TeamId = "..."
Attachments = $attachments
Importance = "high"
Body = #{
ContentType = "html"
Content = $bodyContent
}
}
New-MgTeamChannelMessage #params
Can you please provide the link to the documentation? In my provided solution, I will assume that you reference to chatMessageAttachment.
{
"id": "string (identifier)",
"contentType": "string",
"contentUrl": "string",
"content": "string",
"name": "string",
"thumbnailUrl": "string"
}
From the documentation.
{
"contentType": "application/vnd.microsoft.teams.messaging-announcementBanner",
"content": {
"id": "<Insert random id>",
"title": "This is an important announcement",
"cardImageType": "colorTheme",
"cardImageDetails": "{\"colorTheme\":\"periwinkleBlue\"}"
}
}
From your powershell script.
content is defined as string, not as an object. I guess, you have to stringify the json and add it into content.
{
"id": "<insert id>",
"contentType": "application/vnd.microsoft.teams.messaging-announcementBanner",
"content": "{\"id\":\"<Insert random id>\",\"title\":\"This is an important announcement\",\"cardImageType\":\"colorTheme\",\"cardImageDetails\":\"{\"colorTheme\":\"periwinkleBlue\"}\"}"
}

How can I set the email for a Team with Microsoft Graph API?

We are creating a team group (with the beta Graph API) and we want the emailaddress to contain another value than the value that's based on what's provided in displayName.
While searching through the documentation it seems that this is possible by providing a value for mailNickname in AdditionalData (https://learn.microsoft.com/en-us/graph/teams-create-group-and-team).
So I implemented that. Unfortunately the mailaddress and the alias were still like TestGroup#domain.nl instead of TestMailNickname#domain.nl.
var graphApiServiceClient = new GraphServiceClient(this.authenticationProvider)
{
BaseUrl = "https://graph.microsoft.com/beta"
};
var owner = "valueForOwner";
var teamTemplate = teamTemplateType == TeamTemplateType.Staff
? "educationStaff"
: "educationClass";
var team = new Team
{
AdditionalData = new Dictionary<string, object>
{
{ "template#odata.bind", $"https://graph.microsoft.com/beta/teamsTemplates('{teamTemplate}')" },
{ "owners#odata.bind", new[]{$"https://graph.microsoft.com/beta/users('{owner}')"}},
{ "displayName", "TestGroup" },
{ "description", "This is a testgroup" },
{ "mailNickname", "TestMailNickname" }
}
};
await graphApiServiceClient.Teams.Request().AddAsync(team);
The MailNickname does change when I update the MailNickname property afterwards with an update request like await graphApiServiceClient.Groups[objectId].Request().UpdateAsync(new Group { MailNickname = mailNickname});.
This is confirmed with a graphApiServiceClient.Groups[objectId].Request().GetAsync()
Unfortunately it still shows TestGroup#domain.nl as the alias in the admin at https://admin.microsoft.com/AdminPortal/Home#/groups.
But, updating the value like this doesn't work for the Mail property because it states it's readonly in the update request.
Does anyone know what I am doing wrong in my original create/add request?
Plus does anyone know why the old alias value is still shown instead of the updated alias at https://admin.microsoft.com/AdminPortal/Home#/groups?
This is possible by creating a group via the Graph api first and then using the group ID to create a team for this group.
Create the group via "https://graph.microsoft.com/v1.0/groups".
{
"displayName": "TestGroup",
"mailNickname": "TestMailNickname",
"mailEnabled": true,
"securityEnabled": false,
"description": "This is a testgroup",
"groupTypes": [
"Unified"
],
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/users/OWNEDID"
]
}
Once the group has been made it'll output an ID which you'll then use to create a team via "https://graph.microsoft.com/v1.0/groups/YOURIDHERE/team"
Body would look something like;
{
"memberSettings": {
"allowCreateUpdateChannels": false,
"allowAddRemoveApps": false,
"allowCreateUpdateRemoveTabs": false,
"allowCreateUpdateRemoveConnectors": false
}
}
It looks like you are using an EDU tenant, since you are referencing the particular templates.
I have tested this previously, and the method suggested above will not work with the "EducationClass" template.
The way I got it to work was:
Create the Team with the template
$Teamdata =
'{
"displayName":"' + $newteamName + '",
"description":"' + $newteamName + '",
"template#odata.bind": "https://graph.microsoft.com/v1.0/teamsTemplates(\u0027educationClass\u0027)",
"hideFromOutlookClients": "true",
"hideFromAddressLists": "true",
"members":[
{
"#odata.type":"#microsoft.graph.aadUserConversationMember",
"roles":[
"owner"
],
"user#odata.bind":"'+ $DefaultOwnerURL + '"
}
]
}'
Wait for the group to be created (usually about 20 seconds)
patch the group mailnickname
$Body_SetGroupSDSSettings =
'{
"mailNickname": "' + $newteamMailNickname + '"
}'
It isn't perfect, but it is the best way I could find to do this.

Twilio and Sendgrid - Incoming SMS

Current we send one-way SMS via MS Flow and Twilio which works fine. I have been exploring how to handle incoming SMS, so I followed a guide and managed to utilise Sendgrid to forward incoming SMS to my e-mail address which also works.
However, I am looking to have the original sender receive the SMS via e-mail. I can give each staff member their own phone number which would define each individual but I need a way of Twilio or Sendgrid doing a lookup prior to sending the reply e-mail so it knows where to send it i.e.
User 1 = 01234455678,
User 2 = 01234543245,
User 3 = 06546546445,...etc.
I guess I could re-create the same process for each number but it would require lots of Twilio Functions and Variables which doesn't seem like a great way to accomplish this?
Sorry, I a not much of a coder and try to use on-line guides and forums where I can.
Many Thanks,
JP
You can try something like this, hosting the mapping on Twilio Assets as a Private asset but you could also pull this information into Studio via the HTTP Request Widget if you hosted it on an external server (a bit more advanced). In my case I called my file mapping.json which has the format:
[
{
"name": "John Doe",
"phone": "+14075551212",
"email": "jdoe#example.com"
},
{
"name": "Susan Doe",
"phone": "+19545551212",
"email": "sdoe#example.com"
},
{
"name": "Nadia Doe",
"phone": "+14705551212",
"email": "ndoe#example.com"
},
{
"name": "Carl Doe",
"phone": "+18025551212",
"email": "cdoe#example.com"
}
]
Then you would use the Run Function widget and send in 3 key:value pairs (Function Parameters):
From - {{trigger.message.From}}
To - {{trigger.message.To}}
Body - {{trigger.message.Body}}
Your Twilio Function would then consume these parameters and the contents of the private asset to handle the mapping. Make sure to configure your Twilio Functions environment with the Sendgrid NPM package, #sendgrid/mail version 7.0.1 and you configure the two Sendgrid specific environmental variables below with their respective values (accessed via the context object in the JavaScript):
SENDGRID_API_KEY
FROM_EMAIL_ADDRESS
const fs = require('fs');
const sgMail = require('#sendgrid/mail');
exports.handler = function(context, event, callback) {
let from = event.From;
let to = event.To;
let body = event.Body;
let fileName = 'mapping.json';
let file = Runtime.getAssets()[fileName].path;
let text = fs.readFileSync(file);
let mappings = JSON.parse(text);
// Filter array to match to number
let result = mappings.filter(record => record.phone === to);
if (result.length) {
sgMail.setApiKey(context.SENDGRID_API_KEY);
// Define message params
const msg = {
to: result[0].email,
from: context.FROM_EMAIL_ADDRESS,
text: body,
subject: `New SMS from: ${from}`,
};
// Send message
sgMail.send(msg)
.then(response => {
console.log("Success.");
callback();
})
.catch(err => {
console.log("Not Success.");
callback(err);
});
} else {
console.log("** NO MATCH **");
callback();
}
};
Let me know how it goes.

Keep getting Twilio error message 90100 on execution, but results are working

I'm not a programmer so please forgive me. But I've spent hours and hours of research on the topic of collecting information with Twilio AutoPilot and posted that data to Airtable, which I will then have Zapier do some things with that data. I finally had a breakthrough today and am now able to post data from a call or text to Airtable. The only way I got the ending to work was to send the call or text to Studio to finish up the call. Everything seems to work from the end user standpoint, but I'm getting an error 90100 from Twilio. I'm sure I'm just missing one line of code for this to work, and I'm at the end of my rope.
{
"actions": [
{
"say": "Okay lets get you a new appointment. I just need you to answer a few questions."
},
{
"collect": {
"name": "member",
"questions": [
{
"question": "Please tell me your first name.",
"name": "name",
"type": "Twilio.FIRST_NAME"
},
{
"question": "Thanks, and what is your email address?",
"name": "email",
"type": "Twilio.EMAIL"
}
],
"on_complete": {
"redirect": "task://complete_booking"
}
}
}
]
}
Then i have another task setup to redirect to the Twilio Function. This is probably overkill, but it's what I found in research.
{
"actions": [
{
"redirect": {
"method": "POST",
"uri": "https://TWILIO_FUNCTION_URL/atable_post"
}
}
]
}
Then the function is as follows. Mind you, this is posting correctly to airtable.
exports.handler = function(context, event, callback) {
let memory = JSON.parse(event.Memory);
let name = memory.twilio.collected_data.member.answers.name.answer;
let email = memory.twilio.collected_data.member.answers.email.answer;
console.log(memory);
let member = {
name : memory.twilio.collected_data.member.answers.name.answer,
email : memory.twilio.collected_data.member.answers.email.answer,
date : Date.now()
};
var Airtable = require("airtable");
var base = new Airtable({apikey: context.AIRTABLE_API_KEY}).base("AIRTABLE_ID");
base("Members1").create(member, function(err, record) {
if (err) { console.error(err); return; }
console.log(record.getId());
callback(null, member);
});
};
The call hung up at this point, so I redirected it to a Studio Flow, which does work and the call finishes with the response I'm give it before ending the call. Again, everything is working fine, but I get the following error from twilio, and I have no idea how to resolve it.
Invalid Autopilot Actions JSON: Invalid Autopilot Action
Any help would be greatly appreciated. Thanks!
Nice work James! It looks the the issue is the redirect to your Twilio Function is not returning the expected JSON Action response to execute.
Autopilot - Redirect
https://www.twilio.com/docs/autopilot/actions/redirect
Redirecting to URLs When redirecting to a URL, Redirect will make an
HTTP callback to your application and will expect an Autopilot Actions
JSON as a response. The request will contain all the dialogue
information. This is an example of a dynamic Action since the JSON is
rendered dynamically with a URL or your own endpoint.
Can you modify the Twilio Function to return valid Action JSON to Autopilot which sets the returned data, if needed via the Remember action which you can access from Studio?

Firebase Cloud Messaging not sending aps payload in correct format for iOS Notification Content & Service Extensions

I'm trying to implement notifications using Firebase. The notification is received correctly when the app is in the background or foreground. So, the basic mechanics are working.
Now I've added Content Extensions and Service Extensions to the app. The Content Extension works when I use a local notification, but the Firebase message payload seems incorrect as far as the optional fields are considered. Here is a link to an image of my console:
And here is the Firebase remote notification payload that comes across (with some of the long Google numbers edited for anonymity:
{
aps =
{
alert =
{
body = "Eureka! 11";
title = "Patient is not doing well";
};
};
category = provider-body-panel;
gcm.message_id = 0:149073;
gcm.n.e = 1;
google.c.a.c_id = 2825604;
google.c.a.e = 1;
google.c.a.ts = 149073;
google.c.a.udt = 0;
mutable-content = 1;
}
It appears that the "category" and "mutable-content" are not in the correct place. They should be in the aps payload.
How can I get those options to be in the payload so that my app can correctly parse it and connect it with the Content and Service Extensions?
To start off, I'm going to mention that there are two types of message payloads for FCM. notification and data. See the documentation here
When sending notifications through the Firebase Notifications Console, it will be treated as a notification payload. However, if you add in Custom Data, it will add it in the payload as a custom key-value pair.
For example, in your post, the FCM payload should look something like this:
{
"notification": {
"body" : "Eureka!",
"title": "Patient is not doing well"
},
"data": {
"category": "provider-body-panel",
"mutable-content" : true,
"click_action" : "provider-body-panel"
}
}
What's wrong?
click_action should be inside notification.
mutable-content should be mutable_content (notice the underscore) and should be on the same level as notification.
(this one I might've misunderstood, but) There is no category parameter for FCM, click_action already corresponds to it.
See the docs for the parameters here.
It it is currently not possible to set the value for click_action and mutable_content when using the Firebase Notifications Console. You'll have to build the payload yourself, something like this:
{
"to": "<REGISTRATION_TOKEN_HERE>",
"mutable_content" : true,
"notification": {
"body" : "Eureka!",
"title": "Patient is not doing well",
"click_action" : "provider-body-panel"
}
}
Then send it from your own App Server. You could also do this by using Postman or cURL
"mutable-content should be "mutable_content" (keyword for firebase server to send as mutable-content for IOS) as you mentioned in your post, I think you left out in edit.
Below is an example with also the corrected format for the data section in the json sent to the FCM server.
So update would be:
{
"to" : "YOUR firebase messaging registration id here",
"mutable_content":true,
"notification": {
"title": "Its about time",
"body": "To go online Amigo",
"click_action": "NotificationCategoryIdentifier ForYourNotificationActions"
},
"data":{
"customKey":"custom data you want to appear in the message payload"
"media-attachment":"mycustom image url",
"catalogID":"mycustom catalog for my custom app"
}
}
Update Firebase Admin SDK and use sendMulticast(payload) method
var admin = require("firebase-admin")
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
// Create a list containing up to 500 registration tokens.
// These registration tokens come from the client FCM SDKs.
const registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// …
'YOUR_REGISTRATION_TOKEN_N',
];
// See documentation on defining a message payload.
var message = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
tokens: registrationTokens,
apns: {
payload: {
aps: {
'mutable-content': true, // use single quote
'category': 'INVITE_CATEGORY' // use single quote
}
},
},
};
// Send a message to the device corresponding to the provided
// registration tokens.
admin.messaging().sendMulticast(message)
.then((response) => {
if (response.failureCount > 0) {
const failedTokens = [];
response.responses.forEach((resp, idx) => {
if (!resp.success) {
failedTokens.push(registrationTokens[idx]);
}
});
console.log('List of tokens that caused failures: ' + failedTokens);
}
});
Ref: https://firebase.google.com/docs/cloud-messaging/send-message#send_messages_to_specific_devices
This worked for me with Cloud functions with Node.js
const payload = {
notification: {
title: name,
body: messageText,
badge: "1",
mutable_content: "true"
},
data: {
type: "MESSAGE",
fromUserId: name,
attachmentUrl: imageUrl
}};

Resources