diff --git a/CHANGES.rst b/CHANGES.rst
index 3d3de1a..685b9d4 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,6 +23,11 @@
 
 - Reach 100% test coverage and maintain it via tox.ini and Travis CI.
 
+- Replaced deprecated dependency on ``optparse`` with equivalent
+  ``argparse``. The help messages have changed and errors are
+  generally more clear. Specifying a ``--config`` path that doesn't
+  exist is now an error instead of being silently ignored.
+
 4.0.1 (2014-12-29)
 ==================
 
diff --git a/src/zope/sendmail/queue.py b/src/zope/sendmail/queue.py
index ec667d6..7296999 100644
--- a/src/zope/sendmail/queue.py
+++ b/src/zope/sendmail/queue.py
@@ -19,7 +19,7 @@ __docformat__ = 'restructuredtext'
 
 import atexit
 import logging
-import optparse
+import argparse
 import os
 import smtplib
 import stat
@@ -363,39 +363,56 @@ class ConsoleApp(object):
         "queue_path",
     ]
 
-    parser = optparse.OptionParser(usage='%prog [OPTIONS] path/to/maildir')
-    parser.add_option('--daemon', action='store_true',
-                      help="Run in daemon mode, periodically checking queue "
-                           "and sending messages.  Default is to send all "
-                           "messages in queue once and exit.")
-    parser.add_option('--interval', metavar='<#secs>', type='float', default=3,
-                      help="How often to check queue when in daemon mode. "
-                           "Default is %default seconds.")
-    parser.add_option('--hostname', default='localhost',
-                      help="Name of smtp host to use for delivery.  Default is "
-                           "%default.")
-    parser.add_option('--port', type='int', default=25,
-                      help="Which port on smtp server to deliver mail to. "
-                           "Default is %default.")
-    parser.add_option('--username',
-                      help="Username to use to log in to smtp server.  Default "
-                           "is none.")
-    parser.add_option('--password',
-                      help="Password to use to log in to smtp server.  Must be "
-                           "specified if username is specified.")
-    parser.add_option('--force-tls', action='store_true',
-                      help="Do not connect if TLS is not available.  Not "
-                           "enabled by default.")
-    parser.add_option('--no-tls', action='store_true',
-                      help="Do not use TLS even if is available.  Not enabled "
-                           "by default.")
-    parser.add_option('--config', metavar='<inifile>',
-                      help="Get configuration from specified ini file; it must "
-                           "contain a section [%s] that can contain the "
-                           "following keys: %s. If you specify the queue path "
-                           "in the ini file, you don't need to specify it on "
-                           "the command line." % (INI_SECTION,
-                                                  ', '.join(INI_NAMES)))
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--daemon', action='store_true',
+                        help=("Run in daemon mode, periodically checking queue "
+                              "and sending messages.  Default is to send all "
+                              "messages in queue once and exit."))
+    parser.add_argument('--interval', metavar='<#secs>', type=float, default=3,
+                        help=("How often to check queue when in daemon mode. "
+                              "Default is %(default)s seconds."))
+    smtp_group = parser.add_argument_group("SMTP Server",
+                                           "Connection information for the SMTP server")
+    smtp_group.add_argument('--hostname', default='localhost',
+                            help=("Name of SMTP host to use for delivery.  Default is "
+                                  "%(default)s."))
+    smtp_group.add_argument('--port', type=int, default=25,
+                            help=("Which port on SMTP server to deliver mail to. "
+                                  "Default is %(default)s."))
+
+    auth_group = parser.add_argument_group("Authentication",
+                                           ("Authentication information for the SMTP server. "
+                                            "If one is provided, they must both be. One or both "
+                                            "can be provided in the --config file."))
+    auth_group.add_argument('--username',
+                            help=("Username to use to log in to SMTP server.  Default "
+                                  "is none."))
+    auth_group.add_argument('--password',
+                            help=("Password to use to log in to SMTP server.  Must be "
+                                  "specified if username is specified."))
+    del auth_group
+    tls_group = smtp_group.add_mutually_exclusive_group()
+    tls_group.add_argument('--force-tls', action='store_true',
+                           help=("Do not connect if TLS is not available.  Not "
+                                 "enabled by default."))
+    tls_group.add_argument('--no-tls', action='store_true',
+                           help=("Do not use TLS even if is available.  Not enabled "
+                                 "by default."))
+    del tls_group
+    del smtp_group
+    parser.add_argument('--config', metavar='<inifile>',
+                        type=argparse.FileType(),
+                        help=("Get configuration from specified ini file; it must "
+                              "contain a section [%s] that can contain the "
+                              "following keys: %s. If you specify the queue path "
+                              "in the ini file, you don't need to specify it on "
+                              "the command line. With the exception of the queue path, "
+                              "options specified in the ini file override options on the "
+                              "command line." % (INI_SECTION, ', '.join(INI_NAMES))))
+    parser.add_argument("maildir", default=None, nargs="?",
+                        help=("The path to the mail queue directory."
+                              "If not given, it must be found in the --config file."
+                              "If given, this overrides a value in the --config file"))
 
     daemon = False
     interval = 3
