diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7088252e8d..51e7053193 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1313,6 +1313,7 @@
     <string name="CallNotificationBuilder_connecting">Connecting...</string>
     <string name="Permissions_permission_required">Permission required</string>
     <string name="Permissions_continue">Continue</string>
+    <string name="ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms">Signal needs SMS permission in order to send an SMS, but it has been permanently denied. Please continue to app settings, select \"Permissions\" and enable \"SMS\".</string>
 
 
     <!-- EOF -->
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index afcc0d358a..259b10f653 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -112,7 +112,6 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
 import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
 import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
 import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
-import org.thoughtcrime.securesms.database.SmsDatabase;
 import org.thoughtcrime.securesms.database.ThreadDatabase;
 import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
 import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
@@ -1663,48 +1662,49 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), expiresIn, subscriptionId, initiating);
   }
 
-  private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating)
-      throws InvalidMessageException
-  {
-    final SettableFuture<Void> future          = new SettableFuture<>();
-    final Context              context         = getApplicationContext();
-          OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(recipient,
-                                                                          slideDeck,
-                                                                          body,
-                                                                          System.currentTimeMillis(),
-                                                                          subscriptionId,
-                                                                          expiresIn,
-                                                                          distributionType);
+  private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final long expiresIn, final int subscriptionId, final boolean initiating) {
 
-    if (isSecureText && !forceSms) {
-      outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
-    }
+    OutgoingMediaMessage outgoingMessageCandidate = new OutgoingMediaMessage(recipient, slideDeck, body, System.currentTimeMillis(), subscriptionId, expiresIn, distributionType);
 
-    attachmentManager.clear(glideRequests, false);
-    composeText.setText("");
-    final long id = fragment.stageOutgoingMessage(outgoingMessage);
+    final SettableFuture<Void> future  = new SettableFuture<>();
+    final Context              context = getApplicationContext();
 
-    new AsyncTask<OutgoingMediaMessage, Void, Long>() {
-      @Override
-      protected Long doInBackground(OutgoingMediaMessage... messages) {
-        if (initiating) {
-          DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
-        }
+    final OutgoingMediaMessage outgoingMessage;
 
-        return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() {
-          @Override
-          public void onComplete() {
-            fragment.releaseOutgoingMessage(id);
-          }
-        });
-      }
+    if (isSecureText && !forceSms) {
+      outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate);
+    } else {
+      outgoingMessage = outgoingMessageCandidate;
+    }
 
