I’m fairly new to Python and I’m trying to write a plugin for a text editor.
I want to know if there is a way to launch default system E-Mail client from python code.
Advertisement
Answer
With pywin32:
JavaScript
x
3
1
import win32api
2
win32api.ShellExecute(0,'open','mailto:',None,None ,0)
3
Update
Ah, I misread your question and presumed you’re on Win platform.
A platform independent solution would be open mailto
link in a browser, like
JavaScript
1
3
1
import webbrowser
2
webbrowser.open('mailto:', new=1)
3
Update 2
Some additional research (in fact, first two pages of google search) revealed this excellent snippet:
JavaScript
1
339
339
1
#!/usr/bin/env python
2
3
'''Utilities for opening files or URLs in the registered default application
4
and for sending e-mail using the user's preferred composer.
5
6
'''
7
8
__version__ = '1.1'
9
__all__ = ['open', 'mailto']
10
11
import os
12
import sys
13
import webbrowser
14
import subprocess
15
16
from email.Utils import encode_rfc2231
17
18
_controllers = {}
19
_open = None
20
21
22
class BaseController(object):
23
'''Base class for open program controllers.'''
24
25
def __init__(self, name):
26
self.name = name
27
28
def open(self, filename):
29
raise NotImplementedError
30
31
32
class Controller(BaseController):
33
'''Controller for a generic open program.'''
34
35
def __init__(self, *args):
36
super(Controller, self).__init__(os.path.basename(args[0]))
37
self.args = list(args)
38
39
def _invoke(self, cmdline):
40
if sys.platform[:3] == 'win':
41
closefds = False
42
startupinfo = subprocess.STARTUPINFO()
43
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
44
else:
45
closefds = True
46
startupinfo = None
47
48
if (os.environ.get('DISPLAY') or sys.platform[:3] == 'win' or
49
sys.platform == 'darwin'):
50
inout = file(os.devnull, 'r+')
51
else:
52
# for TTY programs, we need stdin/out
53
inout = None
54
55
# if possible, put the child precess in separate process group,
56
# so keyboard interrupts don't affect child precess as well as
57
# Python
58
setsid = getattr(os, 'setsid', None)
59
if not setsid:
60
setsid = getattr(os, 'setpgrp', None)
61
62
pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout,
63
stderr=inout, close_fds=closefds,
64
preexec_fn=setsid, startupinfo=startupinfo)
65
66
# It is assumed that this kind of tools (gnome-open, kfmclient,
67
# exo-open, xdg-open and open for OSX) immediately exit after lauching
68
# the specific application
69
returncode = pipe.wait()
70
if hasattr(self, 'fixreturncode'):
71
returncode = self.fixreturncode(returncode)
72
return not returncode
73
74
def open(self, filename):
75
if isinstance(filename, basestring):
76
cmdline = self.args + [filename]
77
else:
78
# assume it is a sequence
79
cmdline = self.args + filename
80
try:
81
return self._invoke(cmdline)
82
except OSError:
83
return False
84
85
86
# Platform support for Windows
87
if sys.platform[:3] == 'win':
88
89
class Start(BaseController):
90
'''Controller for the win32 start progam through os.startfile.'''
91
92
def open(self, filename):
93
try:
94
os.startfile(filename)
95
except WindowsError:
96
# [Error 22] No application is associated with the specified
97
# file for this operation: '<URL>'
98
return False
99
else:
100
return True
101
102
_controllers['windows-default'] = Start('start')
103
_open = _controllers['windows-default'].open
104
105
106
# Platform support for MacOS
107
elif sys.platform == 'darwin':
108
_controllers['open']= Controller('open')
109
_open = _controllers['open'].open
110
111
112
# Platform support for Unix
113
else:
114
115
import commands
116
117
# @WARNING: use the private API of the webbrowser module
118
from webbrowser import _iscommand
119
120
class KfmClient(Controller):
121
'''Controller for the KDE kfmclient program.'''
122
123
def __init__(self, kfmclient='kfmclient'):
124
super(KfmClient, self).__init__(kfmclient, 'exec')
125
self.kde_version = self.detect_kde_version()
126
127
def detect_kde_version(self):
128
kde_version = None
129
try:
130
info = commands.getoutput('kde-config --version')
131
132
for line in info.splitlines():
133
if line.startswith('KDE'):
134
kde_version = line.split(':')[-1].strip()
135
break
136
except (OSError, RuntimeError):
137
pass
138
139
return kde_version
140
141
def fixreturncode(self, returncode):
142
if returncode is not None and self.kde_version > '3.5.4':
143
return returncode
144
else:
145
return os.EX_OK
146
147
def detect_desktop_environment():
148
'''Checks for known desktop environments
149
150
Return the desktop environments name, lowercase (kde, gnome, xfce)
151
or "generic"
152
153
'''
154
155
desktop_environment = 'generic'
156
157
if os.environ.get('KDE_FULL_SESSION') == 'true':
158
desktop_environment = 'kde'
159
elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
160
desktop_environment = 'gnome'
161
else:
162
try:
163
info = commands.getoutput('xprop -root _DT_SAVE_MODE')
164
if ' = "xfce4"' in info:
165
desktop_environment = 'xfce'
166
except (OSError, RuntimeError):
167
pass
168
169
return desktop_environment
170
171
172
def register_X_controllers():
173
if _iscommand('kfmclient'):
174
_controllers['kde-open'] = KfmClient()
175
176
for command in ('gnome-open', 'exo-open', 'xdg-open'):
177
if _iscommand(command):
178
_controllers[command] = Controller(command)
179
180
def get():
181
controllers_map = {
182
'gnome': 'gnome-open',
183
'kde': 'kde-open',
184
'xfce': 'exo-open',
185
}
186
187
desktop_environment = detect_desktop_environment()
188
189
try:
190
controller_name = controllers_map[desktop_environment]
191
return _controllers[controller_name].open
192
193
except KeyError:
194
if _controllers.has_key('xdg-open'):
195
return _controllers['xdg-open'].open
196
else:
197
return webbrowser.open
198
199
200
if os.environ.get("DISPLAY"):
201
register_X_controllers()
202
_open = get()
203
204
205
def open(filename):
206
'''Open a file or an URL in the registered default application.'''
207
208
return _open(filename)
209
210
211
def _fix_addersses(**kwargs):
212
for headername in ('address', 'to', 'cc', 'bcc'):
213
try:
214
headervalue = kwargs[headername]
215
if not headervalue:
216
del kwargs[headername]
217
continue
218
elif not isinstance(headervalue, basestring):
219
# assume it is a sequence
220
headervalue = ','.join(headervalue)
221
222
except KeyError:
223
pass
224
except TypeError:
225
raise TypeError('string or sequence expected for "%s", '
226
'%s found' % (headername,
227
type(headervalue).__name__))
228
else:
229
translation_map = {'%': '%25', '&': '%26', '?': '%3F'}
230
for char, replacement in translation_map.items():
231
headervalue = headervalue.replace(char, replacement)
232
kwargs[headername] = headervalue
233
234
return kwargs
235
236
237
def mailto_format(**kwargs):
238
# @TODO: implement utf8 option
239
240
kwargs = _fix_addersses(**kwargs)
241
parts = []
242
for headername in ('to', 'cc', 'bcc', 'subject', 'body', 'attach'):
243
if kwargs.has_key(headername):
244
headervalue = kwargs[headername]
245
if not headervalue:
246
continue
247
if headername in ('address', 'to', 'cc', 'bcc'):
248
parts.append('%s=%s' % (headername, headervalue))
249
else:
250
headervalue = encode_rfc2231(headervalue) # @TODO: check
251
parts.append('%s=%s' % (headername, headervalue))
252
253
mailto_string = 'mailto:%s' % kwargs.get('address', '')
254
if parts:
255
mailto_string = '%s?%s' % (mailto_string, '&'.join(parts))
256
257
return mailto_string
258
259
260
def mailto(address, to=None, cc=None, bcc=None, subject=None, body=None,
261
attach=None):
262
'''Send an e-mail using the user's preferred composer.
263
264
Open the user's preferred e-mail composer in order to send a mail to
265
address(es) that must follow the syntax of RFC822. Multiple addresses
266
may be provided (for address, cc and bcc parameters) as separate
267
arguments.
268
269
All parameters provided are used to prefill corresponding fields in
270
the user's e-mail composer. The user will have the opportunity to
271
change any of this information before actually sending the e-mail.
272
273
address - specify the destination recipient
274
cc - specify a recipient to be copied on the e-mail
275
bcc - specify a recipient to be blindly copied on the e-mail
276
subject - specify a subject for the e-mail
277
body - specify a body for the e-mail. Since the user will be able
278
to make changes before actually sending the e-mail, this
279
can be used to provide the user with a template for the
280
e-mail text may contain linebreaks
281
attach - specify an attachment for the e-mail. file must point to
282
an existing file
283
284
'''
285
286
mailto_string = mailto_format(**locals())
287
return open(mailto_string)
288
289
290
if __name__ == '__main__':
291
from optparse import OptionParser
292
293
version = '%%prog %s' % __version__
294
usage = (
295
'nn%prog FILENAME [FILENAME(s)] -- for opening files'
296
'nn%prog -m [OPTIONS] ADDRESS [ADDRESS(es)] -- for sending e-mails'
297
)
298
299
parser = OptionParser(usage=usage, version=version, description=__doc__)
300
parser.add_option('-m', '--mailto', dest='mailto_mode', default=False,
301
action='store_true', help='set mailto mode. '
302
'If not set any other option is ignored')
303
parser.add_option('--cc', dest='cc', help='specify a recipient to be '
304
'copied on the e-mail')
305
parser.add_option('--bcc', dest='bcc', help='specify a recipient to be '
306
'blindly copied on the e-mail')
307
parser.add_option('--subject', dest='subject',
308
help='specify a subject for the e-mail')
309
parser.add_option('--body', dest='body', help='specify a body for the '
310
'e-mail. Since the user will be able to make changes '
311
'before actually sending the e-mail, this can be used '
312
'to provide the user with a template for the e-mail '
313
'text may contain linebreaks')
314
parser.add_option('--attach', dest='attach', help='specify an attachment '
315
'for the e-mail. file must point to an existing file')
316
317
(options, args) = parser.parse_args()
318
319
if not args:
320
parser.print_usage()
321
parser.exit(1)
322
323
if options.mailto_mode:
324
if not mailto(args, None, options.cc, options.bcc, options.subject,
325
options.body, options.attach):
326
sys.exit('Unable to open the e-mail client')
327
else:
328
for name in ('cc', 'bcc', 'subject', 'body', 'attach'):
329
if getattr(options, name):
330
parser.error('The "cc", "bcc", "subject", "body" and "attach" '
331
'options are only accepten in mailto mode')
332
success = False
333
for arg in args:
334
if not open(arg):
335
print 'Unable to open "%s"' % arg
336
else:
337
success = True
338
sys.exit(success)
339
Enjoy.