]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-smtp: smtp-submit: Added support for asynchronous message submission.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Fri, 5 May 2017 15:06:32 +0000 (17:06 +0200)
committerTimo Sirainen <tss@dovecot.fi>
Fri, 8 Sep 2017 15:16:36 +0000 (18:16 +0300)
src/lib-lda/mail-send.c
src/lib-lda/smtp-client.c
src/lib-smtp/smtp-submit.c
src/lib-smtp/smtp-submit.h
src/lib-smtp/test-smtp-submit.c

index 5527afcefc4db1878e6564ad63beca8dffe488fd..371687b9cb40ce5a0759d18952c83861432c0ca0 100644 (file)
@@ -87,7 +87,7 @@ int mail_send_rejection(struct mail_deliver_context *ctx, const char *recipient,
        smtp_set.submission_host = ctx->set->submission_host;
        smtp_set.sendmail_path = ctx->set->sendmail_path;
 
-       smtp_submit = smtp_submit_init(&smtp_set, NULL);
+       smtp_submit = smtp_submit_init_simple(&smtp_set, NULL);
        smtp_submit_add_rcpt(smtp_submit, return_addr);
        output = smtp_submit_send(smtp_submit);
 
@@ -187,7 +187,7 @@ int mail_send_rejection(struct mail_deliver_context *ctx, const char *recipient,
        str_truncate(str, 0);
        str_printfa(str, "\r\n\r\n--%s--\r\n", boundary);
        o_stream_nsend(output, str_data(str), str_len(str));
-       if ((ret = smtp_submit_deinit_timeout
+       if ((ret = smtp_submit_run_timeout
                (smtp_submit, ctx->timeout_secs, &error)) < 0) {
                i_error("msgid=%s: Temporarily failed to send rejection: %s",
                        orig_msgid == NULL ? "" : str_sanitize(orig_msgid, 80),
@@ -197,5 +197,6 @@ int mail_send_rejection(struct mail_deliver_context *ctx, const char *recipient,
                        orig_msgid == NULL ? "" : str_sanitize(orig_msgid, 80),
                        str_sanitize(error, 512));
        }
+       smtp_submit_deinit(&smtp_submit);
        return ret < 0 ? -1 : 0;
 }
index 6f8d8d7a5fd1207ef0ca7cb5fa42606879161e1d..c8702a60c9f0429c1fb3e8fbbe0ac8914d1df0a8 100644 (file)
@@ -21,7 +21,7 @@ smtp_client_init(const struct lda_settings *set, const char *return_path)
         smtp_set.sendmail_path = set->sendmail_path;
 
        client = i_new(struct smtp_client, 1);
-       client->submit = smtp_submit_init(&smtp_set, return_path);
+       client->submit = smtp_submit_init_simple(&smtp_set, return_path);
        return client;
 }
 
@@ -41,7 +41,7 @@ void smtp_client_abort(struct smtp_client **_client)
 
        *_client = NULL;
 
-       smtp_submit_abort(&client->submit);
+       smtp_submit_deinit(&client->submit);
        i_free(client);
 }
 
@@ -53,9 +53,11 @@ int smtp_client_deinit(struct smtp_client *client, const char **error_r)
 int smtp_client_deinit_timeout(struct smtp_client *client,
                               unsigned int timeout_secs, const char **error_r)
 {
-       int  ret;
+       int ret;
 
-       ret = smtp_submit_deinit_timeout(client->submit, timeout_secs, error_r);
+       ret = smtp_submit_run_timeout(client->submit, timeout_secs, error_r);
+       smtp_submit_deinit(&client->submit);
        i_free(client);
+
        return ret;
 }
index 749c8f08313828887317f1dfa438ab7f8c25fffb..4da0086e3f1b9da4383298ccf0b9740fcc21ccef 100644 (file)
 
 #define DEFAULT_SUBMISSION_PORT 25
 
+struct smtp_submit_session {
+       pool_t pool;
+       struct smtp_submit_settings set;
+};
+
 struct smtp_submit {
        pool_t pool;
+
+       struct smtp_submit_session *session;
+
        struct ostream *output;
        struct istream *input;
 
-       struct smtp_submit_settings set;
        ARRAY_TYPE(const_string) destinations;
        const char *return_path;
+
+       struct timeout *to_error;
+       int status;
        const char *error;
 
-       bool success:1;
-       bool finished:1;
-       bool tempfail:1;
+       struct program_client *prg_client;
+       struct lmtp_client *lmtp_client;
+
+       smtp_submit_callback_t *callback;
+       void *context;
+
+       bool simple:1;
 };
 
+struct smtp_submit_session *
+smtp_submit_session_init(const struct smtp_submit_settings *set)
+{
+       struct smtp_submit_session *session;
+       pool_t pool;
+
+       pool = pool_alloconly_create("smtp submit session", 128);
+       session = p_new(pool, struct smtp_submit_session, 1);
+       session->pool = pool;
+
+       session->set.hostname = p_strdup_empty(pool, set->hostname);
+       session->set.submission_host = p_strdup_empty(pool, set->submission_host);
+       session->set.sendmail_path = p_strdup_empty(pool, set->sendmail_path);
+       return session;
+}
+
+void smtp_submit_session_deinit(struct smtp_submit_session **_session)
+{
+       struct smtp_submit_session *session = *_session;
+
+       *_session = NULL;
+
+       pool_unref(&session->pool);
+}
+
 struct smtp_submit *
-smtp_submit_init(const struct smtp_submit_settings *set, const char *return_path)
+smtp_submit_init(struct smtp_submit_session *session, const char *return_path)
 {
        struct smtp_submit *subm;
        pool_t pool;
 
        pool = pool_alloconly_create("smtp submit", 256);
        subm = p_new(pool, struct smtp_submit, 1);
+       subm->session = session;
        subm->pool = pool;
 
-       subm->set.hostname = p_strdup_empty(pool, set->hostname);
-       subm->set.submission_host = p_strdup_empty(pool, set->submission_host);
-       subm->set.sendmail_path = p_strdup_empty(pool, set->sendmail_path);
-
        subm->return_path = p_strdup(pool, return_path);
        p_array_init(&subm->destinations, pool, 2);
        return subm;
 }
 
+struct smtp_submit *
+smtp_submit_init_simple(const struct smtp_submit_settings *set,
+       const char *return_path)
+{
+       struct smtp_submit_session *session;
+       struct smtp_submit *subm;
+
+       session = smtp_submit_session_init(set);
+       subm = smtp_submit_init(session, return_path);
+       subm->simple = TRUE;
+       return subm;
+}
+
+void smtp_submit_deinit(struct smtp_submit **_subm)
+{
+       struct smtp_submit *subm = *_subm;
+
+       *_subm = NULL;
+
+       if (subm->output != NULL) {
+               o_stream_ignore_last_errors(subm->output);
+               o_stream_destroy(&subm->output);
+       }
+       if (subm->input != NULL)
+               i_stream_destroy(&subm->input);
+
+       if (subm->prg_client != NULL)
+               program_client_destroy(&subm->prg_client);
+       if (subm->lmtp_client != NULL)
+               lmtp_client_deinit(&subm->lmtp_client);
+
+       if (subm->to_error != NULL)
+               timeout_remove(&subm->to_error);
+
+       if (subm->simple)
+                smtp_submit_session_deinit(&subm->session);
+       pool_unref(&subm->pool);
+}
+
 void smtp_submit_add_rcpt(struct smtp_submit *subm, const char *address)
 {
        i_assert(subm->output == NULL);
@@ -73,86 +148,132 @@ struct ostream *smtp_submit_send(struct smtp_submit *subm)
        return subm->output;
 }
 
-static void smtp_submit_send_finished(void *context)
+static void
+smtp_submit_callback(struct smtp_submit *subm, int status,
+       const char *error)
 {
-       struct smtp_submit *smtp_submit = context;
+       struct smtp_submit_result result;
+       smtp_submit_callback_t *callback;
 
-       smtp_submit->finished = TRUE;
-       io_loop_stop(current_ioloop);
+       if (subm->to_error != NULL)
+               timeout_remove(&subm->to_error);
+
+       i_zero(&result);
+       result.status = status;
+       result.error = error;
+
+       callback = subm->callback;
+       subm->callback = NULL;
+       callback(&result, subm->context);
+}
+
+static void
+smtp_submit_delayed_error_callback(struct smtp_submit *subm)
+{
+       smtp_submit_callback(subm, -1, subm->error);
+}
+
+static void
+smtp_submit_delayed_error(struct smtp_submit *subm,
+       const char *error)
+{
+       subm->status = -1;
+       subm->error = p_strdup(subm->pool, error);
+       subm->to_error = timeout_add_short(0,
+               smtp_submit_delayed_error_callback, subm);
 }
 
 static void
 smtp_submit_error(struct smtp_submit *subm,
-                bool tempfail, const char *error)
+       int status, const char *error)
 {
-       if (subm->error == NULL) {
-               subm->tempfail = tempfail;
-               subm->error = p_strdup_printf(subm->pool,
-                       "smtp(%s): %s",
-                       subm->set.submission_host, error);
-       }
+       const struct smtp_submit_settings *set = &subm->session->set;
+       i_assert(status <= 0);
+       if (subm->error != NULL)
+               return;
+
+       subm->status = status;
+       subm->error = p_strdup_printf(subm->pool,
+               "smtp(%s): %s",
+               set->submission_host, error);
+}
+
+static void
+smtp_submit_success(struct smtp_submit *subm)
+{
+       if (subm->error != NULL)
+               return;
+       subm->status = 1;
+}
+
+static void
+smtp_submit_send_host_finished(void *context)
+{
+       struct smtp_submit *subm = (struct smtp_submit *)context;
+
+       i_assert(subm->status > 0 || subm->error != NULL);
+       smtp_submit_callback(subm, subm->status, subm->error);
 }
 
 static void
 rcpt_to_callback(enum lmtp_client_result result, const char *reply, void *context)
 {
-       struct smtp_submit *subm = context;
+       struct smtp_submit *subm = (struct smtp_submit *)context;
 
        if (result != LMTP_CLIENT_RESULT_OK) {
-               smtp_submit_error(subm, (reply[0] != '5'),
+               smtp_submit_error(subm,
+                       (reply[0] != '5' ? -1 : 0),
                        t_strdup_printf("RCPT TO failed: %s", reply));
-               smtp_submit_send_finished(subm);
        }
 }
 
 static void
 data_callback(enum lmtp_client_result result, const char *reply, void *context)
 {
-       struct smtp_submit *subm = context;
+       struct smtp_submit *subm = (struct smtp_submit *)context;
 
        if (result != LMTP_CLIENT_RESULT_OK) {
-               smtp_submit_error(subm, (reply[0] != '5'),
+               smtp_submit_error(subm,
+                       (reply[0] != '5' ? -1 : 0),
                        t_strdup_printf("DATA failed: %s", reply));
-               smtp_submit_send_finished(subm);
-       } else {
-               subm->success = TRUE;
+               return;
        }
+
+       smtp_submit_success(subm);
 }
 
-static int
+static void
 smtp_submit_send_host(struct smtp_submit *subm,
-                      unsigned int timeout_secs, const char **error_r)
+                      unsigned int timeout_secs)
 {
+       const struct smtp_submit_settings *set = &subm->session->set;
        struct lmtp_client_settings client_set;
        struct lmtp_client *lmtp_client;
-       struct ioloop *ioloop;
        const char *host, *const *destp;
        in_port_t port;
 
-       if (net_str2hostport(subm->set.submission_host,
-                            DEFAULT_SUBMISSION_PORT, &host, &port) < 0) {
-               *error_r = t_strdup_printf(
-                       "Invalid submission_host: %s", host);
-               return -1;
+       if (net_str2hostport(set->submission_host,
+                       DEFAULT_SUBMISSION_PORT, &host, &port) < 0) {
+               smtp_submit_delayed_error(subm, t_strdup_printf(
+                       "Invalid submission_host: %s", host));
+               return;
        }
 
        i_zero(&client_set);
        client_set.mail_from = subm->return_path == NULL ? "<>" :
                t_strconcat("<", subm->return_path, ">", NULL);
-       client_set.my_hostname = subm->set.hostname;
+       client_set.my_hostname = set->hostname;
        client_set.timeout_secs = timeout_secs;
 
-       ioloop = io_loop_create();
-       lmtp_client = lmtp_client_init(&client_set, smtp_submit_send_finished,
-                                 subm);
+       lmtp_client = lmtp_client_init(&client_set,
+               smtp_submit_send_host_finished, subm);
 
        if (lmtp_client_connect_tcp(lmtp_client, LMTP_CLIENT_PROTOCOL_SMTP,
                                    host, port) < 0) {
+               smtp_submit_delayed_error(subm, t_strdup_printf(
+                       "Couldn't connect to %s:%u", host, port));
                lmtp_client_deinit(&lmtp_client);
-               io_loop_destroy(&ioloop);
-               *error_r = t_strdup_printf("Couldn't connect to %s:%u",
-                                          host, port);
-               return -1;
+               return;
        }
 
        array_foreach(&subm->destinations, destp) {
@@ -160,39 +281,41 @@ smtp_submit_send_host(struct smtp_submit *subm,
                                     data_callback, subm);
        }
 
+       subm->lmtp_client = lmtp_client;
+
        lmtp_client_send(lmtp_client, subm->input);
        i_stream_unref(&subm->input);
+}
 
-       if (!subm->finished)
-               io_loop_run(ioloop);
-       lmtp_client_deinit(&lmtp_client);
-       io_loop_destroy(&ioloop);
-
-       if (subm->success)
-               return 1;
-       else if (subm->tempfail) {
-               i_assert(subm->error != NULL);
-               *error_r = t_strdup(subm->error);
-               return -1;
-       } else {
-               i_assert(subm->error != NULL);
-               *error_r = t_strdup(subm->error);
-               return 0;
+static void
+smtp_submit_sendmail_callback(int status, struct smtp_submit *subm)
+{
+       if (status < 0) {
+               smtp_submit_callback(subm, -1,
+                       "Failed to execute sendmail");
+               return;
+       }
+       if (status == 0) {
+               smtp_submit_callback(subm, -1,
+                       "Sendmail program returned error");
+               return;
        }
+
+       smtp_submit_callback(subm, 1, NULL);
 }
 
-static int
+static void
 smtp_submit_send_sendmail(struct smtp_submit *subm,
-                      unsigned int timeout_secs, const char **error_r)
+                      unsigned int timeout_secs)
 {
+       const struct smtp_submit_settings *set = &subm->session->set;
        const char *const *sendmail_args, *sendmail_bin, *str;
        ARRAY_TYPE(const_string) args;
        unsigned int i;
        struct program_client_settings pc_set;
        struct program_client *pc;
-       int ret;
 
-       sendmail_args = t_strsplit(subm->set.sendmail_path, " ");
+       sendmail_args = t_strsplit(set->sendmail_path, " ");
        t_array_init(&args, 16);
        i_assert(sendmail_args[0] != NULL);
        sendmail_bin = sendmail_args[0];
@@ -221,57 +344,75 @@ smtp_submit_send_sendmail(struct smtp_submit *subm,
        program_client_set_input(pc, subm->input);
        i_stream_unref(&subm->input);
 
-       ret = program_client_run(pc);
+       subm->prg_client = pc;
 
-       program_client_destroy(&pc);
+       program_client_run_async(pc, smtp_submit_sendmail_callback, subm);
+}
 
-       if (ret < 0) {
-               *error_r = "Failed to execute sendmail";
-               return -1;
-       } else if (ret == 0) {
-               *error_r = "Sendmail program returned error";
-               return -1;
-       }
-       return 1;
+struct smtp_submit_run_context {
+       int status;
+       char *error;
+};
+
+static void
+smtp_submit_run_callback(const struct smtp_submit_result *result,
+       struct smtp_submit_run_context *rctx)
+{
+       rctx->error = i_strdup(result->error);
+       rctx->status = result->status;
+       io_loop_stop(current_ioloop);
 }
 
-void smtp_submit_abort(struct smtp_submit **_subm)
+int smtp_submit_run_timeout(struct smtp_submit *subm,
+                              unsigned int timeout_secs, const char **error_r)
 {
-       struct smtp_submit *subm = *_subm;
+       struct smtp_submit_run_context rctx;
+       struct ioloop *ioloop;
 
-       *_subm = NULL;
+       ioloop = io_loop_create();
+       io_loop_set_running(ioloop);
 
-       if (subm->output != NULL) {
-               o_stream_ignore_last_errors(subm->output);
-               o_stream_destroy(&subm->output);
+       i_zero(&rctx);
+       smtp_submit_run_async(subm, timeout_secs,
+               smtp_submit_run_callback, &rctx);
+
+       if (io_loop_is_running(ioloop))
+               io_loop_run(ioloop);
+
+       io_loop_destroy(&ioloop);
+
+       if (rctx.error == NULL)
+               *error_r = NULL;
+       else {
+               *error_r = t_strdup(rctx.error);
+               i_free(rctx.error);
        }
-       if (subm->input != NULL)
-               i_stream_destroy(&subm->input);
-       pool_unref(&subm->pool);
+
+       return rctx.status;
 }
 
-int smtp_submit_deinit(struct smtp_submit *subm, const char **error_r)
+int smtp_submit_run(struct smtp_submit *submit, const char **error_r)
 {
-       return smtp_submit_deinit_timeout(subm, 0, error_r);
+       return smtp_submit_run_timeout(submit, 0, error_r);
 }
 
-int smtp_submit_deinit_timeout(struct smtp_submit *subm,
-                              unsigned int timeout_secs, const char **error_r)
+#undef smtp_submit_run_async
+void smtp_submit_run_async(struct smtp_submit *subm,
+                              unsigned int timeout_secs,
+                              smtp_submit_callback_t *callback, void *context)
 {
-       int ret;
+       const struct smtp_submit_settings *set = &subm->session->set;
+
+       subm->callback = callback;
+       subm->context = context;
 
        /* the mail has been written to a file. now actually send it. */
        subm->input = iostream_temp_finish
                (&subm->output, IO_BLOCK_SIZE);
 
-       if (subm->set.submission_host != NULL) {
-               ret = smtp_submit_send_host
-                       (subm, timeout_secs, error_r);
+       if (set->submission_host != NULL) {
+               smtp_submit_send_host(subm, timeout_secs);
        } else {
-               ret = smtp_submit_send_sendmail
-                       (subm, timeout_secs, error_r);
+               smtp_submit_send_sendmail(subm, timeout_secs);
        }
-
-       smtp_submit_abort(&subm);
-       return ret;
 }
index 25d255bdf2d2bafc564495d6560a62d5b1695d4a..c461397e367a7526d8c528f1cdf199e188318be4 100644 (file)
@@ -1,25 +1,63 @@
 #ifndef SMTP_SUBMIT_H
 #define SMTP_SUBMIT_H
 
+struct smtp_submit_session;
+struct smtp_submit;
+
 struct smtp_submit_settings {
        const char *hostname;
        const char *submission_host;
        const char *sendmail_path;
 };
 
-struct smtp_submit * ATTR_NULL(3)
-smtp_submit_init(const struct smtp_submit_settings *set,
+struct smtp_submit_result {
+       /* 1 on success,
+          0 on permanent failure (e.g. invalid destination),
+         -1 on temporary failure */
+       int status;
+
+       const char *error;
+};
+
+typedef void
+smtp_submit_callback_t(const struct smtp_submit_result *result,
+       void *context);
+
+/* Use submit session to reuse resources (e.g. SMTP connections) between
+   submissions (FIXME: actually implement this) */
+struct smtp_submit_session *
+smtp_submit_session_init(const struct smtp_submit_settings *set);
+void smtp_submit_session_deinit(struct smtp_submit_session **_session);
+
+struct smtp_submit *
+smtp_submit_init(struct smtp_submit_session *session,
+       const char *return_path);
+struct smtp_submit *
+smtp_submit_init_simple(const struct smtp_submit_settings *set,
        const char *return_path);
+void smtp_submit_deinit(struct smtp_submit **_sct);
+
 /* Add a new recipient */
 void smtp_submit_add_rcpt(struct smtp_submit *subm, const char *address);
 /* Get an output stream where the message can be written to. The recipients
    must already be added before calling this. */
 struct ostream *smtp_submit_send(struct smtp_submit *subm);
-void smtp_submit_abort(struct smtp_submit **_subm);
+
+/* Submit the message. Callback is called once the message submission
+   finishes. */
+void smtp_submit_run_async(struct smtp_submit *subm,
+                              unsigned int timeout_secs,
+                              smtp_submit_callback_t *callback, void *context);
+#define smtp_submit_run_async(subm, timeout_secs, callback, context) \
+       smtp_submit_run_async(subm, timeout_secs, \
+               (smtp_submit_callback_t*)callback, \
+               (char*)context + CALLBACK_TYPECHECK(callback, \
+                       void (*)(const struct smtp_submit_result *result, typeof(context))))
+
 /* Returns 1 on success, 0 on permanent failure (e.g. invalid destination),
    -1 on temporary failure. */
-int smtp_submit_deinit(struct smtp_submit *subm, const char **error_r);
-/* Same as smtp_submit_deinit(), but timeout after given number of seconds. */
-int smtp_submit_deinit_timeout(struct smtp_submit *subm,
+int smtp_submit_run(struct smtp_submit *subm, const char **error_r);
+/* Same as smtp_submit_run(), but timeout after given number of seconds. */
+int smtp_submit_run_timeout(struct smtp_submit *subm,
                               unsigned int timeout_secs, const char **error_r);
 #endif
index e96ada704b1260b43fe265b65ee6547e5b1cd2c1..d3215c909e070bfc4fa5581a1f1dfb38ad80ae4c 100644 (file)
@@ -30,6 +30,17 @@ static const char *test_message1 =
        "From: sender@example.com\r\n"
        "\r\n"
        "Test message\r\n";
+static const char *test_message2 =
+       "Subject: Test message\r\n"
+       "To: rcpt@example.com\r\n"
+       "From: sender@example.com\r\n"
+       "\r\n"
+       "Test message Test message Test message Test message Test message\r\n"
+       "Test message Test message Test message Test message Test message\r\n"
+       "Test message Test message Test message Test message Test message\r\n"
+       "Test message Test message Test message Test message Test message\r\n"
+       "Test message Test message Test message Test message Test message\r\n"
+       "Test message Test message Test message Test message Test message\r\n";
 
 /*
  * Types
@@ -748,16 +759,18 @@ test_client_denied_second_rcpt(const struct smtp_submit_settings *submit_set)
        smtp_submit_set.submission_host =
                t_strdup_printf("127.0.0.1:%u", bind_ports[0]);
 
-       smtp_submit = smtp_submit_init(&smtp_submit_set, "sender@example.com");
+       smtp_submit = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
 
        smtp_submit_add_rcpt(smtp_submit, "rcpt@example.com");
        smtp_submit_add_rcpt(smtp_submit, "rcpt2@example.com");
        output = smtp_submit_send(smtp_submit);
        o_stream_send_str(output, test_message1);
 
-       ret = smtp_submit_deinit_timeout(smtp_submit, 5, &error);
+       ret = smtp_submit_run_timeout(smtp_submit, 1000, &error);
        test_out_reason("run (ret == 0)", ret == 0, error);
 
+       smtp_submit_deinit(&smtp_submit);
+
        return FALSE;
 }
 
@@ -1423,6 +1436,80 @@ test_client_successful_delivery(const struct smtp_submit_settings *submit_set)
        return FALSE;
 }
 
+struct _parallel_delivery_client {
+       unsigned int count;
+};
+
+static void
+test_client_parallel_delivery_callback(const struct smtp_submit_result *result,
+       struct _parallel_delivery_client *ctx)
+{
+       if (result->status <= 0)
+               i_error("Submit failed: %s", result->error);
+
+       if (--ctx->count == 0)
+               io_loop_stop(current_ioloop);
+}
+
+static bool
+test_client_parallel_delivery(const struct smtp_submit_settings *submit_set)
+{
+       struct smtp_submit_settings smtp_submit_set;
+       struct _parallel_delivery_client *ctx;
+       struct smtp_submit *smtp_submit1, *smtp_submit2;
+       struct ostream *output;
+       struct ioloop *ioloop;
+
+       ioloop = io_loop_create();
+
+       ctx = i_new(struct _parallel_delivery_client, 1);
+       ctx->count = 2;
+
+       smtp_submit_set = *submit_set;
+
+       /* submit 1 */
+       smtp_submit_set.submission_host =
+               t_strdup_printf("127.0.0.1:%u",  bind_ports[0]);
+       smtp_submit1 = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
+
+       smtp_submit_add_rcpt(smtp_submit1, "rcpt@example.com");
+       output = smtp_submit_send(smtp_submit1);
+       o_stream_send_str(output, test_message1);
+
+       smtp_submit_run_async(smtp_submit1, 5,
+               test_client_parallel_delivery_callback, ctx);
+
+       /* submit 2 */
+       smtp_submit_set.submission_host =
+               t_strdup_printf("127.0.0.1:%u",  bind_ports[1]);
+       smtp_submit2 = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
+
+       smtp_submit_add_rcpt(smtp_submit2, "rcpt@example.com");
+       output = smtp_submit_send(smtp_submit2);
+       o_stream_send_str(output, test_message2);
+
+       smtp_submit_run_async(smtp_submit2, 5,
+               test_client_parallel_delivery_callback, ctx);
+
+       io_loop_run(ioloop);
+
+       smtp_submit_deinit(&smtp_submit1);
+       smtp_submit_deinit(&smtp_submit2);
+       io_loop_destroy(&ioloop);
+
+       /* verify delivery */
+       test_message_delivery(test_message1,
+               t_strdup_printf("%s/message-%u.eml",
+                       test_tmp_dir_get(), bind_ports[0]));
+       test_message_delivery(test_message2,
+               t_strdup_printf("%s/message-%u.eml",
+                       test_tmp_dir_get(), bind_ports[1]));
+
+       i_free(ctx);
+
+       return FALSE;
+}
+
 /* test */
 
 static void test_successful_delivery(void)
@@ -1437,6 +1524,13 @@ static void test_successful_delivery(void)
                test_client_successful_delivery,
                test_server_successful_delivery, 1);
        test_end();
+
+       test_begin("parallel delivery");
+       test_expect_errors(0);
+       test_run_client_server(&smtp_submit_set,
+               test_client_parallel_delivery,
+               test_server_successful_delivery, 2);
+       test_end();
 }
 
 /*
@@ -1459,15 +1553,17 @@ test_client_failed_sendmail(const struct smtp_submit_settings *submit_set)
        smtp_submit_set = *submit_set;
        smtp_submit_set.sendmail_path = sendmail_path;
 
-       smtp_submit = smtp_submit_init(&smtp_submit_set, "sender@example.com");
+       smtp_submit = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
 
        smtp_submit_add_rcpt(smtp_submit, "rcpt@example.com");
        output = smtp_submit_send(smtp_submit);
        o_stream_send_str(output, test_message1);
 
-       ret = smtp_submit_deinit_timeout(smtp_submit, 5, &error);
+       ret = smtp_submit_run_timeout(smtp_submit, 5, &error);
        test_out_reason("run (ret < 0)", ret < 0, error);
 
+       smtp_submit_deinit(&smtp_submit);
+
        return FALSE;
 }
 
@@ -1509,15 +1605,17 @@ test_client_successful_sendmail(const struct smtp_submit_settings *submit_set)
        smtp_submit_set = *submit_set;
        smtp_submit_set.sendmail_path = sendmail_path;
 
-       smtp_submit = smtp_submit_init(&smtp_submit_set, "sender@example.com");
+       smtp_submit = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
 
        smtp_submit_add_rcpt(smtp_submit, "rcpt@example.com");
        output = smtp_submit_send(smtp_submit);
        o_stream_send_str(output, test_message1);
 
-       ret = smtp_submit_deinit_timeout(smtp_submit, 5, &error);
+       ret = smtp_submit_run_timeout(smtp_submit, 5, &error);
        test_out_reason("run (ret > 0)", ret > 0, error);
 
+       smtp_submit_deinit(&smtp_submit);
+
        /* verify delivery */
        test_message_delivery(test_message1, msg_path);
 
@@ -1539,6 +1637,103 @@ static void test_successful_sendmail(void)
        test_end();
 }
 
+/*
+ * Parallel sendmail
+ */
+
+/* client */
+
+struct _parallel_sendmail_client {
+       unsigned int count;
+};
+
+static void
+test_client_parallel_sendmail_callback(const struct smtp_submit_result *result,
+       struct _parallel_sendmail_client *ctx)
+{
+       if (result->status <= 0)
+               i_error("Submit failed: %s", result->error);
+
+       if (--ctx->count == 0)
+               io_loop_stop(current_ioloop);
+}
+
+static bool
+test_client_parallel_sendmail(const struct smtp_submit_settings *submit_set)
+{
+       struct smtp_submit_settings smtp_submit_set;
+       struct _parallel_sendmail_client *ctx;
+       struct smtp_submit *smtp_submit1, *smtp_submit2;
+       struct ostream *output;
+       const char *sendmail_path1, *sendmail_path2;
+       const char *msg_path1, *msg_path2;
+       struct ioloop *ioloop;
+
+       ctx = i_new(struct _parallel_sendmail_client, 1);
+       ctx->count = 2;
+
+       ioloop = io_loop_create();
+
+       msg_path1 = t_strdup_printf("%s/message1.eml", test_tmp_dir_get());
+       msg_path2 = t_strdup_printf("%s/message2.eml", test_tmp_dir_get());
+
+       sendmail_path1 = t_strdup_printf(
+               TEST_BIN_DIR"/sendmail-success.sh %s", msg_path1);
+       sendmail_path2 = t_strdup_printf(
+               TEST_BIN_DIR"/sendmail-success.sh %s", msg_path2);
+
+       smtp_submit_set = *submit_set;
+
+       /* submit 1 */
+       smtp_submit_set.sendmail_path = sendmail_path1;
+       smtp_submit1 = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
+
+       smtp_submit_add_rcpt(smtp_submit1, "rcpt@example.com");
+       output = smtp_submit_send(smtp_submit1);
+       o_stream_send_str(output, test_message1);
+
+       smtp_submit_run_async(smtp_submit1, 5,
+               test_client_parallel_sendmail_callback, ctx);
+
+       /* submit 2 */
+       smtp_submit_set.sendmail_path = sendmail_path2;
+       smtp_submit2 = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
+
+       smtp_submit_add_rcpt(smtp_submit2, "rcpt@example.com");
+       output = smtp_submit_send(smtp_submit2);
+       o_stream_send_str(output, test_message2);
+
+       smtp_submit_run_async(smtp_submit2, 5,
+               test_client_parallel_sendmail_callback, ctx);
+
+       io_loop_run(ioloop);
+
+       smtp_submit_deinit(&smtp_submit1);
+       smtp_submit_deinit(&smtp_submit2);
+       io_loop_destroy(&ioloop);
+
+       /* verify delivery */
+       test_message_delivery(test_message1, msg_path1);
+       test_message_delivery(test_message2, msg_path2);
+
+       return FALSE;
+}
+
+/* test */
+
+static void test_parallel_sendmail(void)
+{
+       struct smtp_submit_settings smtp_submit_set;
+
+       test_client_defaults(&smtp_submit_set);
+
+       test_begin("parallel sendmail");
+       test_expect_errors(0);
+       test_run_client_server(&smtp_submit_set,
+               test_client_parallel_sendmail, NULL, 0);
+       test_end();
+}
+
 /*
  * All tests
  */
@@ -1560,6 +1755,7 @@ static void (*const test_functions[])(void) = {
        test_successful_delivery,
        test_failed_sendmail,
        test_successful_sendmail,
+       test_parallel_sendmail,
        NULL
 };
 
@@ -1588,19 +1784,23 @@ test_client_smtp_send_simple(const struct smtp_submit_settings *smtp_set,
        struct smtp_submit_settings smtp_submit_set;
        struct smtp_submit *smtp_submit;
        struct ostream *output;
+       int ret;
 
        /* send the message */
        smtp_submit_set = *smtp_set;
        smtp_submit_set.submission_host = host,
 
-       smtp_submit = smtp_submit_init(&smtp_submit_set, "sender@example.com");
+       smtp_submit = smtp_submit_init_simple(&smtp_submit_set, "sender@example.com");
 
        smtp_submit_add_rcpt(smtp_submit, "rcpt@example.com");
        output = smtp_submit_send(smtp_submit);
        o_stream_send_str(output, message);
 
-       return smtp_submit_deinit_timeout
-               (smtp_submit, timeout_secs, error_r);
+       ret = smtp_submit_run_timeout(smtp_submit, timeout_secs, error_r);
+
+       smtp_submit_deinit(&smtp_submit);
+
+       return ret;
 }
 
 static int