@@ -425,7 +442,7 @@ class ConsoleApp(object):
         queue.run(forever=self.daemon)
 
     def _process_args(self, args):
-        opts, args = self.parser.parse_args(args)
+        opts = self.parser.parse_args(args)
         self.daemon = opts.daemon
         self.interval = opts.interval
         self.hostname = opts.hostname
@@ -434,19 +451,18 @@ class ConsoleApp(object):
         self.password = opts.password
         self.force_tls = opts.force_tls
         self.no_tls = opts.no_tls
+
         if opts.config:
-            self._load_config(opts.config)
-        if len(args) > 1:
-            self.parser.error('too many arguments')
-        elif args:
-            self.queue_path = args[0]
+            fname = opts.config.name
+            opts.config.close()
+            self._load_config(fname)
+        self.queue_path = opts.maildir or self.queue_path
+
         if not self.queue_path:
             self.parser.error('please specify the queue path')
         if (self.username or self.password) and \
            not (self.username and self.password):
             self.parser.error('Must use username and password together.')
-        if self.force_tls and self.no_tls:
-            self.parser.error('--force-tls and --no-tls are mutually exclusive.')
 
     def _load_config(self, path):
         section = self.INI_SECTION

Port to argparse
We're still using the deprecated optparse. This will be easier after #7 
diff --git a/src/zope/sendmail/tests/test_queue.py b/src/zope/sendmail/tests/test_queue.py
index ca00337..9baac58 100644
--- a/src/zope/sendmail/tests/test_queue.py
+++ b/src/zope/sendmail/tests/test_queue.py
@@ -376,17 +376,26 @@ class TestConsoleApp(unittest.TestCase):
         self.delivery = QueuedMailDelivery(self.queue_dir)
         self.maildir = Maildir(self.queue_dir, True)
         self.mailer = MailerStub()
-        self.real_stderr = sys.stderr
-        self.stderr = io.StringIO() if bytes is not str else io.BytesIO()
+        io_type = io.StringIO if bytes is not str else io.BytesIO
+        self.stderr = io_type()
+        self.stdout = io_type()
 
     def tearDown(self):
-        sys.stderr = self.real_stderr
         shutil.rmtree(self.dir)
 
+    def _make_one(self, cmdline):
+        cmdline = cmdline.split() if isinstance(cmdline, str) else cmdline
+        with patched(sys, 'stdout', self.stdout), \
+             patched(sys, 'stderr', self.stderr):
+            return ConsoleApp(cmdline, verbose=False)
+
+    def _get_output(self):
+        return self.stderr.getvalue() + self.stdout.getvalue()
+
     def test_args_processing(self):
         # simplest case that works
         cmdline = "zope-sendmail %s" % self.dir
-        app = ConsoleApp(cmdline.split(), verbose=False)
+        app = self._make_one(cmdline)
         self.assertEqual("zope-sendmail", app.script_name)
         self.assertEqual(self.dir, app.queue_path)
         self.assertFalse(app.daemon)
@@ -401,15 +410,18 @@ class TestConsoleApp(unittest.TestCase):
     def test_args_processing_no_queue_path(self):
         # simplest case that doesn't work: no queue path specified
         cmdline = "zope-sendmail"
-        sys.stderr = self.stderr
-        self.assertRaises(SystemExit, ConsoleApp, cmdline.split(), verbose=False)
+
+        with self.assertRaises(SystemExit):
+            self._make_one(cmdline)
+
+        self.assertIn('please specify the queue path', self._get_output())
 
     def test_args_processing_almost_all_options(self):
         # use (almost) all of the options
         cmdline = "zope-sendmail --daemon --interval 7 --hostname foo --port 75 " \
             "--username chris --password rossi --force-tls " \
             "%s" % self.dir
-        app = ConsoleApp(cmdline.split(), verbose=False)
+        app = self._make_one(cmdline)
         self.assertEqual("zope-sendmail", app.script_name)
         self.assertEqual(self.dir, app.queue_path)
         self.assertTrue(app.daemon)
