3

I am trying to send email using MFMailComposeViewController on my iOS application.

I have a Gmail account configured in the iPhone default Mail app. But whenever I try to send email, I am getting MFMailComposeViewController canSendMail as false, because of this my code fails to open the Mail app with the given to, cc and attachments

#import <MessageUI/MessageUI.h>
#import "RNMail.h"
#import <React/RCTConvert.h>
#import <React/RCTLog.h>

@implementation RNMail
{
    NSMutableDictionary *_callbacks;
}

- (instancetype)init
{
    if ((self = [super init])) {
        _callbacks = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();
}

+ (BOOL)requiresMainQueueSetup
{
    return YES;
}

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(mail:(NSDictionary *)options
                  callback: (RCTResponseSenderBlock)callback)
{
    if ([MFMailComposeViewController canSendMail])
    {
        MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init];
        mail.mailComposeDelegate = self;
        _callbacks[RCTKeyForInstance(mail)] = callback;

        if (options[@"subject"]){
            NSString *subject = [RCTConvert NSString:options[@"subject"]];
            [mail setSubject:subject];
        }

        bool *isHTML = NO;

        if (options[@"isHTML"]){
            isHTML = [options[@"isHTML"] boolValue];
        }

        if (options[@"body"]){
            NSString *body = [RCTConvert NSString:options[@"body"]];
            [mail setMessageBody:body isHTML:isHTML];
        }

        if (options[@"recipients"]){
            NSArray *recipients = [RCTConvert NSArray:options[@"recipients"]];
            [mail setToRecipients:recipients];
        }

        if (options[@"ccRecipients"]){
            NSArray *ccRecipients = [RCTConvert NSArray:options[@"ccRecipients"]];
            [mail setCcRecipients:ccRecipients];
        }

        if (options[@"bccRecipients"]){
            NSArray *bccRecipients = [RCTConvert NSArray:options[@"bccRecipients"]];
            [mail setBccRecipients:bccRecipients];
        }

        if (options[@"attachment"] && options[@"attachment"][@"path"] && options[@"attachment"][@"type"]){
            NSString *attachmentPath = [RCTConvert NSString:options[@"attachment"][@"path"]];
            NSString *attachmentType = [RCTConvert NSString:options[@"attachment"][@"type"]];
            NSString *attachmentName = [RCTConvert NSString:options[@"attachment"][@"name"]];

            // Set default filename if not specificed
            if (!attachmentName) {
                attachmentName = [[attachmentPath lastPathComponent] stringByDeletingPathExtension];
            }

            // Get the resource path and read the file using NSData
            NSData *fileData = [NSData dataWithContentsOfFile:attachmentPath];

            // Determine the MIME type
            NSString *mimeType;

            /*
             * Add additional mime types and PR if necessary. Find the list
             * of supported formats at http://www.iana.org/assignments/media-types/media-types.xhtml
             */
            if ([attachmentType isEqualToString:@"jpg"]) {
                mimeType = @"image/jpeg";
            } else if ([attachmentType isEqualToString:@"png"]) {
                mimeType = @"image/png";
            } else if ([attachmentType isEqualToString:@"doc"]) {
                mimeType = @"application/msword";
            } else if ([attachmentType isEqualToString:@"ppt"]) {
                mimeType = @"application/vnd.ms-powerpoint";
            } else if ([attachmentType isEqualToString:@"html"]) {
                mimeType = @"text/html";
            } else if ([attachmentType isEqualToString:@"csv"]) {
                mimeType = @"text/csv";
            } else if ([attachmentType isEqualToString:@"pdf"]) {
                mimeType = @"application/pdf";
            } else if ([attachmentType isEqualToString:@"vcard"]) {
                mimeType = @"text/vcard";
            } else if ([attachmentType isEqualToString:@"json"]) {
                mimeType = @"application/json";
            } else if ([attachmentType isEqualToString:@"zip"]) {
                mimeType = @"application/zip";
            } else if ([attachmentType isEqualToString:@"text"]) {
                mimeType = @"text/*";
            } else if ([attachmentType isEqualToString:@"mp3"]) {
                mimeType = @"audio/mpeg";
            } else if ([attachmentType isEqualToString:@"wav"]) {
                mimeType = @"audio/wav";
            } else if ([attachmentType isEqualToString:@"aiff"]) {
                mimeType = @"audio/aiff";
            } else if ([attachmentType isEqualToString:@"flac"]) {
                mimeType = @"audio/flac";
            } else if ([attachmentType isEqualToString:@"ogg"]) {
                mimeType = @"audio/ogg";
            } else if ([attachmentType isEqualToString:@"xls"]) {
                mimeType = @"application/vnd.ms-excel";
            }

            // Add attachment
            [mail addAttachmentData:fileData mimeType:mimeType fileName:attachmentName];
        }

        UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController];

        while (root.presentedViewController) {
            root = root.presentedViewController;
        }
        [root presentViewController:mail animated:YES completion:nil];
    } else {
        callback(@[@"not_available"]);
    }
}

