1

I use mu4e for email with multiple accounts. I have multiple accounts in mu4e and followed the manual with a function my-mu4e-set-account and a hook to it:

(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-account)

I can start a message with

(compose-mail "To <to@to>" "subject" '(("From" . "me <me@me>")))

I get queried twice about which account to use:

[mu4e] Select context: [A]ccount1 [S]omeOther
Compose with account: (Account1/SomeOther)

and then the "From" of my choice is overwritten by the selection from mu4e.

How can I set the "From" header programmatically without mu4e asking for the context twice? And how can I pre-fill the body of the email, since compose-mail only takes headers in the other-headers argument and the body is not a header?

Update: Following the comments, here is a scenario. I have a list of 20 email addresses to which I want to send a similar email with a custom To: and the name in the body. I would like to send those twenty emails programmatically from mu4e:

  • initiate the email
  • set the right context with the From: address
  • set the subject
  • set the custom recipient
  • set the body, customized to the recipient
  • send the email

At the moment, the interactive prompts interrupt me at step 2 of setting the From: address. The second prompt was my blunder from adding a function from the manual:

(defun my-mu4e-set-account ()
  "Set the account for composing a message.
   This function is taken from: 
     https://www.djcbsoftware.nl/code/mu/mu4e/Multiple-accounts.html"
  (let* ((account
    (if mu4e-compose-parent-message
        (let ((maildir (mu4e-message-field mu4e-compose-parent-message :maildir)))
          (string-match "/\\(.*?\\)/" maildir)
          (match-string 1 maildir))
      (completing-read (format "Compose with account: (%s) "
                               (mapconcat #'(lambda (var) (car var))
                                          my-mu4e-account-alist "/"))
                       (mapcar #'(lambda (var) (car var)) my-mu4e-account-alist)
                       nil t nil nil (caar my-mu4e-account-alist))))
         (account-vars (cdr (assoc account my-mu4e-account-alist))))
    (if account-vars
        (mapc #'(lambda (var)
                  (set (car var) (cadr var)))
              account-vars)
      (error "No email account found"))))
(add-hook 'mu4e-compose-pre-hook 'my-mu4e-set-account)

From the comments, I can set the body with message-goto-body and insert. So the only other question is how to set the right context programmatically. I disabled the context switch hooks and tried this code:

(compose-mail "" "" '(("From" . "me <me@domain.com>")))

and I get a draft from the last used context email address and a body with a mysterious 158 in it.

Second update: I read the Contexts part of the manual and am still unclear on how to set the right context. The variables mu4e-contexts and my-mu4e-account-alist are set to my list of accounts, e.g. Account1 and SomeOther, and I don't know how to tell mu4e to pick the one that matches a string such as SomeOther. I tried

(setq mu4e-compose-pre-hook nil)
(mu4e-context-switch nil "SomeOther")

I compose a new message with C-x m and still get a draft from Account1.

Although the suggestion of auto-answer in a comment could work, I believe a more straightforward solution exists.

miguelmorin
  • 1,751
  • 11
  • 33
  • please describe the scenario a little more to clarify what you're after. For what it seems, using `compose-mail`, I'd say that your troubles will be solved with any of the skeletons to fill the body creating the draft from mu4e. I mean, how you get the To: field? you want to draft only new mail? replies to messages in the inbox?. Also it seems like you don't have a default context, and why mu4e is asking. Once you have the buffer up, `message-goto-body` and `insert` will be enough. – Muihlinn Jul 13 '20 at 11:40
  • Maybe `(let ((mu4e-compose-context-policy 'pick-first)) ...)` or set it to nil. I think there's a bug in the version I'm using so this is only a guess. – jagrg Jul 15 '20 at 00:02
  • @Muihlinn You're right, I could use `message-goto-body` to set the body of the email. That leaves selecting the context programmatically and I added a clarification. – miguelmorin Aug 03 '20 at 16:11
  • @jagrg Using `(setq mu4e-compose-context-policy nil)` skipped the prompt, which shows it's the right approach. How can I set the context programmatically? – miguelmorin Aug 03 '20 at 16:25
  • This is not a solution, but a workaround: you can consider using [auto-answer](https://github.com/YoungFrog/auto-answer/blob/master/auto-answer.el) to deal with the prompt. This [answer](https://emacs.stackexchange.com/a/26745/23697) shows how to do this. In short, bind the variable `auto-answer` to an alist of the form `(prompt . answer)` and the interactive prompt matching the ones in the alist will be answered with the associated to the matched prompt. – Firmin Martin Aug 03 '20 at 20:31
  • I think you should be following [this part](https://www.djcbsoftware.nl/code/mu/mu4e/Contexts.html) of the manual and not one you linked. Either way, you can find the account you want to use from the list of accounts and then set `mu4e-contexts` or `my-mu4e-account-alist` to it locally. Then tell mu4e to pick the first one in the list. – jagrg Aug 03 '20 at 20:33
  • `mu4e-context-switch` should do it. – Muihlinn Aug 04 '20 at 06:44
  • I answered these last three comments in a second update. – miguelmorin Aug 04 '20 at 13:19

1 Answers1

4

Something as simple as this snippet will work, the only pitfall is that mu4e should be loaded before trying to select a context or it'll fail. From here you can "complicate" the thing as much as you need.

The string used to call the context is the :name you used to define it. Also you could get it from mu4e-contexts variable. All the rest is pretty straightforward.

(defun compose-message (context to subject body)
  (let ((mu4e-compose-context-policy nil))
    (mu4e-context-switch t context)
    (compose-mail to subject)
    (message-goto-body)
    (insert body)))

Stopped there because in fact you don't want to "draft" in the sense of storing it into draft mail, just to precompose an email automatically, probably check it out, then send.

jagrg
  • 3,824
  • 4
  • 19
Muihlinn
  • 2,576
  • 1
  • 14
  • 22
  • The first line seems to run successfully: `(mu4e-context-switch t "SomeOther")` in the scratch buffer shows `#s(mu4e-context "SomeOther" nil nil (lambda (msg) (if msg (progn ...))) ((mu4e-trash-folder . "/SomeOther/Deleted Items") (mu4e-refile-folder . "/SomeOther/Deleted Items")))`. Yet, the second line drafts the email from the default account, `Account1`. – miguelmorin Aug 05 '20 at 15:46
  • That is what the function should return in the scratch buffer. What I don't know is why it didn't changed the context, worked as-is with all my 6 accounts. Also, if the context doesn't exists it signals the error, so I'm puzzled. If you switch the context from mu4e `;` and compose does it change and drafts using it?. BTW I tested it with 1.4.10. – Muihlinn Aug 05 '20 at 16:16
  • If I switch the context within `mu4e` with `;`, it shows the new context, yet the new message has a `from:` of the default context. I have set `(setq mu4e-compose-context-policy nil)`. Is it only a matter of changing the `From: ` address? – miguelmorin Aug 05 '20 at 16:39
  • 1
    no, AFAIK it takes all the references from the context, it'll go through, and will be filed, under different mailboxes and sent from the selected context account. As q&d test, save it as draft and look at what draft mailbox is filed. I'd say that there is something wrong somewhere in your config, perhaps the from is the same in both configs and it's fooling you. Context part in the modeline should tell you. – Muihlinn Aug 05 '20 at 18:05
  • Thanks for the debugging help. You're right, this is a problem beyond your code snippet. I asked about it at https://emacs.stackexchange.com/questions/59986/why-is-mu4e-setting-the-context-wrong and will test your solution once I fix that. – miguelmorin Aug 06 '20 at 11:08