@@ -424,21 +436,28 @@ class TestConsoleApp(unittest.TestCase):
         # Add an extra argument
         cmdline += ' another-one'
         with self.assertRaises(SystemExit):
-            ConsoleApp(cmdline.split(), verbose=False)
+            self._make_one(cmdline)
 
-        # self.assertIn('too many arguments', self.stderr.getvalue())
+        self.assertIn('unrecognized argument', self._get_output())
 
     def test_args_processing_username_without_password(self):
         # test username without password
         cmdline = "zope-sendmail --username chris %s" % self.dir
-        sys.stderr = self.stderr
-        self.assertRaises(SystemExit, ConsoleApp, cmdline.split(), verbose=False)
+
+        with self.assertRaises(SystemExit):
+            self._make_one(cmdline)
+
+        self.assertIn('Must use username and password together', self._get_output())
+
 
     def test_args_processing_force_tls_and_no_tls(self):
         # test force_tls and no_tls
         cmdline = "zope-sendmail --force-tls --no-tls %s" % self.dir
-        sys.stderr = self.stderr
-        self.assertRaises(SystemExit, ConsoleApp, cmdline.split(), verbose=False)
+
+        with self.assertRaises(SystemExit):
+            self._make_one(cmdline)
+
+        self.assertIn('--no-tls: not allowed with argument', self._get_output())
 
     def test_ini_parse(self):
         ini_path = os.path.join(self.dir, "zope-sendmail.ini")
@@ -473,6 +492,14 @@ class TestConsoleApp(unittest.TestCase):
         self.assertFalse(app.force_tls)
         self.assertFalse(app.no_tls)
 
+    def test_ini_not_exist(self):
+        cmdline = ['sendmail', '--config', 'this path cannot be opened']
+        with self.assertRaises(SystemExit) as exc:
+            self._make_one(cmdline)
+
+        self.assertIn('No such file or directory', self._get_output())
+        self.assertEqual(exc.exception.code, 2)
+
     def test_run(self):
         cmdline = ['sendmail', self.dir]
 
@@ -481,5 +508,14 @@ class TestConsoleApp(unittest.TestCase):
              patched(ConsoleApp, 'MailerKind', MailerStub):
             queue.run(cmdline)
 
+    def test_help(self):
+        cmdline = ['prog', '--help']
+
+        with self.assertRaises(SystemExit) as exc:
+            self._make_one(cmdline)
+
+        self.assertIn('usage', self._get_output())
+        self.assertEqual(exc.exception.code, 0)
+
 def test_suite():
     return unittest.defaultTestLoader.loadTestsFromName(__name__)

pip install -e .[test]
parse_log_pytest
requirements.txt
pytest
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
six==1.17.0
tomli==1.2.3
transaction==3.1.0
typing_extensions==4.1.1
zipp==3.6.0
zope.component==5.1.0
zope.configuration==4.4.1
zope.event==4.6
zope.exceptions==4.6
zope.hookable==5.4
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.location==4.3
zope.proxy==4.6.1
zope.schema==6.2.1
zope.security==5.8
-e git+https://github.com/zopefoundation/zope.sendmail.git@6e377f7e9a849bf4b39c416ddc9f7ce010a0785d#egg=zope.sendmail
zope.testing==5.0.1
zope.testrunner==5.6

name: zope.sendmail
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - six==1.17.0
      - tomli==1.2.3
      - transaction==3.1.0
      - typing-extensions==4.1.1
      - zipp==3.6.0
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-location==4.3
      - zope-proxy==4.6.1
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-testing==5.0.1
      - zope-testrunner==5.6
prefix: /opt/conda/envs/zope.sendmail

src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_args_processing_almost_all_options src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_args_processing_force_tls_and_no_tls src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_help src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_ini_not_exist
src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_deliveration src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_error_logging src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_makeMaildir_creates src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_parseMessage src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_parseMessage_error src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_run_forever src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_smtp_recipients_refused src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_smtp_response_error_permanent src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_smtp_response_error_transient src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_stop_while_running src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_threadName src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_tmpfile_cannot_stat src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_tmpfile_exists src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_tmpfile_too_old src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_tmpfile_too_old_unlink_fails_dne src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_tmpfile_too_old_unlink_fails_generic src/zope/sendmail/tests/test_queue.py::TestQueueProcessorThread::test_utime_fails_dne src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_args_processing src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_args_processing_no_queue_path src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_args_processing_username_without_password src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_ini_parse src/zope/sendmail/tests/test_queue.py::TestConsoleApp::test_run src/zope/sendmail/tests/test_queue.py::test_suite
CHANGES.rst src/zope/sendmail/queue.py
CHANGES.rst src/zope/sendmail/queue.py