Atualizado em 19/06/2026
Os e-mails que a sua loja envia — confirmação de pedido, aviso de processamento, nota de conclusão, fatura — não são templates soltos: cada um é uma classe que estende WC_Email, registrada no sistema de e-mails e disparada por uma transição de status do pedido. Quem entende essa arquitetura customiza com cirurgia (injeta um aviso, muda um assunto, cria um e-mail novo) e, principalmente, evita o acidente clássico de disparar centenas de mensagens numa importação. Este artigo cobre os três níveis de customização — hooks de conteúdo, override de template e criação de um e-mail próprio — além da parte que mais derruba projetos: entregabilidade.
Como o sistema de e-mails funciona
O WooCommerce mantém uma coleção de objetos WC_Email, acessível via WC()->mailer()->get_emails() e gerenciável em WooCommerce › Ajustes › E-mails. Cada e-mail tem um id único (é a chave para quase tudo), um destinatário (cliente ou administrador), assunto, cabeçalho e dois templates: um HTML e um texto puro.
O gatilho da maioria é uma transição de status. Quando um pedido passa para "processing", o WooCommerce dispara o hook woocommerce_order_status_processing, ao qual o e-mail "processando pedido" está conectado pelo seu trigger(). É por isso que mudar status por código tem efeito colateral: usar $order->update_status( 'completed' ) envia o e-mail de pedido concluído — o que normalmente é desejável, mas em uma migração de milhares de pedidos é desastre.
| E-mail (id) | Para | Gatilho (status / momento) |
|---|---|---|
new_order | Admin | Pedido pago entra (pending → processing/completed) |
customer_processing_order | Cliente | Status muda para processando |
customer_completed_order | Cliente | Status muda para concluído |
customer_on_hold_order | Cliente | Status muda para aguardando (on-hold) |
customer_refunded_order | Cliente | Pedido reembolsado |
cancelled_order / failed_order | Admin | Status cancelado / falhou |
customer_invoice | Cliente | Manual (fatura/pagamento pendente, a partir do pedido) |
customer_note | Cliente | Manual (nota adicionada ao cliente) |
customer_new_account | Cliente | Criação de conta |
Cuidado com disparos em massa. Importações, sincronizações com ERP e migrações frequentemente alteram status de muitos pedidos. Cada transição pode disparar um e-mail real. Antes de rodar um script desses, considere suspender o mailer (remover temporariamente os hooks de envio) ou usar métodos que não transicionam status. Testar em staging não é opcional aqui.
Nível 1 — Injetar conteúdo com hooks
O jeito mais seguro de customizar (porque sobrevive a atualizações) é injetar conteúdo nos hooks que o template de pedido expõe: woocommerce_email_before_order_table e woocommerce_email_after_order_table (antes/depois da tabela de itens) e woocommerce_email_order_meta (na área de metadados). O detalhe crítico: o callback roda para todos os e-mails, então cheque $email->id para agir só onde deseja — e escape tudo:
<?php
add_action( 'woocommerce_email_before_order_table', 'devwp_aviso_no_email', 20, 4 );
/**
* @param WC_Order $order Pedido.
* @param bool $sent_to_admin Se vai para o admin.
* @param bool $plain_text Se é a versão texto puro.
* @param WC_Email $email O objeto do e-mail (tem ->id).
*/
function devwp_aviso_no_email( $order, $sent_to_admin, $plain_text, $email ) {
// Só no e-mail de pedido CONCLUÍDO enviado ao CLIENTE
if ( empty( $email->id ) || 'customer_completed_order' !== $email->id ) {
return;
}
$prazo = esc_html( get_option( 'devwp_prazo_garantia', '7 dias' ) );
if ( $plain_text ) {
echo "\nTrocas e devolucoes em ate {$prazo}.\n";
} else {
echo '<p style="margin:16px 0;padding:12px;background:#f7f7f7;">'
. 'Trocas e devoluções em até <strong>' . $prazo . '</strong>.'
. '</p>';
}
}
Para mudar o assunto ou o título de um e-mail específico, existem filtros dinâmicos que terminam com o id do e-mail — não precisa checar $email->id porque o filtro já é específico. Também dá para acrescentar cabeçalhos (por exemplo, um Bcc) via woocommerce_email_headers:
<?php
// Assunto do e-mail de pedido concluído (filtro dinâmico por id)
add_filter( 'woocommerce_email_subject_customer_completed_order', function ( $subject, $order ) {
return sprintf( 'Seu pedido #%s foi enviado!', $order->get_order_number() );
}, 10, 2 );
// Adiciona um Bcc de auditoria a todos os e-mails (cuidado com privacidade)
add_filter( 'woocommerce_email_headers', function ( $headers, $email_id, $order ) {
if ( 'customer_completed_order' === $email_id ) {
$headers .= 'Bcc: [email protected]' . "\r\n";
}
return $headers;
}, 10, 3 );
Nível 2 — Sobrescrever o template do e-mail
Quando a mudança é estrutural (layout, ordem dos blocos, branding pesado), copie o template do e-mail para o tema. Os arquivos vivem em woocommerce/templates/emails/; a cópia vai para seu-tema/woocommerce/emails/ com o mesmo nome. O WooCommerce então usa a sua versão. Cabeçalho e rodapé compartilhados são email-header.php e email-footer.php:
// Copie mantendo o caminho relativo (de -> para):
wp-content/plugins/woocommerce/templates/emails/customer-completed-order.php
-> wp-content/themes/SEU-TEMA/woocommerce/emails/customer-completed-order.php
// Cabeçalho e rodapé (afetam TODOS os e-mails):
.../woocommerce/templates/emails/email-header.php
-> .../themes/SEU-TEMA/woocommerce/emails/email-header.php
.../woocommerce/templates/emails/email-footer.php
-> .../themes/SEU-TEMA/woocommerce/emails/email-footer.php
Sobrescrever um template congela aquele arquivo na versão em que você copiou: melhorias e correções que o WooCommerce publicar nele não chegam à sua loja. Por isso, prefira os hooks do Nível 1 sempre que possível e reserve o override para o que não dá para fazer por hook. Quando atualizar o WooCommerce, revise se os templates copiados ficaram defasados (o painel de Status avisa quando há templates desatualizados). Detalhes em sobrescrever templates do WooCommerce.
Nível 3 — Criar um e-mail transacional novo
Para um e-mail que o WooCommerce não tem (por exemplo, "pedido pronto para retirada"), você cria uma classe que estende WC_Email. Ela define o id, o título/descrição que aparecem em Ajustes › E-mails, os templates e — o mais importante — um método trigger() que monta e envia a mensagem. No construtor, você conecta o trigger() ao hook que deve dispará-lo:
<?php
add_action( 'plugins_loaded', function () {
if ( ! class_exists( 'WC_Email' ) ) {
return; // WooCommerce ainda não carregou
}
class DevWP_Email_Pronto_Retirada extends WC_Email {
public function __construct() {
$this->id = 'devwp_pronto_retirada';
$this->title = 'Pedido pronto para retirada';
$this->description = 'Avisa o cliente que o pedido pode ser retirado.';
$this->customer_email = true;
$this->template_html = 'emails/devwp-pronto-retirada.php';
$this->template_plain = 'emails/plain/devwp-pronto-retirada.php';
// Permite carregar os templates da pasta do seu plugin
$this->template_base = plugin_dir_path( __FILE__ ) . 'templates/';
// Dispara quando o pedido entra em status custom "pronto-retirada"
add_action( 'woocommerce_order_status_pronto-retirada_notification',
array( $this, 'trigger' ), 10, 2 );
parent::__construct();
$this->recipient = $this->get_option( 'recipient', '' );
}
public function get_default_subject() {
return 'Seu pedido #{order_number} está pronto para retirada';
}
public function get_default_heading() {
return 'Pode retirar seu pedido';
}
public function trigger( $order_id, $order = false ) {
$this->setup_locale();
if ( $order_id && ! $order instanceof WC_Order ) {
$order = wc_get_order( $order_id );
}
if ( $order instanceof WC_Order ) {
$this->object = $order;
$this->recipient = $order->get_billing_email();
// placeholders {order_number}, {order_date}, etc.
$this->placeholders['{order_number}'] = $order->get_order_number();
}
if ( $this->is_enabled() && $this->get_recipient() ) {
$this->send(
$this->get_recipient(),
$this->get_subject(),
$this->get_content(),
$this->get_headers(),
$this->get_attachments()
);
}
$this->restore_locale();
}
public function get_content_html() {
return wc_get_template_html( $this->template_html, array(
'order' => $this->object,
'email_heading' => $this->get_heading(),
'sent_to_admin' => false,
'plain_text' => false,
'email' => $this,
), '', $this->template_base );
}
}
} );
Criada a classe, falta registrá-la para que o WooCommerce a instancie e a mostre no painel. É o papel do filtro woocommerce_email_classes — adicione a sua classe ao array, indexada pelo id:
<?php
add_filter( 'woocommerce_email_classes', function ( $emails ) {
$emails['DevWP_Email_Pronto_Retirada'] = new DevWP_Email_Pronto_Retirada();
return $emails;
} );
// Se o gatilho é um status custom, registre o "_notification" também:
add_action( 'woocommerce_order_status_pronto-retirada', function ( $order_id ) {
do_action( 'woocommerce_order_status_pronto-retirada_notification', $order_id );
} );
A partir daqui o seu e-mail é cidadão de primeira classe: aparece em Ajustes › E-mails, pode ser ligado/desligado, ter assunto e destinatário editados, e usa o mesmo cabeçalho/rodapé dos demais. Basta criar os arquivos de template em templates/emails/ dentro do plugin.
Entregabilidade e o modo de teste
De nada adianta um e-mail bonito que não chega. Por padrão o WordPress usa wp_mail() sobre o mail() do PHP, sem autenticação e com remetente que raramente bate com o domínio — combinação que cai em spam ou é recusada. A correção profissional é enviar por SMTP autenticado ou por um serviço transacional (Amazon SES, Postmark, SendGrid, Mailgun), com SPF, DKIM e DMARC configurados no DNS do domínio.
Sempre teste. Faça um pedido real em staging e percorra os status para ver cada e-mail. Inspecione a versão HTML e a texto puro. Use uma ferramenta que mostre se passou em SPF/DKIM/DMARC. Lembre que o "inliner" de CSS do WooCommerce ajuda, mas clientes como Outlook ainda descartam CSS moderno — mantenha o markup simples e baseado em tabelas, herdando do template padrão.
Armadilhas comuns
- Não checar
$email->id. Hooks de conteúdo rodam para todos os e-mails; sem oif, o seu texto vaza em mensagens onde não deveria. - Esquecer o escaping. Tudo que você imprime no e-mail deve passar por
esc_html()/esc_url()— dados de pedido vêm do cliente. - Disparo em massa. Alterar status em importações/migrações dispara e-mails reais; suspenda o mailer antes.
- Override que apodrece. Template copiado para o tema não recebe atualizações do WooCommerce; revise após cada update.
- Confiar no
mail()do PHP. Sem SMTP/SPF/DKIM, e-mail transacional vira spam. - HTML complexo. CSS externo e JavaScript não funcionam em e-mail; use tabelas e estilos inline.
Boas práticas
- Hook antes de template. Prefira
before/after_order_tableeorder_metaa sobrescrever arquivos. - Specificidade por
id. Use filtros dinâmicos (..._subject_{id}) ou cheque$email->idpara mirar um e-mail só. - Herde a classe certa. Ao criar um e-mail, estenda
WC_Emaile reutilizeemail-header/footer; não reinvente o layout. - SMTP sempre. Em produção, envie por SMTP/serviço transacional com autenticação de domínio.
- Teste os dois formatos. HTML e texto puro, em vários clientes, antes de publicar.
E-mails transacionais são a voz da sua loja em cada etapa do pedido — e se ligam diretamente às transições de status que você dispara pelos hooks do WooCommerce e pela CRUD API de pedidos. Quando a customização for de layout, faça com cuidado o override de templates. Se a sua loja precisa de e-mails customizados, melhoria de entregabilidade ou um fluxo de notificações sob medida, conheça os serviços de suporte e manutenção WooCommerce ou veja os demais artigos de desenvolvimento.
Perguntas frequentes
Quando os e-mails do WooCommerce são disparados?
A maioria é disparada por transições de status do pedido. Ao mudar para "processando" sai o e-mail de pedido em processamento; ao ir para "concluído", o de pedido concluído; ao ser criado um pedido pago, o "novo pedido" para o administrador. Outros são manuais (a fatura/invoice e a nota do cliente são enviadas a partir do pedido no admin). Por isso, mexer em status por código — inclusive via update_status() — pode disparar e-mails reais; em importações e migrações, é fácil enviar centenas de mensagens sem querer.
Como customizo o conteúdo de um e-mail sem editar o template?
Use os hooks de conteúdo: woocommerce_email_before_order_table, woocommerce_email_after_order_table e woocommerce_email_order_meta injetam HTML em pontos específicos de todos os e-mails de pedido. Como o callback roda para todos os e-mails, sempre cheque $email->id para agir só onde quer (por exemplo, apenas em customer_completed_order) e escape tudo que imprimir. Para mudar texto fixo, há filtros como woocommerce_email_subject_{id} e woocommerce_email_heading_{id}.
Como sobrescrevo o visual (template) de um e-mail?
Copiando o template de woocommerce/templates/emails/ para seu-tema/woocommerce/emails/, mantendo o mesmo nome e caminho relativo. O WooCommerce passa a usar a sua cópia. Para o cabeçalho/rodapé compartilhados, são emails/email-header.php e emails/email-footer.php. Cuidado: ao sobrescrever, você "congela" aquele arquivo — atualizações do WooCommerce não o alcançam, então é preciso revisar a cópia quando o plugin atualiza. Veja sobrescrever templates.
Como crio um e-mail transacional totalmente novo?
Você cria uma classe que estende WC_Email (definindo id, title, templates e um método trigger()), registra-a no filtro woocommerce_email_classes para que apareça em Ajustes › E-mails, e dispara o trigger() a partir de um hook (geralmente uma transição de status como woocommerce_order_status_completed, ou um do_action próprio). A partir daí ela é tratada como qualquer e-mail nativo: ligar/desligar, assunto, destinatário, tudo gerenciável no painel.
Por que os e-mails do WooCommerce não chegam (caem no spam)?
Quase sempre é entregabilidade, não bug. Por padrão o WordPress envia com wp_mail()/PHP mail(), sem autenticação, de um remetente que não bate com o domínio — receita para spam ou bloqueio. A correção é enviar por SMTP autenticado (ou um serviço transacional como Amazon SES, Postmark, SendGrid), com SPF, DKIM e DMARC do domínio configurados. Teste sempre antes de ir para produção; um e-mail de pedido que não chega é venda perdida e suporte aberto.
Por que meu HTML some ou aparece quebrado no e-mail?
Porque e-mail não é navegador. Clientes como Outlook e Gmail descartam CSS externo, muito CSS moderno e até alguns seletores; o padrão prático ainda é HTML com tabelas e estilos inline. O WooCommerce até roda um "inliner" de CSS por padrão, mas markup complexo ou JavaScript simplesmente não funciona. Mantenha o conteúdo simples, teste em vários clientes e evite reinventar a estrutura do template — herde do email-header.php/email-footer.php existentes.