Microsoft365R icon indicating copy to clipboard operation
Microsoft365R copied to clipboard

Send email on behalf of

Open quickbendelat opened this issue 1 year ago • 7 comments

I have got my company's IT team to set up a Shared Mailbox. I can use get_business_outlook(), and then the methods create_email() and send() successfully.

Now, IT have created some other email addresses and have set them up as "Mail enabled security group" so that these can be used to send emails via the Shared Mailbox. However, I cannot find any documentation on how to send on behalf of the Shared Mailbox. I want the sender to be the individual email address and not the email address of the Shared Mailbox.

The use case is that there will be multiple Docker Containers running in Azure that will have their own email addresses. The architecture that has been defined is for all these to send via the one Shared Mailbox.

quickbendelat avatar Aug 01 '22 04:08 quickbendelat

Have you tried sending the email as normal, but setting the From: address to the desired individual? I haven't got access to a business MS365 org right now, so can't test this.

hongooi73 avatar Aug 01 '22 17:08 hongooi73

I've tried it out, but no success. Adding a "from" argument to either one of create_emaill() and send() both give the error: "unused argument (from = ...)"

quickbendelat avatar Aug 01 '22 21:08 quickbendelat

See if this helps: https://docs.microsoft.com/en-us/graph/outlook-create-send-messages#setting-the-from-and-sender-properties

You can update the properties of an (unsent) email object with the object$update() method.

Also try asking on Stack Overflow, make sure to use the microsoft-graph-api tag rather than anything R-specific.

hongooi73 avatar Aug 02 '22 00:08 hongooi73

I tried modifying the build_email_request method, to add a "from" arguement:

build_email_request.blastula_message <- function(body, content_type,
    subject=NULL, to=NA, cc=NA, bcc=NA, reply_to=NA, from=NA, token=NULL, user_id=NULL, ...)
{
    req <- list(
        body=list(
            contentType="html",  # blastula emails are always HTML
            content=body$html_str
        )
    )
    if(!is_empty(subject))
        req$subject <- subject
    
    utils::modifyList(req, build_email_recipients(to, cc, bcc, reply_to, from))
}

and the build_email_recipients() function based on this link showing that "from" is a field for Microsoft Graph api: https://docs.microsoft.com/en-us/graph/outlook-send-mail-from-other-user

build_email_recipients <- function(to, cc, bcc, reply_to, from)
{
    make_recipients <- function(addr_list)
    {
        # NA means don't update current value
        if(!is_empty(addr_list) && is.na(addr_list))
            return(NA)

        # handle case of a single az_user object
        if(is.object(addr_list))
            addr_list <- list(addr_list)
        

        lapply(addr_list, function(x)
        {
            if(inherits(x, "az_user"))
            {
                props <- x$properties
                x <- if(!is.null(props$mail))
                    props$mail
                else props$userPrincipalName
                if(is_empty(x) || nchar(x) == 0)
                    stop("Unable to find email address", call.=FALSE)
                name <- props$displayName
            }
            else name <- x <- as.character(x)
            if(!all(grepl(".+@.+", x)))  # basic check for a valid address
                stop("Invalid email address supplied", call.=FALSE)
            list(emailAddress=list(name=name, address=x))
        })
    }

    out <- list(
        toRecipients=make_recipients(to),
        ccRecipients=make_recipients(cc),
        bccRecipients=make_recipients(bcc),
        replyTo=make_recipients(reply_to)
    )
    out[sapply(out, function(x) is_empty(x) || !is.na(x))]
}

But it fails at the the second step in the code below inside the create_email() function:

req <- build_email_request(body, content_type, subject, to, cc, bcc, reply_to, from)

res <- ms_outlook_email$new(self$token, self$tenant,
                                      self$do_operation("messages", body=req, http_verb="POST"), user_id=self$user_id)

with the error:

Bad Request (HTTP 400). Failed to complete operation. Message:
Property from in payload has a value that does not match schema.

quickbendelat avatar Aug 02 '22 00:08 quickbendelat

Based on your comment about looking at the object$update() method and looking at this link where I am led to believe "sender" is a valid property: https://docs.microsoft.com/en-us/graph/outlook-send-mail-from-other-user

I then changed tack and have now tried adding an extra method to the ms_outlook_email R6 object:

set_sender=function(sender=NULL)
    {
      if(is_empty(sender))
        message("Clearing sender")
    
      sender <- build_email_sender(sender)
      sender$sender <- self$properties$sender
      do.call(self$update, sender)
    },

which is a modified copy of the set_reply_to method.

Also created a modified copy of build_email_recipients :

build_email_sender <- function(sender)
{
  make_recipients <- function(addr_list)
  {
    # NA means don't update current value
    if(!is_empty(addr_list) && is.na(addr_list))
      return(NA)
    
    # handle case of a single az_user object
    if(is.object(addr_list))
      addr_list <- list(addr_list)
    
    
    lapply(addr_list, function(x)
    {
      if(inherits(x, "az_user"))
      {
        props <- x$properties
        x <- if(!is.null(props$mail))
          props$mail
        else props$userPrincipalName
        if(is_empty(x) || nchar(x) == 0)
          stop("Unable to find email address", call.=FALSE)
        name <- props$displayName
      }
      else name <- x <- as.character(x)
      if(!all(grepl(".+@.+", x)))  # basic check for a valid address
        stop("Invalid email address supplied", call.=FALSE)
      list(emailAddress=list(name=name, address=x))
    })
  }
  # browser()
  out <- list(
    sender=make_recipients(sender)
  )
  out[sapply(out, function(x) is_empty(x) || !is.na(x))]
}

My understanding is that this will build a list that can be passed through to the object in the Shared mailbox. However, when I run eml$set_sender, I receive the error:

Error in process_response(res, match.arg(http_status_handler), simplify) : 
Bad Request (HTTP 400). Failed to complete operation. Message:
Empty Payload. JSON content expected.

Debugging build_email_sender(), I get a valid output the same as build_email_recipients

Browse[2]> out
$sender
$sender[[1]]
$sender[[1]]$emailAddress
$sender[[1]]$emailAddress$name
[1] "[email protected]"

$sender[[1]]$emailAddress$address
[1] "[email protected]"

It would be great if someone with better knowledge than me could work on adding extra methods to set the "sender", as this would enhance the usability of this package.

quickbendelat avatar Aug 02 '22 02:08 quickbendelat

KISS solution for anyone coming across this issue. Based on @hongooi73's tip to use the object$update() method, the Microsoft Graph API JSON payload schema ( https://learn.microsoft.com/en-us/graph/outlook-send-mail-from-other-user#sending-with-microsoft-graph), and noting that the object$update()'s internals are:

function (...) 
{
    self$do_operation(body = list(...), encode = "json", http_verb = "PATCH")
    self$properties <- self$do_operation()
    self
}

You can write a nested list (to go into the ellipsis above) so the method will send a properly formatted payload to modify the draft email like this:

email$update(
  from = list(
    emailAddress = list(
      address = "[email protected]")
  )
)

daniel-fahey avatar Jun 14 '23 10:06 daniel-fahey

Can I please follow up on this and ask if I can use the

email$update(
   ...

to add a different "reply to" address? (Just to make sure, I want to send an email on my behalf but with another address for which I have permission to do so. But I would like the recipient to hit reply and send the email to my own address. Is that possible?).

Thanks!

giabaio avatar Oct 05 '23 16:10 giabaio