#pragma mark MFMailComposeViewControllerDelegate Methods

- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    NSString *key = RCTKeyForInstance(controller);
    RCTResponseSenderBlock callback = _callbacks[key];
    if (callback) {
        switch (result) {
            case MFMailComposeResultSent:
                callback(@[[NSNull null] , @"sent"]);
                break;
            case MFMailComposeResultSaved:
                callback(@[[NSNull null] , @"saved"]);
                break;
            case MFMailComposeResultCancelled:
                callback(@[[NSNull null] , @"cancelled"]);
                break;
            case MFMailComposeResultFailed:
                callback(@[@"failed"]);
                break;
            default:
                callback(@[@"error"]);
                break;
        }
        [_callbacks removeObjectForKey:key];
    } else {
        RCTLogWarn(@"No callback registered for mail: %@", controller.title);
    }
    UIViewController *ctrl = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (ctrl.presentedViewController && ctrl != controller) {
        ctrl = ctrl.presentedViewController;
    }
    [ctrl dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark Private

static NSString *RCTKeyForInstance(id instance)
{
    return [NSString stringWithFormat:@"%p", instance];
}

@end

iOS version: 12.1.2

I want to know is there any recent changes on MFMailComposeViewController and it causing this?

I have mail account configured and it's working fine. So it's not a duplicate of any issues already posted

Jothi Kannan
  • 3,320
  • 6
  • 40
  • 77
  • 3
    Possible duplicate of [When will \[MFMailComposeViewController canSendMail\] return NO](https://stackoverflow.com/questions/2481029/when-will-mfmailcomposeviewcontroller-cansendmail-return-no) – mag_zbc Jan 24 '19 at 15:36
  • See I have a mail account configured and it's working fine, so it might not be duplicate. – Jothi Kannan Jan 24 '19 at 15:46
  • The answers also say about the account being _enabled_, configured for sending mail, and having a MDM profile configured to not allow third party apps to send mail. – mag_zbc Jan 24 '19 at 15:50
  • Nope I don't have any MDM profile configured on my application. – Jothi Kannan Jan 24 '19 at 15:54

1 Answers1

5

MFMailComposeViewController is directly linked to Apple's mail app. It will only ever return true when Apple's mail app is installed and configured.

If you want to open Gmail (or whatever default app you have installed) then you need to use the mailto:// scheme

Here is my function to do the mailto part - including the absurdly simplistic conversion from html to plain text!

Note that mailto:// doesn't support attachments or html contents.

/// Send email using mailto
/// - Parameters:
///   - to: array of email addresses
///   - subject: subject
///   - body: body
///   - isHtml: isHtml (only <br/> and <br /> are supported)
/// - Returns: true if sent
    func sendByURL(to:[String],subject:String,body:String, isHtml:Bool) -> Bool {
        
        
        var txtBody = body
        if isHtml {
            txtBody = body.replacingOccurrences(of: "<br />", with: "\n")
            txtBody = txtBody.replacingOccurrences(of: "<br/>", with: "\n")
            if txtBody.contains("/>") {
                print("Can't send html email with url interface")
                return false
            }
        }

        let toJoined = to.joined(separator: ",")
        guard var feedbackUrl = URLComponents.init(string: "mailto:\(toJoined)") else {
            return false
        }
                 

        var queryItems: [URLQueryItem] = []
        queryItems.append(URLQueryItem.init(name: "SUBJECT", value: subject))
        queryItems.append(URLQueryItem.init(name: "BODY",
                                            value: txtBody))
        feedbackUrl.queryItems = queryItems
        
        if let url = feedbackUrl.url {
//            This is probably an unnecessary check
//            You do need to add 'mailto' to your LSApplicationQuerySchemes for the check to be allowed
//            <key>LSApplicationQueriesSchemes</key>
//            <array>
//                <string>mailto</string>
//            </array>
            if UIApplication.shared.canOpenURL(url){
                UIApplication.shared.open(url)
                return true
            }
        }
        
        return false
     
    }
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49