-      @Override
-      protected void onPostExecute(Long result) {
-        sendComplete(result);
-        future.set(null);
-      }
-    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, outgoingMessage);
+    Permissions.with(this)
+               .request(Manifest.permission.SEND_SMS)
+               .ifNecessary(isSecureText || forceSms)
+               .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
+               .onAllGranted(() -> {
+                 attachmentManager.clear(glideRequests, false);
+                 composeText.setText("");
+                 final long id = fragment.stageOutgoingMessage(outgoingMessage);
+
+                 new AsyncTask<Void, Void, Long>() {
+                   @Override
+                   protected Long doInBackground(Void... param) {
+                     if (initiating) {
+                       DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
+                     }
+
+                     return MessageSender.send(context, masterSecret, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
+                   }
+
+                   @Override
+                   protected void onPostExecute(Long result) {
+                     sendComplete(result);
+                     future.set(null);
+                   }
+                 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+               })
+               .onAnyDenied(() -> future.set(null))
+               .execute();
 
     return future;
   }
@@ -1712,38 +1712,43 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
   private void sendTextMessage(final boolean forceSms, final long expiresIn, final int subscriptionId, final boolean initiatingConversation)
       throws InvalidMessageException
   {
-    final Context context = getApplicationContext();
+    final Context context     = getApplicationContext();
+    final String  messageBody = getMessage();
+
     OutgoingTextMessage message;
 
     if (isSecureText && !forceSms) {
-      message = new OutgoingEncryptedMessage(recipient, getMessage(), expiresIn);
+      message = new OutgoingEncryptedMessage(recipient, messageBody, expiresIn);
     } else {
-      message = new OutgoingTextMessage(recipient, getMessage(), expiresIn, subscriptionId);
+      message = new OutgoingTextMessage(recipient, messageBody, expiresIn, subscriptionId);
     }
 
-    this.composeText.setText("");
-    final long id = fragment.stageOutgoingMessage(message);
-
-    new AsyncTask<OutgoingTextMessage, Void, Long>() {
-      @Override
-      protected Long doInBackground(OutgoingTextMessage... messages) {
-        if (initiatingConversation) {
-          DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
-        }
-
-        return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, new SmsDatabase.InsertListener() {
-          @Override
-          public void onComplete() {
-            fragment.releaseOutgoingMessage(id);
-          }
-        });
-      }
-
-      @Override
-      protected void onPostExecute(Long result) {
-        sendComplete(result);
-      }
-    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
+    Permissions.with(this)
+               .request(Manifest.permission.SEND_SMS)
+               .ifNecessary(forceSms || !isSecureText)
+               .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_sms_permission_in_order_to_send_an_sms))
+               .onAllGranted(() -> {
+                 this.composeText.setText("");
+                 final long id = fragment.stageOutgoingMessage(message);
+
+                 new AsyncTask<OutgoingTextMessage, Void, Long>() {
+                   @Override
+                   protected Long doInBackground(OutgoingTextMessage... messages) {
+                     if (initiatingConversation) {
+                       DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true);
+                     }
+
+                     return MessageSender.send(context, masterSecret, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
+                   }
+
+                   @Override
+                   protected void onPostExecute(Long result) {
+                     sendComplete(result);
+                   }
+                 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message);
+
+               })
+               .execute();
   }
 
   private void updateToggleButtonState() {
@@ -1830,31 +1835,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
     future.addListener(new ListenableFuture.Listener<Pair<Uri, Long>>() {
       @Override
       public void onSuccess(final @NonNull Pair<Uri, Long> result) {
-        try {
-          boolean    forceSms       = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
-          int        subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
-          long       expiresIn      = recipient.getExpireMessages() * 1000;
-          boolean    initiating     = threadId == -1;
-          AudioSlide audioSlide     = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true);
-          SlideDeck  slideDeck      = new SlideDeck();
-          slideDeck.addSlide(audioSlide);
-
-          sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener<Void>() {
-            @Override
-            public void onSuccess(Void nothing) {
-              new AsyncTask<Void, Void, Void>() {
-                @Override
-                protected Void doInBackground(Void... params) {
-                  PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
-                  return null;
-                }
-              }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-            }
-          });
-        } catch (InvalidMessageException e) {
-          Log.w(TAG, e);
-          Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_error_sending_voice_message, Toast.LENGTH_LONG).show();
-        }
+        boolean    forceSms       = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
+        int        subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
+        long       expiresIn      = recipient.getExpireMessages() * 1000;
+        boolean    initiating     = threadId == -1;
+        AudioSlide audioSlide     = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaUtil.AUDIO_AAC, true);
+        SlideDeck  slideDeck      = new SlideDeck();
+        slideDeck.addSlide(audioSlide);
+
+        sendMediaMessage(forceSms, "", slideDeck, expiresIn, subscriptionId, initiating).addListener(new AssertedSuccessListener<Void>() {
+          @Override
+          public void onSuccess(Void nothing) {
+            new AsyncTask<Void, Void, Void>() {
+              @Override
+              protected Void doInBackground(Void... params) {
+                PersistentBlobProvider.getInstance(ConversationActivity.this).delete(result.first);
+                return null;
+              }
+            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+          }
+        });
       }
 
       @Override
diff --git a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
index 45f2b25578..7d2966f493 100644
--- a/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/SmsSendJob.java
@@ -118,6 +118,9 @@ public class SmsSendJob extends SendJob {
         Log.w(TAG, npe);
         throw new UndeliverableMessageException(npe2);
       }
+    } catch (SecurityException se) {
+      Log.w(TAG, se);
+      throw new UndeliverableMessageException(se);
     }
   }
 
diff --git a/src/org/thoughtcrime/securesms/permissions/Permissions.java b/src/org/thoughtcrime/securesms/permissions/Permissions.java
index 127a4a6e00..3876150776 100644
--- a/src/org/thoughtcrime/securesms/permissions/Permissions.java
+++ b/src/org/thoughtcrime/securesms/permissions/Permissions.java
@@ -64,6 +64,8 @@ public class Permissions {
 
     private boolean ifNecesary;
 
+    private boolean condition = true;
+
     PermissionsBuilder(PermissionObject permissionObject) {
       this.permissionObject = permissionObject;
     }
@@ -78,6 +80,12 @@ public class Permissions {
       return this;
     }
 
+    public PermissionsBuilder ifNecessary(boolean condition) {
+      this.ifNecesary = true;
+      this.condition  = condition;
+      return this;
+    }
+
     public PermissionsBuilder withRationaleDialog(@NonNull String message, @NonNull @DrawableRes int... headers) {
       this.rationalDialogHeader   = headers;
       this.rationaleDialogMessage = message;
@@ -128,7 +136,7 @@ public class Permissions {
       PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener,
                                                           someGrantedListener, someDeniedListener, somePermanentlyDeniedListener);
 
-      if (ifNecesary && permissionObject.hasAll(requestedPermissions)) {
+      if (ifNecesary && (permissionObject.hasAll(requestedPermissions) || !condition)) {
         executePreGrantedPermissionsRequest(request);
       } else if (rationaleDialogMessage != null && rationalDialogHeader != null) {
         executePermissionsRequestWithRationale(request);